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, SaveOptions},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 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(HashSet<Entity<Buffer>>),
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(self.buffer.read(cx).all_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 perform_format(
15640 &mut self,
15641 project: Entity<Project>,
15642 trigger: FormatTrigger,
15643 target: FormatTarget,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) -> Task<Result<()>> {
15647 let buffer = self.buffer.clone();
15648 let (buffers, target) = match target {
15649 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15650 FormatTarget::Ranges(selection_ranges) => {
15651 let multi_buffer = buffer.read(cx);
15652 let snapshot = multi_buffer.read(cx);
15653 let mut buffers = HashSet::default();
15654 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15655 BTreeMap::new();
15656 for selection_range in selection_ranges {
15657 for (buffer, buffer_range, _) in
15658 snapshot.range_to_buffer_ranges(selection_range)
15659 {
15660 let buffer_id = buffer.remote_id();
15661 let start = buffer.anchor_before(buffer_range.start);
15662 let end = buffer.anchor_after(buffer_range.end);
15663 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15664 buffer_id_to_ranges
15665 .entry(buffer_id)
15666 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15667 .or_insert_with(|| vec![start..end]);
15668 }
15669 }
15670 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15671 }
15672 };
15673
15674 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15675 let selections_prev = transaction_id_prev
15676 .and_then(|transaction_id_prev| {
15677 // default to selections as they were after the last edit, if we have them,
15678 // instead of how they are now.
15679 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15680 // will take you back to where you made the last edit, instead of staying where you scrolled
15681 self.selection_history
15682 .transaction(transaction_id_prev)
15683 .map(|t| t.0.clone())
15684 })
15685 .unwrap_or_else(|| {
15686 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15687 self.selections.disjoint_anchors()
15688 });
15689
15690 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15691 let format = project.update(cx, |project, cx| {
15692 project.format(buffers, target, true, trigger, cx)
15693 });
15694
15695 cx.spawn_in(window, async move |editor, cx| {
15696 let transaction = futures::select_biased! {
15697 transaction = format.log_err().fuse() => transaction,
15698 () = timeout => {
15699 log::warn!("timed out waiting for formatting");
15700 None
15701 }
15702 };
15703
15704 buffer
15705 .update(cx, |buffer, cx| {
15706 if let Some(transaction) = transaction {
15707 if !buffer.is_singleton() {
15708 buffer.push_transaction(&transaction.0, cx);
15709 }
15710 }
15711 cx.notify();
15712 })
15713 .ok();
15714
15715 if let Some(transaction_id_now) =
15716 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15717 {
15718 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15719 if has_new_transaction {
15720 _ = editor.update(cx, |editor, _| {
15721 editor
15722 .selection_history
15723 .insert_transaction(transaction_id_now, selections_prev);
15724 });
15725 }
15726 }
15727
15728 Ok(())
15729 })
15730 }
15731
15732 fn organize_imports(
15733 &mut self,
15734 _: &OrganizeImports,
15735 window: &mut Window,
15736 cx: &mut Context<Self>,
15737 ) -> Option<Task<Result<()>>> {
15738 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15739 let project = match &self.project {
15740 Some(project) => project.clone(),
15741 None => return None,
15742 };
15743 Some(self.perform_code_action_kind(
15744 project,
15745 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15746 window,
15747 cx,
15748 ))
15749 }
15750
15751 fn perform_code_action_kind(
15752 &mut self,
15753 project: Entity<Project>,
15754 kind: CodeActionKind,
15755 window: &mut Window,
15756 cx: &mut Context<Self>,
15757 ) -> Task<Result<()>> {
15758 let buffer = self.buffer.clone();
15759 let buffers = buffer.read(cx).all_buffers();
15760 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15761 let apply_action = project.update(cx, |project, cx| {
15762 project.apply_code_action_kind(buffers, kind, true, cx)
15763 });
15764 cx.spawn_in(window, async move |_, cx| {
15765 let transaction = futures::select_biased! {
15766 () = timeout => {
15767 log::warn!("timed out waiting for executing code action");
15768 None
15769 }
15770 transaction = apply_action.log_err().fuse() => transaction,
15771 };
15772 buffer
15773 .update(cx, |buffer, cx| {
15774 // check if we need this
15775 if let Some(transaction) = transaction {
15776 if !buffer.is_singleton() {
15777 buffer.push_transaction(&transaction.0, cx);
15778 }
15779 }
15780 cx.notify();
15781 })
15782 .ok();
15783 Ok(())
15784 })
15785 }
15786
15787 fn restart_language_server(
15788 &mut self,
15789 _: &RestartLanguageServer,
15790 _: &mut Window,
15791 cx: &mut Context<Self>,
15792 ) {
15793 if let Some(project) = self.project.clone() {
15794 self.buffer.update(cx, |multi_buffer, cx| {
15795 project.update(cx, |project, cx| {
15796 project.restart_language_servers_for_buffers(
15797 multi_buffer.all_buffers().into_iter().collect(),
15798 cx,
15799 );
15800 });
15801 })
15802 }
15803 }
15804
15805 fn stop_language_server(
15806 &mut self,
15807 _: &StopLanguageServer,
15808 _: &mut Window,
15809 cx: &mut Context<Self>,
15810 ) {
15811 if let Some(project) = self.project.clone() {
15812 self.buffer.update(cx, |multi_buffer, cx| {
15813 project.update(cx, |project, cx| {
15814 project.stop_language_servers_for_buffers(
15815 multi_buffer.all_buffers().into_iter().collect(),
15816 cx,
15817 );
15818 cx.emit(project::Event::RefreshInlayHints);
15819 });
15820 });
15821 }
15822 }
15823
15824 fn cancel_language_server_work(
15825 workspace: &mut Workspace,
15826 _: &actions::CancelLanguageServerWork,
15827 _: &mut Window,
15828 cx: &mut Context<Workspace>,
15829 ) {
15830 let project = workspace.project();
15831 let buffers = workspace
15832 .active_item(cx)
15833 .and_then(|item| item.act_as::<Editor>(cx))
15834 .map_or(HashSet::default(), |editor| {
15835 editor.read(cx).buffer.read(cx).all_buffers()
15836 });
15837 project.update(cx, |project, cx| {
15838 project.cancel_language_server_work_for_buffers(buffers, cx);
15839 });
15840 }
15841
15842 fn show_character_palette(
15843 &mut self,
15844 _: &ShowCharacterPalette,
15845 window: &mut Window,
15846 _: &mut Context<Self>,
15847 ) {
15848 window.show_character_palette();
15849 }
15850
15851 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15852 if self.mode.is_minimap() {
15853 return;
15854 }
15855
15856 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15857 let buffer = self.buffer.read(cx).snapshot(cx);
15858 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15859 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15860 let is_valid = buffer
15861 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15862 .any(|entry| {
15863 entry.diagnostic.is_primary
15864 && !entry.range.is_empty()
15865 && entry.range.start == primary_range_start
15866 && entry.diagnostic.message == active_diagnostics.active_message
15867 });
15868
15869 if !is_valid {
15870 self.dismiss_diagnostics(cx);
15871 }
15872 }
15873 }
15874
15875 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15876 match &self.active_diagnostics {
15877 ActiveDiagnostic::Group(group) => Some(group),
15878 _ => None,
15879 }
15880 }
15881
15882 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15883 self.dismiss_diagnostics(cx);
15884 self.active_diagnostics = ActiveDiagnostic::All;
15885 }
15886
15887 fn activate_diagnostics(
15888 &mut self,
15889 buffer_id: BufferId,
15890 diagnostic: DiagnosticEntry<usize>,
15891 window: &mut Window,
15892 cx: &mut Context<Self>,
15893 ) {
15894 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15895 return;
15896 }
15897 self.dismiss_diagnostics(cx);
15898 let snapshot = self.snapshot(window, cx);
15899 let buffer = self.buffer.read(cx).snapshot(cx);
15900 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15901 return;
15902 };
15903
15904 let diagnostic_group = buffer
15905 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15906 .collect::<Vec<_>>();
15907
15908 let blocks =
15909 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15910
15911 let blocks = self.display_map.update(cx, |display_map, cx| {
15912 display_map.insert_blocks(blocks, cx).into_iter().collect()
15913 });
15914 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15915 active_range: buffer.anchor_before(diagnostic.range.start)
15916 ..buffer.anchor_after(diagnostic.range.end),
15917 active_message: diagnostic.diagnostic.message.clone(),
15918 group_id: diagnostic.diagnostic.group_id,
15919 blocks,
15920 });
15921 cx.notify();
15922 }
15923
15924 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15925 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15926 return;
15927 };
15928
15929 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15930 if let ActiveDiagnostic::Group(group) = prev {
15931 self.display_map.update(cx, |display_map, cx| {
15932 display_map.remove_blocks(group.blocks, cx);
15933 });
15934 cx.notify();
15935 }
15936 }
15937
15938 /// Disable inline diagnostics rendering for this editor.
15939 pub fn disable_inline_diagnostics(&mut self) {
15940 self.inline_diagnostics_enabled = false;
15941 self.inline_diagnostics_update = Task::ready(());
15942 self.inline_diagnostics.clear();
15943 }
15944
15945 pub fn diagnostics_enabled(&self) -> bool {
15946 self.mode.is_full()
15947 }
15948
15949 pub fn inline_diagnostics_enabled(&self) -> bool {
15950 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15951 }
15952
15953 pub fn show_inline_diagnostics(&self) -> bool {
15954 self.show_inline_diagnostics
15955 }
15956
15957 pub fn toggle_inline_diagnostics(
15958 &mut self,
15959 _: &ToggleInlineDiagnostics,
15960 window: &mut Window,
15961 cx: &mut Context<Editor>,
15962 ) {
15963 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15964 self.refresh_inline_diagnostics(false, window, cx);
15965 }
15966
15967 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15968 self.diagnostics_max_severity = severity;
15969 self.display_map.update(cx, |display_map, _| {
15970 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15971 });
15972 }
15973
15974 pub fn toggle_diagnostics(
15975 &mut self,
15976 _: &ToggleDiagnostics,
15977 window: &mut Window,
15978 cx: &mut Context<Editor>,
15979 ) {
15980 if !self.diagnostics_enabled() {
15981 return;
15982 }
15983
15984 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15985 EditorSettings::get_global(cx)
15986 .diagnostics_max_severity
15987 .filter(|severity| severity != &DiagnosticSeverity::Off)
15988 .unwrap_or(DiagnosticSeverity::Hint)
15989 } else {
15990 DiagnosticSeverity::Off
15991 };
15992 self.set_max_diagnostics_severity(new_severity, cx);
15993 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15994 self.active_diagnostics = ActiveDiagnostic::None;
15995 self.inline_diagnostics_update = Task::ready(());
15996 self.inline_diagnostics.clear();
15997 } else {
15998 self.refresh_inline_diagnostics(false, window, cx);
15999 }
16000
16001 cx.notify();
16002 }
16003
16004 pub fn toggle_minimap(
16005 &mut self,
16006 _: &ToggleMinimap,
16007 window: &mut Window,
16008 cx: &mut Context<Editor>,
16009 ) {
16010 if self.supports_minimap(cx) {
16011 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16012 }
16013 }
16014
16015 fn refresh_inline_diagnostics(
16016 &mut self,
16017 debounce: bool,
16018 window: &mut Window,
16019 cx: &mut Context<Self>,
16020 ) {
16021 let max_severity = ProjectSettings::get_global(cx)
16022 .diagnostics
16023 .inline
16024 .max_severity
16025 .unwrap_or(self.diagnostics_max_severity);
16026
16027 if !self.inline_diagnostics_enabled()
16028 || !self.show_inline_diagnostics
16029 || max_severity == DiagnosticSeverity::Off
16030 {
16031 self.inline_diagnostics_update = Task::ready(());
16032 self.inline_diagnostics.clear();
16033 return;
16034 }
16035
16036 let debounce_ms = ProjectSettings::get_global(cx)
16037 .diagnostics
16038 .inline
16039 .update_debounce_ms;
16040 let debounce = if debounce && debounce_ms > 0 {
16041 Some(Duration::from_millis(debounce_ms))
16042 } else {
16043 None
16044 };
16045 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16046 if let Some(debounce) = debounce {
16047 cx.background_executor().timer(debounce).await;
16048 }
16049 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16050 editor
16051 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16052 .ok()
16053 }) else {
16054 return;
16055 };
16056
16057 let new_inline_diagnostics = cx
16058 .background_spawn(async move {
16059 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16060 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16061 let message = diagnostic_entry
16062 .diagnostic
16063 .message
16064 .split_once('\n')
16065 .map(|(line, _)| line)
16066 .map(SharedString::new)
16067 .unwrap_or_else(|| {
16068 SharedString::from(diagnostic_entry.diagnostic.message)
16069 });
16070 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16071 let (Ok(i) | Err(i)) = inline_diagnostics
16072 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16073 inline_diagnostics.insert(
16074 i,
16075 (
16076 start_anchor,
16077 InlineDiagnostic {
16078 message,
16079 group_id: diagnostic_entry.diagnostic.group_id,
16080 start: diagnostic_entry.range.start.to_point(&snapshot),
16081 is_primary: diagnostic_entry.diagnostic.is_primary,
16082 severity: diagnostic_entry.diagnostic.severity,
16083 },
16084 ),
16085 );
16086 }
16087 inline_diagnostics
16088 })
16089 .await;
16090
16091 editor
16092 .update(cx, |editor, cx| {
16093 editor.inline_diagnostics = new_inline_diagnostics;
16094 cx.notify();
16095 })
16096 .ok();
16097 });
16098 }
16099
16100 fn pull_diagnostics(
16101 &mut self,
16102 buffer_id: Option<BufferId>,
16103 window: &Window,
16104 cx: &mut Context<Self>,
16105 ) -> Option<()> {
16106 if !self.mode().is_full() {
16107 return None;
16108 }
16109 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16110 .diagnostics
16111 .lsp_pull_diagnostics;
16112 if !pull_diagnostics_settings.enabled {
16113 return None;
16114 }
16115 let project = self.project.as_ref()?.downgrade();
16116 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16117 let mut buffers = self.buffer.read(cx).all_buffers();
16118 if let Some(buffer_id) = buffer_id {
16119 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16120 }
16121
16122 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16123 cx.background_executor().timer(debounce).await;
16124
16125 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16126 buffers
16127 .into_iter()
16128 .flat_map(|buffer| {
16129 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16130 })
16131 .collect::<FuturesUnordered<_>>()
16132 }) else {
16133 return;
16134 };
16135
16136 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16137 match pull_task {
16138 Ok(()) => {
16139 if editor
16140 .update_in(cx, |editor, window, cx| {
16141 editor.update_diagnostics_state(window, cx);
16142 })
16143 .is_err()
16144 {
16145 return;
16146 }
16147 }
16148 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16149 }
16150 }
16151 });
16152
16153 Some(())
16154 }
16155
16156 pub fn set_selections_from_remote(
16157 &mut self,
16158 selections: Vec<Selection<Anchor>>,
16159 pending_selection: Option<Selection<Anchor>>,
16160 window: &mut Window,
16161 cx: &mut Context<Self>,
16162 ) {
16163 let old_cursor_position = self.selections.newest_anchor().head();
16164 self.selections.change_with(cx, |s| {
16165 s.select_anchors(selections);
16166 if let Some(pending_selection) = pending_selection {
16167 s.set_pending(pending_selection, SelectMode::Character);
16168 } else {
16169 s.clear_pending();
16170 }
16171 });
16172 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16173 }
16174
16175 pub fn transact(
16176 &mut self,
16177 window: &mut Window,
16178 cx: &mut Context<Self>,
16179 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16180 ) -> Option<TransactionId> {
16181 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16182 this.start_transaction_at(Instant::now(), window, cx);
16183 update(this, window, cx);
16184 this.end_transaction_at(Instant::now(), cx)
16185 })
16186 }
16187
16188 pub fn start_transaction_at(
16189 &mut self,
16190 now: Instant,
16191 window: &mut Window,
16192 cx: &mut Context<Self>,
16193 ) {
16194 self.end_selection(window, cx);
16195 if let Some(tx_id) = self
16196 .buffer
16197 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16198 {
16199 self.selection_history
16200 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16201 cx.emit(EditorEvent::TransactionBegun {
16202 transaction_id: tx_id,
16203 })
16204 }
16205 }
16206
16207 pub fn end_transaction_at(
16208 &mut self,
16209 now: Instant,
16210 cx: &mut Context<Self>,
16211 ) -> Option<TransactionId> {
16212 if let Some(transaction_id) = self
16213 .buffer
16214 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16215 {
16216 if let Some((_, end_selections)) =
16217 self.selection_history.transaction_mut(transaction_id)
16218 {
16219 *end_selections = Some(self.selections.disjoint_anchors());
16220 } else {
16221 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16222 }
16223
16224 cx.emit(EditorEvent::Edited { transaction_id });
16225 Some(transaction_id)
16226 } else {
16227 None
16228 }
16229 }
16230
16231 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16232 if self.selection_mark_mode {
16233 self.change_selections(None, window, cx, |s| {
16234 s.move_with(|_, sel| {
16235 sel.collapse_to(sel.head(), SelectionGoal::None);
16236 });
16237 })
16238 }
16239 self.selection_mark_mode = true;
16240 cx.notify();
16241 }
16242
16243 pub fn swap_selection_ends(
16244 &mut self,
16245 _: &actions::SwapSelectionEnds,
16246 window: &mut Window,
16247 cx: &mut Context<Self>,
16248 ) {
16249 self.change_selections(None, window, cx, |s| {
16250 s.move_with(|_, sel| {
16251 if sel.start != sel.end {
16252 sel.reversed = !sel.reversed
16253 }
16254 });
16255 });
16256 self.request_autoscroll(Autoscroll::newest(), cx);
16257 cx.notify();
16258 }
16259
16260 pub fn toggle_fold(
16261 &mut self,
16262 _: &actions::ToggleFold,
16263 window: &mut Window,
16264 cx: &mut Context<Self>,
16265 ) {
16266 if self.is_singleton(cx) {
16267 let selection = self.selections.newest::<Point>(cx);
16268
16269 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16270 let range = if selection.is_empty() {
16271 let point = selection.head().to_display_point(&display_map);
16272 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16273 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16274 .to_point(&display_map);
16275 start..end
16276 } else {
16277 selection.range()
16278 };
16279 if display_map.folds_in_range(range).next().is_some() {
16280 self.unfold_lines(&Default::default(), window, cx)
16281 } else {
16282 self.fold(&Default::default(), window, cx)
16283 }
16284 } else {
16285 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16286 let buffer_ids: HashSet<_> = self
16287 .selections
16288 .disjoint_anchor_ranges()
16289 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16290 .collect();
16291
16292 let should_unfold = buffer_ids
16293 .iter()
16294 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16295
16296 for buffer_id in buffer_ids {
16297 if should_unfold {
16298 self.unfold_buffer(buffer_id, cx);
16299 } else {
16300 self.fold_buffer(buffer_id, cx);
16301 }
16302 }
16303 }
16304 }
16305
16306 pub fn toggle_fold_recursive(
16307 &mut self,
16308 _: &actions::ToggleFoldRecursive,
16309 window: &mut Window,
16310 cx: &mut Context<Self>,
16311 ) {
16312 let selection = self.selections.newest::<Point>(cx);
16313
16314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16315 let range = if selection.is_empty() {
16316 let point = selection.head().to_display_point(&display_map);
16317 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16318 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16319 .to_point(&display_map);
16320 start..end
16321 } else {
16322 selection.range()
16323 };
16324 if display_map.folds_in_range(range).next().is_some() {
16325 self.unfold_recursive(&Default::default(), window, cx)
16326 } else {
16327 self.fold_recursive(&Default::default(), window, cx)
16328 }
16329 }
16330
16331 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16332 if self.is_singleton(cx) {
16333 let mut to_fold = Vec::new();
16334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16335 let selections = self.selections.all_adjusted(cx);
16336
16337 for selection in selections {
16338 let range = selection.range().sorted();
16339 let buffer_start_row = range.start.row;
16340
16341 if range.start.row != range.end.row {
16342 let mut found = false;
16343 let mut row = range.start.row;
16344 while row <= range.end.row {
16345 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16346 {
16347 found = true;
16348 row = crease.range().end.row + 1;
16349 to_fold.push(crease);
16350 } else {
16351 row += 1
16352 }
16353 }
16354 if found {
16355 continue;
16356 }
16357 }
16358
16359 for row in (0..=range.start.row).rev() {
16360 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16361 if crease.range().end.row >= buffer_start_row {
16362 to_fold.push(crease);
16363 if row <= range.start.row {
16364 break;
16365 }
16366 }
16367 }
16368 }
16369 }
16370
16371 self.fold_creases(to_fold, true, window, cx);
16372 } else {
16373 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16374 let buffer_ids = self
16375 .selections
16376 .disjoint_anchor_ranges()
16377 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16378 .collect::<HashSet<_>>();
16379 for buffer_id in buffer_ids {
16380 self.fold_buffer(buffer_id, cx);
16381 }
16382 }
16383 }
16384
16385 fn fold_at_level(
16386 &mut self,
16387 fold_at: &FoldAtLevel,
16388 window: &mut Window,
16389 cx: &mut Context<Self>,
16390 ) {
16391 if !self.buffer.read(cx).is_singleton() {
16392 return;
16393 }
16394
16395 let fold_at_level = fold_at.0;
16396 let snapshot = self.buffer.read(cx).snapshot(cx);
16397 let mut to_fold = Vec::new();
16398 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16399
16400 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16401 while start_row < end_row {
16402 match self
16403 .snapshot(window, cx)
16404 .crease_for_buffer_row(MultiBufferRow(start_row))
16405 {
16406 Some(crease) => {
16407 let nested_start_row = crease.range().start.row + 1;
16408 let nested_end_row = crease.range().end.row;
16409
16410 if current_level < fold_at_level {
16411 stack.push((nested_start_row, nested_end_row, current_level + 1));
16412 } else if current_level == fold_at_level {
16413 to_fold.push(crease);
16414 }
16415
16416 start_row = nested_end_row + 1;
16417 }
16418 None => start_row += 1,
16419 }
16420 }
16421 }
16422
16423 self.fold_creases(to_fold, true, window, cx);
16424 }
16425
16426 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16427 if self.buffer.read(cx).is_singleton() {
16428 let mut fold_ranges = Vec::new();
16429 let snapshot = self.buffer.read(cx).snapshot(cx);
16430
16431 for row in 0..snapshot.max_row().0 {
16432 if let Some(foldable_range) = self
16433 .snapshot(window, cx)
16434 .crease_for_buffer_row(MultiBufferRow(row))
16435 {
16436 fold_ranges.push(foldable_range);
16437 }
16438 }
16439
16440 self.fold_creases(fold_ranges, true, window, cx);
16441 } else {
16442 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16443 editor
16444 .update_in(cx, |editor, _, cx| {
16445 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16446 editor.fold_buffer(buffer_id, cx);
16447 }
16448 })
16449 .ok();
16450 });
16451 }
16452 }
16453
16454 pub fn fold_function_bodies(
16455 &mut self,
16456 _: &actions::FoldFunctionBodies,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) {
16460 let snapshot = self.buffer.read(cx).snapshot(cx);
16461
16462 let ranges = snapshot
16463 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16464 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16465 .collect::<Vec<_>>();
16466
16467 let creases = ranges
16468 .into_iter()
16469 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16470 .collect();
16471
16472 self.fold_creases(creases, true, window, cx);
16473 }
16474
16475 pub fn fold_recursive(
16476 &mut self,
16477 _: &actions::FoldRecursive,
16478 window: &mut Window,
16479 cx: &mut Context<Self>,
16480 ) {
16481 let mut to_fold = Vec::new();
16482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16483 let selections = self.selections.all_adjusted(cx);
16484
16485 for selection in selections {
16486 let range = selection.range().sorted();
16487 let buffer_start_row = range.start.row;
16488
16489 if range.start.row != range.end.row {
16490 let mut found = false;
16491 for row in range.start.row..=range.end.row {
16492 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16493 found = true;
16494 to_fold.push(crease);
16495 }
16496 }
16497 if found {
16498 continue;
16499 }
16500 }
16501
16502 for row in (0..=range.start.row).rev() {
16503 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16504 if crease.range().end.row >= buffer_start_row {
16505 to_fold.push(crease);
16506 } else {
16507 break;
16508 }
16509 }
16510 }
16511 }
16512
16513 self.fold_creases(to_fold, true, window, cx);
16514 }
16515
16516 pub fn fold_at(
16517 &mut self,
16518 buffer_row: MultiBufferRow,
16519 window: &mut Window,
16520 cx: &mut Context<Self>,
16521 ) {
16522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16523
16524 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16525 let autoscroll = self
16526 .selections
16527 .all::<Point>(cx)
16528 .iter()
16529 .any(|selection| crease.range().overlaps(&selection.range()));
16530
16531 self.fold_creases(vec![crease], autoscroll, window, cx);
16532 }
16533 }
16534
16535 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16536 if self.is_singleton(cx) {
16537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16538 let buffer = &display_map.buffer_snapshot;
16539 let selections = self.selections.all::<Point>(cx);
16540 let ranges = selections
16541 .iter()
16542 .map(|s| {
16543 let range = s.display_range(&display_map).sorted();
16544 let mut start = range.start.to_point(&display_map);
16545 let mut end = range.end.to_point(&display_map);
16546 start.column = 0;
16547 end.column = buffer.line_len(MultiBufferRow(end.row));
16548 start..end
16549 })
16550 .collect::<Vec<_>>();
16551
16552 self.unfold_ranges(&ranges, true, true, cx);
16553 } else {
16554 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16555 let buffer_ids = self
16556 .selections
16557 .disjoint_anchor_ranges()
16558 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16559 .collect::<HashSet<_>>();
16560 for buffer_id in buffer_ids {
16561 self.unfold_buffer(buffer_id, cx);
16562 }
16563 }
16564 }
16565
16566 pub fn unfold_recursive(
16567 &mut self,
16568 _: &UnfoldRecursive,
16569 _window: &mut Window,
16570 cx: &mut Context<Self>,
16571 ) {
16572 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16573 let selections = self.selections.all::<Point>(cx);
16574 let ranges = selections
16575 .iter()
16576 .map(|s| {
16577 let mut range = s.display_range(&display_map).sorted();
16578 *range.start.column_mut() = 0;
16579 *range.end.column_mut() = display_map.line_len(range.end.row());
16580 let start = range.start.to_point(&display_map);
16581 let end = range.end.to_point(&display_map);
16582 start..end
16583 })
16584 .collect::<Vec<_>>();
16585
16586 self.unfold_ranges(&ranges, true, true, cx);
16587 }
16588
16589 pub fn unfold_at(
16590 &mut self,
16591 buffer_row: MultiBufferRow,
16592 _window: &mut Window,
16593 cx: &mut Context<Self>,
16594 ) {
16595 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16596
16597 let intersection_range = Point::new(buffer_row.0, 0)
16598 ..Point::new(
16599 buffer_row.0,
16600 display_map.buffer_snapshot.line_len(buffer_row),
16601 );
16602
16603 let autoscroll = self
16604 .selections
16605 .all::<Point>(cx)
16606 .iter()
16607 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16608
16609 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16610 }
16611
16612 pub fn unfold_all(
16613 &mut self,
16614 _: &actions::UnfoldAll,
16615 _window: &mut Window,
16616 cx: &mut Context<Self>,
16617 ) {
16618 if self.buffer.read(cx).is_singleton() {
16619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16620 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16621 } else {
16622 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16623 editor
16624 .update(cx, |editor, cx| {
16625 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16626 editor.unfold_buffer(buffer_id, cx);
16627 }
16628 })
16629 .ok();
16630 });
16631 }
16632 }
16633
16634 pub fn fold_selected_ranges(
16635 &mut self,
16636 _: &FoldSelectedRanges,
16637 window: &mut Window,
16638 cx: &mut Context<Self>,
16639 ) {
16640 let selections = self.selections.all_adjusted(cx);
16641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16642 let ranges = selections
16643 .into_iter()
16644 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16645 .collect::<Vec<_>>();
16646 self.fold_creases(ranges, true, window, cx);
16647 }
16648
16649 pub fn fold_ranges<T: ToOffset + Clone>(
16650 &mut self,
16651 ranges: Vec<Range<T>>,
16652 auto_scroll: bool,
16653 window: &mut Window,
16654 cx: &mut Context<Self>,
16655 ) {
16656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16657 let ranges = ranges
16658 .into_iter()
16659 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16660 .collect::<Vec<_>>();
16661 self.fold_creases(ranges, auto_scroll, window, cx);
16662 }
16663
16664 pub fn fold_creases<T: ToOffset + Clone>(
16665 &mut self,
16666 creases: Vec<Crease<T>>,
16667 auto_scroll: bool,
16668 _window: &mut Window,
16669 cx: &mut Context<Self>,
16670 ) {
16671 if creases.is_empty() {
16672 return;
16673 }
16674
16675 let mut buffers_affected = HashSet::default();
16676 let multi_buffer = self.buffer().read(cx);
16677 for crease in &creases {
16678 if let Some((_, buffer, _)) =
16679 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16680 {
16681 buffers_affected.insert(buffer.read(cx).remote_id());
16682 };
16683 }
16684
16685 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16686
16687 if auto_scroll {
16688 self.request_autoscroll(Autoscroll::fit(), cx);
16689 }
16690
16691 cx.notify();
16692
16693 self.scrollbar_marker_state.dirty = true;
16694 self.folds_did_change(cx);
16695 }
16696
16697 /// Removes any folds whose ranges intersect any of the given ranges.
16698 pub fn unfold_ranges<T: ToOffset + Clone>(
16699 &mut self,
16700 ranges: &[Range<T>],
16701 inclusive: bool,
16702 auto_scroll: bool,
16703 cx: &mut Context<Self>,
16704 ) {
16705 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16706 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16707 });
16708 self.folds_did_change(cx);
16709 }
16710
16711 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16712 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16713 return;
16714 }
16715 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16716 self.display_map.update(cx, |display_map, cx| {
16717 display_map.fold_buffers([buffer_id], cx)
16718 });
16719 cx.emit(EditorEvent::BufferFoldToggled {
16720 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16721 folded: true,
16722 });
16723 cx.notify();
16724 }
16725
16726 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16727 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16728 return;
16729 }
16730 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16731 self.display_map.update(cx, |display_map, cx| {
16732 display_map.unfold_buffers([buffer_id], cx);
16733 });
16734 cx.emit(EditorEvent::BufferFoldToggled {
16735 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16736 folded: false,
16737 });
16738 cx.notify();
16739 }
16740
16741 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16742 self.display_map.read(cx).is_buffer_folded(buffer)
16743 }
16744
16745 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16746 self.display_map.read(cx).folded_buffers()
16747 }
16748
16749 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16750 self.display_map.update(cx, |display_map, cx| {
16751 display_map.disable_header_for_buffer(buffer_id, cx);
16752 });
16753 cx.notify();
16754 }
16755
16756 /// Removes any folds with the given ranges.
16757 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16758 &mut self,
16759 ranges: &[Range<T>],
16760 type_id: TypeId,
16761 auto_scroll: bool,
16762 cx: &mut Context<Self>,
16763 ) {
16764 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16765 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16766 });
16767 self.folds_did_change(cx);
16768 }
16769
16770 fn remove_folds_with<T: ToOffset + Clone>(
16771 &mut self,
16772 ranges: &[Range<T>],
16773 auto_scroll: bool,
16774 cx: &mut Context<Self>,
16775 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16776 ) {
16777 if ranges.is_empty() {
16778 return;
16779 }
16780
16781 let mut buffers_affected = HashSet::default();
16782 let multi_buffer = self.buffer().read(cx);
16783 for range in ranges {
16784 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16785 buffers_affected.insert(buffer.read(cx).remote_id());
16786 };
16787 }
16788
16789 self.display_map.update(cx, update);
16790
16791 if auto_scroll {
16792 self.request_autoscroll(Autoscroll::fit(), cx);
16793 }
16794
16795 cx.notify();
16796 self.scrollbar_marker_state.dirty = true;
16797 self.active_indent_guides_state.dirty = true;
16798 }
16799
16800 pub fn update_fold_widths(
16801 &mut self,
16802 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16803 cx: &mut Context<Self>,
16804 ) -> bool {
16805 self.display_map
16806 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16807 }
16808
16809 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16810 self.display_map.read(cx).fold_placeholder.clone()
16811 }
16812
16813 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16814 self.buffer.update(cx, |buffer, cx| {
16815 buffer.set_all_diff_hunks_expanded(cx);
16816 });
16817 }
16818
16819 pub fn expand_all_diff_hunks(
16820 &mut self,
16821 _: &ExpandAllDiffHunks,
16822 _window: &mut Window,
16823 cx: &mut Context<Self>,
16824 ) {
16825 self.buffer.update(cx, |buffer, cx| {
16826 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16827 });
16828 }
16829
16830 pub fn toggle_selected_diff_hunks(
16831 &mut self,
16832 _: &ToggleSelectedDiffHunks,
16833 _window: &mut Window,
16834 cx: &mut Context<Self>,
16835 ) {
16836 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16837 self.toggle_diff_hunks_in_ranges(ranges, cx);
16838 }
16839
16840 pub fn diff_hunks_in_ranges<'a>(
16841 &'a self,
16842 ranges: &'a [Range<Anchor>],
16843 buffer: &'a MultiBufferSnapshot,
16844 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16845 ranges.iter().flat_map(move |range| {
16846 let end_excerpt_id = range.end.excerpt_id;
16847 let range = range.to_point(buffer);
16848 let mut peek_end = range.end;
16849 if range.end.row < buffer.max_row().0 {
16850 peek_end = Point::new(range.end.row + 1, 0);
16851 }
16852 buffer
16853 .diff_hunks_in_range(range.start..peek_end)
16854 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16855 })
16856 }
16857
16858 pub fn has_stageable_diff_hunks_in_ranges(
16859 &self,
16860 ranges: &[Range<Anchor>],
16861 snapshot: &MultiBufferSnapshot,
16862 ) -> bool {
16863 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16864 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16865 }
16866
16867 pub fn toggle_staged_selected_diff_hunks(
16868 &mut self,
16869 _: &::git::ToggleStaged,
16870 _: &mut Window,
16871 cx: &mut Context<Self>,
16872 ) {
16873 let snapshot = self.buffer.read(cx).snapshot(cx);
16874 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16875 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16876 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16877 }
16878
16879 pub fn set_render_diff_hunk_controls(
16880 &mut self,
16881 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16882 cx: &mut Context<Self>,
16883 ) {
16884 self.render_diff_hunk_controls = render_diff_hunk_controls;
16885 cx.notify();
16886 }
16887
16888 pub fn stage_and_next(
16889 &mut self,
16890 _: &::git::StageAndNext,
16891 window: &mut Window,
16892 cx: &mut Context<Self>,
16893 ) {
16894 self.do_stage_or_unstage_and_next(true, window, cx);
16895 }
16896
16897 pub fn unstage_and_next(
16898 &mut self,
16899 _: &::git::UnstageAndNext,
16900 window: &mut Window,
16901 cx: &mut Context<Self>,
16902 ) {
16903 self.do_stage_or_unstage_and_next(false, window, cx);
16904 }
16905
16906 pub fn stage_or_unstage_diff_hunks(
16907 &mut self,
16908 stage: bool,
16909 ranges: Vec<Range<Anchor>>,
16910 cx: &mut Context<Self>,
16911 ) {
16912 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16913 cx.spawn(async move |this, cx| {
16914 task.await?;
16915 this.update(cx, |this, cx| {
16916 let snapshot = this.buffer.read(cx).snapshot(cx);
16917 let chunk_by = this
16918 .diff_hunks_in_ranges(&ranges, &snapshot)
16919 .chunk_by(|hunk| hunk.buffer_id);
16920 for (buffer_id, hunks) in &chunk_by {
16921 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16922 }
16923 })
16924 })
16925 .detach_and_log_err(cx);
16926 }
16927
16928 fn save_buffers_for_ranges_if_needed(
16929 &mut self,
16930 ranges: &[Range<Anchor>],
16931 cx: &mut Context<Editor>,
16932 ) -> Task<Result<()>> {
16933 let multibuffer = self.buffer.read(cx);
16934 let snapshot = multibuffer.read(cx);
16935 let buffer_ids: HashSet<_> = ranges
16936 .iter()
16937 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16938 .collect();
16939 drop(snapshot);
16940
16941 let mut buffers = HashSet::default();
16942 for buffer_id in buffer_ids {
16943 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16944 let buffer = buffer_entity.read(cx);
16945 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16946 {
16947 buffers.insert(buffer_entity);
16948 }
16949 }
16950 }
16951
16952 if let Some(project) = &self.project {
16953 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16954 } else {
16955 Task::ready(Ok(()))
16956 }
16957 }
16958
16959 fn do_stage_or_unstage_and_next(
16960 &mut self,
16961 stage: bool,
16962 window: &mut Window,
16963 cx: &mut Context<Self>,
16964 ) {
16965 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16966
16967 if ranges.iter().any(|range| range.start != range.end) {
16968 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16969 return;
16970 }
16971
16972 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16973 let snapshot = self.snapshot(window, cx);
16974 let position = self.selections.newest::<Point>(cx).head();
16975 let mut row = snapshot
16976 .buffer_snapshot
16977 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16978 .find(|hunk| hunk.row_range.start.0 > position.row)
16979 .map(|hunk| hunk.row_range.start);
16980
16981 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16982 // Outside of the project diff editor, wrap around to the beginning.
16983 if !all_diff_hunks_expanded {
16984 row = row.or_else(|| {
16985 snapshot
16986 .buffer_snapshot
16987 .diff_hunks_in_range(Point::zero()..position)
16988 .find(|hunk| hunk.row_range.end.0 < position.row)
16989 .map(|hunk| hunk.row_range.start)
16990 });
16991 }
16992
16993 if let Some(row) = row {
16994 let destination = Point::new(row.0, 0);
16995 let autoscroll = Autoscroll::center();
16996
16997 self.unfold_ranges(&[destination..destination], false, false, cx);
16998 self.change_selections(Some(autoscroll), window, cx, |s| {
16999 s.select_ranges([destination..destination]);
17000 });
17001 }
17002 }
17003
17004 fn do_stage_or_unstage(
17005 &self,
17006 stage: bool,
17007 buffer_id: BufferId,
17008 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17009 cx: &mut App,
17010 ) -> Option<()> {
17011 let project = self.project.as_ref()?;
17012 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17013 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17014 let buffer_snapshot = buffer.read(cx).snapshot();
17015 let file_exists = buffer_snapshot
17016 .file()
17017 .is_some_and(|file| file.disk_state().exists());
17018 diff.update(cx, |diff, cx| {
17019 diff.stage_or_unstage_hunks(
17020 stage,
17021 &hunks
17022 .map(|hunk| buffer_diff::DiffHunk {
17023 buffer_range: hunk.buffer_range,
17024 diff_base_byte_range: hunk.diff_base_byte_range,
17025 secondary_status: hunk.secondary_status,
17026 range: Point::zero()..Point::zero(), // unused
17027 })
17028 .collect::<Vec<_>>(),
17029 &buffer_snapshot,
17030 file_exists,
17031 cx,
17032 )
17033 });
17034 None
17035 }
17036
17037 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17038 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17039 self.buffer
17040 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17041 }
17042
17043 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17044 self.buffer.update(cx, |buffer, cx| {
17045 let ranges = vec![Anchor::min()..Anchor::max()];
17046 if !buffer.all_diff_hunks_expanded()
17047 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17048 {
17049 buffer.collapse_diff_hunks(ranges, cx);
17050 true
17051 } else {
17052 false
17053 }
17054 })
17055 }
17056
17057 fn toggle_diff_hunks_in_ranges(
17058 &mut self,
17059 ranges: Vec<Range<Anchor>>,
17060 cx: &mut Context<Editor>,
17061 ) {
17062 self.buffer.update(cx, |buffer, cx| {
17063 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17064 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17065 })
17066 }
17067
17068 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17069 self.buffer.update(cx, |buffer, cx| {
17070 let snapshot = buffer.snapshot(cx);
17071 let excerpt_id = range.end.excerpt_id;
17072 let point_range = range.to_point(&snapshot);
17073 let expand = !buffer.single_hunk_is_expanded(range, cx);
17074 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17075 })
17076 }
17077
17078 pub(crate) fn apply_all_diff_hunks(
17079 &mut self,
17080 _: &ApplyAllDiffHunks,
17081 window: &mut Window,
17082 cx: &mut Context<Self>,
17083 ) {
17084 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17085
17086 let buffers = self.buffer.read(cx).all_buffers();
17087 for branch_buffer in buffers {
17088 branch_buffer.update(cx, |branch_buffer, cx| {
17089 branch_buffer.merge_into_base(Vec::new(), cx);
17090 });
17091 }
17092
17093 if let Some(project) = self.project.clone() {
17094 self.save(
17095 SaveOptions {
17096 format: true,
17097 autosave: false,
17098 },
17099 project,
17100 window,
17101 cx,
17102 )
17103 .detach_and_log_err(cx);
17104 }
17105 }
17106
17107 pub(crate) fn apply_selected_diff_hunks(
17108 &mut self,
17109 _: &ApplyDiffHunk,
17110 window: &mut Window,
17111 cx: &mut Context<Self>,
17112 ) {
17113 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17114 let snapshot = self.snapshot(window, cx);
17115 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17116 let mut ranges_by_buffer = HashMap::default();
17117 self.transact(window, cx, |editor, _window, cx| {
17118 for hunk in hunks {
17119 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17120 ranges_by_buffer
17121 .entry(buffer.clone())
17122 .or_insert_with(Vec::new)
17123 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17124 }
17125 }
17126
17127 for (buffer, ranges) in ranges_by_buffer {
17128 buffer.update(cx, |buffer, cx| {
17129 buffer.merge_into_base(ranges, cx);
17130 });
17131 }
17132 });
17133
17134 if let Some(project) = self.project.clone() {
17135 self.save(
17136 SaveOptions {
17137 format: true,
17138 autosave: false,
17139 },
17140 project,
17141 window,
17142 cx,
17143 )
17144 .detach_and_log_err(cx);
17145 }
17146 }
17147
17148 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17149 if hovered != self.gutter_hovered {
17150 self.gutter_hovered = hovered;
17151 cx.notify();
17152 }
17153 }
17154
17155 pub fn insert_blocks(
17156 &mut self,
17157 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17158 autoscroll: Option<Autoscroll>,
17159 cx: &mut Context<Self>,
17160 ) -> Vec<CustomBlockId> {
17161 let blocks = self
17162 .display_map
17163 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17164 if let Some(autoscroll) = autoscroll {
17165 self.request_autoscroll(autoscroll, cx);
17166 }
17167 cx.notify();
17168 blocks
17169 }
17170
17171 pub fn resize_blocks(
17172 &mut self,
17173 heights: HashMap<CustomBlockId, u32>,
17174 autoscroll: Option<Autoscroll>,
17175 cx: &mut Context<Self>,
17176 ) {
17177 self.display_map
17178 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17179 if let Some(autoscroll) = autoscroll {
17180 self.request_autoscroll(autoscroll, cx);
17181 }
17182 cx.notify();
17183 }
17184
17185 pub fn replace_blocks(
17186 &mut self,
17187 renderers: HashMap<CustomBlockId, RenderBlock>,
17188 autoscroll: Option<Autoscroll>,
17189 cx: &mut Context<Self>,
17190 ) {
17191 self.display_map
17192 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17193 if let Some(autoscroll) = autoscroll {
17194 self.request_autoscroll(autoscroll, cx);
17195 }
17196 cx.notify();
17197 }
17198
17199 pub fn remove_blocks(
17200 &mut self,
17201 block_ids: HashSet<CustomBlockId>,
17202 autoscroll: Option<Autoscroll>,
17203 cx: &mut Context<Self>,
17204 ) {
17205 self.display_map.update(cx, |display_map, cx| {
17206 display_map.remove_blocks(block_ids, cx)
17207 });
17208 if let Some(autoscroll) = autoscroll {
17209 self.request_autoscroll(autoscroll, cx);
17210 }
17211 cx.notify();
17212 }
17213
17214 pub fn row_for_block(
17215 &self,
17216 block_id: CustomBlockId,
17217 cx: &mut Context<Self>,
17218 ) -> Option<DisplayRow> {
17219 self.display_map
17220 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17221 }
17222
17223 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17224 self.focused_block = Some(focused_block);
17225 }
17226
17227 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17228 self.focused_block.take()
17229 }
17230
17231 pub fn insert_creases(
17232 &mut self,
17233 creases: impl IntoIterator<Item = Crease<Anchor>>,
17234 cx: &mut Context<Self>,
17235 ) -> Vec<CreaseId> {
17236 self.display_map
17237 .update(cx, |map, cx| map.insert_creases(creases, cx))
17238 }
17239
17240 pub fn remove_creases(
17241 &mut self,
17242 ids: impl IntoIterator<Item = CreaseId>,
17243 cx: &mut Context<Self>,
17244 ) -> Vec<(CreaseId, Range<Anchor>)> {
17245 self.display_map
17246 .update(cx, |map, cx| map.remove_creases(ids, cx))
17247 }
17248
17249 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17250 self.display_map
17251 .update(cx, |map, cx| map.snapshot(cx))
17252 .longest_row()
17253 }
17254
17255 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17256 self.display_map
17257 .update(cx, |map, cx| map.snapshot(cx))
17258 .max_point()
17259 }
17260
17261 pub fn text(&self, cx: &App) -> String {
17262 self.buffer.read(cx).read(cx).text()
17263 }
17264
17265 pub fn is_empty(&self, cx: &App) -> bool {
17266 self.buffer.read(cx).read(cx).is_empty()
17267 }
17268
17269 pub fn text_option(&self, cx: &App) -> Option<String> {
17270 let text = self.text(cx);
17271 let text = text.trim();
17272
17273 if text.is_empty() {
17274 return None;
17275 }
17276
17277 Some(text.to_string())
17278 }
17279
17280 pub fn set_text(
17281 &mut self,
17282 text: impl Into<Arc<str>>,
17283 window: &mut Window,
17284 cx: &mut Context<Self>,
17285 ) {
17286 self.transact(window, cx, |this, _, cx| {
17287 this.buffer
17288 .read(cx)
17289 .as_singleton()
17290 .expect("you can only call set_text on editors for singleton buffers")
17291 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17292 });
17293 }
17294
17295 pub fn display_text(&self, cx: &mut App) -> String {
17296 self.display_map
17297 .update(cx, |map, cx| map.snapshot(cx))
17298 .text()
17299 }
17300
17301 fn create_minimap(
17302 &self,
17303 minimap_settings: MinimapSettings,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) -> Option<Entity<Self>> {
17307 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17308 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17309 }
17310
17311 fn initialize_new_minimap(
17312 &self,
17313 minimap_settings: MinimapSettings,
17314 window: &mut Window,
17315 cx: &mut Context<Self>,
17316 ) -> Entity<Self> {
17317 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17318
17319 let mut minimap = Editor::new_internal(
17320 EditorMode::Minimap {
17321 parent: cx.weak_entity(),
17322 },
17323 self.buffer.clone(),
17324 self.project.clone(),
17325 Some(self.display_map.clone()),
17326 window,
17327 cx,
17328 );
17329 minimap.scroll_manager.clone_state(&self.scroll_manager);
17330 minimap.set_text_style_refinement(TextStyleRefinement {
17331 font_size: Some(MINIMAP_FONT_SIZE),
17332 font_weight: Some(MINIMAP_FONT_WEIGHT),
17333 ..Default::default()
17334 });
17335 minimap.update_minimap_configuration(minimap_settings, cx);
17336 cx.new(|_| minimap)
17337 }
17338
17339 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17340 let current_line_highlight = minimap_settings
17341 .current_line_highlight
17342 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17343 self.set_current_line_highlight(Some(current_line_highlight));
17344 }
17345
17346 pub fn minimap(&self) -> Option<&Entity<Self>> {
17347 self.minimap
17348 .as_ref()
17349 .filter(|_| self.minimap_visibility.visible())
17350 }
17351
17352 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17353 let mut wrap_guides = smallvec![];
17354
17355 if self.show_wrap_guides == Some(false) {
17356 return wrap_guides;
17357 }
17358
17359 let settings = self.buffer.read(cx).language_settings(cx);
17360 if settings.show_wrap_guides {
17361 match self.soft_wrap_mode(cx) {
17362 SoftWrap::Column(soft_wrap) => {
17363 wrap_guides.push((soft_wrap as usize, true));
17364 }
17365 SoftWrap::Bounded(soft_wrap) => {
17366 wrap_guides.push((soft_wrap as usize, true));
17367 }
17368 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17369 }
17370 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17371 }
17372
17373 wrap_guides
17374 }
17375
17376 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17377 let settings = self.buffer.read(cx).language_settings(cx);
17378 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17379 match mode {
17380 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17381 SoftWrap::None
17382 }
17383 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17384 language_settings::SoftWrap::PreferredLineLength => {
17385 SoftWrap::Column(settings.preferred_line_length)
17386 }
17387 language_settings::SoftWrap::Bounded => {
17388 SoftWrap::Bounded(settings.preferred_line_length)
17389 }
17390 }
17391 }
17392
17393 pub fn set_soft_wrap_mode(
17394 &mut self,
17395 mode: language_settings::SoftWrap,
17396
17397 cx: &mut Context<Self>,
17398 ) {
17399 self.soft_wrap_mode_override = Some(mode);
17400 cx.notify();
17401 }
17402
17403 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17404 self.hard_wrap = hard_wrap;
17405 cx.notify();
17406 }
17407
17408 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17409 self.text_style_refinement = Some(style);
17410 }
17411
17412 /// called by the Element so we know what style we were most recently rendered with.
17413 pub(crate) fn set_style(
17414 &mut self,
17415 style: EditorStyle,
17416 window: &mut Window,
17417 cx: &mut Context<Self>,
17418 ) {
17419 // We intentionally do not inform the display map about the minimap style
17420 // so that wrapping is not recalculated and stays consistent for the editor
17421 // and its linked minimap.
17422 if !self.mode.is_minimap() {
17423 let rem_size = window.rem_size();
17424 self.display_map.update(cx, |map, cx| {
17425 map.set_font(
17426 style.text.font(),
17427 style.text.font_size.to_pixels(rem_size),
17428 cx,
17429 )
17430 });
17431 }
17432 self.style = Some(style);
17433 }
17434
17435 pub fn style(&self) -> Option<&EditorStyle> {
17436 self.style.as_ref()
17437 }
17438
17439 // Called by the element. This method is not designed to be called outside of the editor
17440 // element's layout code because it does not notify when rewrapping is computed synchronously.
17441 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17442 self.display_map
17443 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17444 }
17445
17446 pub fn set_soft_wrap(&mut self) {
17447 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17448 }
17449
17450 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17451 if self.soft_wrap_mode_override.is_some() {
17452 self.soft_wrap_mode_override.take();
17453 } else {
17454 let soft_wrap = match self.soft_wrap_mode(cx) {
17455 SoftWrap::GitDiff => return,
17456 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17457 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17458 language_settings::SoftWrap::None
17459 }
17460 };
17461 self.soft_wrap_mode_override = Some(soft_wrap);
17462 }
17463 cx.notify();
17464 }
17465
17466 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17467 let Some(workspace) = self.workspace() else {
17468 return;
17469 };
17470 let fs = workspace.read(cx).app_state().fs.clone();
17471 let current_show = TabBarSettings::get_global(cx).show;
17472 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17473 setting.show = Some(!current_show);
17474 });
17475 }
17476
17477 pub fn toggle_indent_guides(
17478 &mut self,
17479 _: &ToggleIndentGuides,
17480 _: &mut Window,
17481 cx: &mut Context<Self>,
17482 ) {
17483 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17484 self.buffer
17485 .read(cx)
17486 .language_settings(cx)
17487 .indent_guides
17488 .enabled
17489 });
17490 self.show_indent_guides = Some(!currently_enabled);
17491 cx.notify();
17492 }
17493
17494 fn should_show_indent_guides(&self) -> Option<bool> {
17495 self.show_indent_guides
17496 }
17497
17498 pub fn toggle_line_numbers(
17499 &mut self,
17500 _: &ToggleLineNumbers,
17501 _: &mut Window,
17502 cx: &mut Context<Self>,
17503 ) {
17504 let mut editor_settings = EditorSettings::get_global(cx).clone();
17505 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17506 EditorSettings::override_global(editor_settings, cx);
17507 }
17508
17509 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17510 if let Some(show_line_numbers) = self.show_line_numbers {
17511 return show_line_numbers;
17512 }
17513 EditorSettings::get_global(cx).gutter.line_numbers
17514 }
17515
17516 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17517 self.use_relative_line_numbers
17518 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17519 }
17520
17521 pub fn toggle_relative_line_numbers(
17522 &mut self,
17523 _: &ToggleRelativeLineNumbers,
17524 _: &mut Window,
17525 cx: &mut Context<Self>,
17526 ) {
17527 let is_relative = self.should_use_relative_line_numbers(cx);
17528 self.set_relative_line_number(Some(!is_relative), cx)
17529 }
17530
17531 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17532 self.use_relative_line_numbers = is_relative;
17533 cx.notify();
17534 }
17535
17536 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17537 self.show_gutter = show_gutter;
17538 cx.notify();
17539 }
17540
17541 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17542 self.show_scrollbars = ScrollbarAxes {
17543 horizontal: show,
17544 vertical: show,
17545 };
17546 cx.notify();
17547 }
17548
17549 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17550 self.show_scrollbars.vertical = show;
17551 cx.notify();
17552 }
17553
17554 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17555 self.show_scrollbars.horizontal = show;
17556 cx.notify();
17557 }
17558
17559 pub fn set_minimap_visibility(
17560 &mut self,
17561 minimap_visibility: MinimapVisibility,
17562 window: &mut Window,
17563 cx: &mut Context<Self>,
17564 ) {
17565 if self.minimap_visibility != minimap_visibility {
17566 if minimap_visibility.visible() && self.minimap.is_none() {
17567 let minimap_settings = EditorSettings::get_global(cx).minimap;
17568 self.minimap =
17569 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17570 }
17571 self.minimap_visibility = minimap_visibility;
17572 cx.notify();
17573 }
17574 }
17575
17576 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17577 self.set_show_scrollbars(false, cx);
17578 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17579 }
17580
17581 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17582 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17583 }
17584
17585 /// Normally the text in full mode and auto height editors is padded on the
17586 /// left side by roughly half a character width for improved hit testing.
17587 ///
17588 /// Use this method to disable this for cases where this is not wanted (e.g.
17589 /// if you want to align the editor text with some other text above or below)
17590 /// or if you want to add this padding to single-line editors.
17591 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17592 self.offset_content = offset_content;
17593 cx.notify();
17594 }
17595
17596 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17597 self.show_line_numbers = Some(show_line_numbers);
17598 cx.notify();
17599 }
17600
17601 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17602 self.disable_expand_excerpt_buttons = true;
17603 cx.notify();
17604 }
17605
17606 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17607 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17608 cx.notify();
17609 }
17610
17611 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17612 self.show_code_actions = Some(show_code_actions);
17613 cx.notify();
17614 }
17615
17616 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17617 self.show_runnables = Some(show_runnables);
17618 cx.notify();
17619 }
17620
17621 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17622 self.show_breakpoints = Some(show_breakpoints);
17623 cx.notify();
17624 }
17625
17626 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17627 if self.display_map.read(cx).masked != masked {
17628 self.display_map.update(cx, |map, _| map.masked = masked);
17629 }
17630 cx.notify()
17631 }
17632
17633 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17634 self.show_wrap_guides = Some(show_wrap_guides);
17635 cx.notify();
17636 }
17637
17638 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17639 self.show_indent_guides = Some(show_indent_guides);
17640 cx.notify();
17641 }
17642
17643 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17644 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17645 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17646 if let Some(dir) = file.abs_path(cx).parent() {
17647 return Some(dir.to_owned());
17648 }
17649 }
17650
17651 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17652 return Some(project_path.path.to_path_buf());
17653 }
17654 }
17655
17656 None
17657 }
17658
17659 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17660 self.active_excerpt(cx)?
17661 .1
17662 .read(cx)
17663 .file()
17664 .and_then(|f| f.as_local())
17665 }
17666
17667 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17668 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17669 let buffer = buffer.read(cx);
17670 if let Some(project_path) = buffer.project_path(cx) {
17671 let project = self.project.as_ref()?.read(cx);
17672 project.absolute_path(&project_path, cx)
17673 } else {
17674 buffer
17675 .file()
17676 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17677 }
17678 })
17679 }
17680
17681 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17682 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17683 let project_path = buffer.read(cx).project_path(cx)?;
17684 let project = self.project.as_ref()?.read(cx);
17685 let entry = project.entry_for_path(&project_path, cx)?;
17686 let path = entry.path.to_path_buf();
17687 Some(path)
17688 })
17689 }
17690
17691 pub fn reveal_in_finder(
17692 &mut self,
17693 _: &RevealInFileManager,
17694 _window: &mut Window,
17695 cx: &mut Context<Self>,
17696 ) {
17697 if let Some(target) = self.target_file(cx) {
17698 cx.reveal_path(&target.abs_path(cx));
17699 }
17700 }
17701
17702 pub fn copy_path(
17703 &mut self,
17704 _: &zed_actions::workspace::CopyPath,
17705 _window: &mut Window,
17706 cx: &mut Context<Self>,
17707 ) {
17708 if let Some(path) = self.target_file_abs_path(cx) {
17709 if let Some(path) = path.to_str() {
17710 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17711 }
17712 }
17713 }
17714
17715 pub fn copy_relative_path(
17716 &mut self,
17717 _: &zed_actions::workspace::CopyRelativePath,
17718 _window: &mut Window,
17719 cx: &mut Context<Self>,
17720 ) {
17721 if let Some(path) = self.target_file_path(cx) {
17722 if let Some(path) = path.to_str() {
17723 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17724 }
17725 }
17726 }
17727
17728 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17729 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17730 buffer.read(cx).project_path(cx)
17731 } else {
17732 None
17733 }
17734 }
17735
17736 // Returns true if the editor handled a go-to-line request
17737 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17738 maybe!({
17739 let breakpoint_store = self.breakpoint_store.as_ref()?;
17740
17741 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17742 else {
17743 self.clear_row_highlights::<ActiveDebugLine>();
17744 return None;
17745 };
17746
17747 let position = active_stack_frame.position;
17748 let buffer_id = position.buffer_id?;
17749 let snapshot = self
17750 .project
17751 .as_ref()?
17752 .read(cx)
17753 .buffer_for_id(buffer_id, cx)?
17754 .read(cx)
17755 .snapshot();
17756
17757 let mut handled = false;
17758 for (id, ExcerptRange { context, .. }) in
17759 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17760 {
17761 if context.start.cmp(&position, &snapshot).is_ge()
17762 || context.end.cmp(&position, &snapshot).is_lt()
17763 {
17764 continue;
17765 }
17766 let snapshot = self.buffer.read(cx).snapshot(cx);
17767 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17768
17769 handled = true;
17770 self.clear_row_highlights::<ActiveDebugLine>();
17771
17772 self.go_to_line::<ActiveDebugLine>(
17773 multibuffer_anchor,
17774 Some(cx.theme().colors().editor_debugger_active_line_background),
17775 window,
17776 cx,
17777 );
17778
17779 cx.notify();
17780 }
17781
17782 handled.then_some(())
17783 })
17784 .is_some()
17785 }
17786
17787 pub fn copy_file_name_without_extension(
17788 &mut self,
17789 _: &CopyFileNameWithoutExtension,
17790 _: &mut Window,
17791 cx: &mut Context<Self>,
17792 ) {
17793 if let Some(file) = self.target_file(cx) {
17794 if let Some(file_stem) = file.path().file_stem() {
17795 if let Some(name) = file_stem.to_str() {
17796 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17797 }
17798 }
17799 }
17800 }
17801
17802 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17803 if let Some(file) = self.target_file(cx) {
17804 if let Some(file_name) = file.path().file_name() {
17805 if let Some(name) = file_name.to_str() {
17806 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17807 }
17808 }
17809 }
17810 }
17811
17812 pub fn toggle_git_blame(
17813 &mut self,
17814 _: &::git::Blame,
17815 window: &mut Window,
17816 cx: &mut Context<Self>,
17817 ) {
17818 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17819
17820 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17821 self.start_git_blame(true, window, cx);
17822 }
17823
17824 cx.notify();
17825 }
17826
17827 pub fn toggle_git_blame_inline(
17828 &mut self,
17829 _: &ToggleGitBlameInline,
17830 window: &mut Window,
17831 cx: &mut Context<Self>,
17832 ) {
17833 self.toggle_git_blame_inline_internal(true, window, cx);
17834 cx.notify();
17835 }
17836
17837 pub fn open_git_blame_commit(
17838 &mut self,
17839 _: &OpenGitBlameCommit,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 self.open_git_blame_commit_internal(window, cx);
17844 }
17845
17846 fn open_git_blame_commit_internal(
17847 &mut self,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) -> Option<()> {
17851 let blame = self.blame.as_ref()?;
17852 let snapshot = self.snapshot(window, cx);
17853 let cursor = self.selections.newest::<Point>(cx).head();
17854 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17855 let blame_entry = blame
17856 .update(cx, |blame, cx| {
17857 blame
17858 .blame_for_rows(
17859 &[RowInfo {
17860 buffer_id: Some(buffer.remote_id()),
17861 buffer_row: Some(point.row),
17862 ..Default::default()
17863 }],
17864 cx,
17865 )
17866 .next()
17867 })
17868 .flatten()?;
17869 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17870 let repo = blame.read(cx).repository(cx)?;
17871 let workspace = self.workspace()?.downgrade();
17872 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17873 None
17874 }
17875
17876 pub fn git_blame_inline_enabled(&self) -> bool {
17877 self.git_blame_inline_enabled
17878 }
17879
17880 pub fn toggle_selection_menu(
17881 &mut self,
17882 _: &ToggleSelectionMenu,
17883 _: &mut Window,
17884 cx: &mut Context<Self>,
17885 ) {
17886 self.show_selection_menu = self
17887 .show_selection_menu
17888 .map(|show_selections_menu| !show_selections_menu)
17889 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17890
17891 cx.notify();
17892 }
17893
17894 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17895 self.show_selection_menu
17896 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17897 }
17898
17899 fn start_git_blame(
17900 &mut self,
17901 user_triggered: bool,
17902 window: &mut Window,
17903 cx: &mut Context<Self>,
17904 ) {
17905 if let Some(project) = self.project.as_ref() {
17906 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17907 return;
17908 };
17909
17910 if buffer.read(cx).file().is_none() {
17911 return;
17912 }
17913
17914 let focused = self.focus_handle(cx).contains_focused(window, cx);
17915
17916 let project = project.clone();
17917 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17918 self.blame_subscription =
17919 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17920 self.blame = Some(blame);
17921 }
17922 }
17923
17924 fn toggle_git_blame_inline_internal(
17925 &mut self,
17926 user_triggered: bool,
17927 window: &mut Window,
17928 cx: &mut Context<Self>,
17929 ) {
17930 if self.git_blame_inline_enabled {
17931 self.git_blame_inline_enabled = false;
17932 self.show_git_blame_inline = false;
17933 self.show_git_blame_inline_delay_task.take();
17934 } else {
17935 self.git_blame_inline_enabled = true;
17936 self.start_git_blame_inline(user_triggered, window, cx);
17937 }
17938
17939 cx.notify();
17940 }
17941
17942 fn start_git_blame_inline(
17943 &mut self,
17944 user_triggered: bool,
17945 window: &mut Window,
17946 cx: &mut Context<Self>,
17947 ) {
17948 self.start_git_blame(user_triggered, window, cx);
17949
17950 if ProjectSettings::get_global(cx)
17951 .git
17952 .inline_blame_delay()
17953 .is_some()
17954 {
17955 self.start_inline_blame_timer(window, cx);
17956 } else {
17957 self.show_git_blame_inline = true
17958 }
17959 }
17960
17961 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17962 self.blame.as_ref()
17963 }
17964
17965 pub fn show_git_blame_gutter(&self) -> bool {
17966 self.show_git_blame_gutter
17967 }
17968
17969 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17970 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17971 }
17972
17973 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17974 self.show_git_blame_inline
17975 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17976 && !self.newest_selection_head_on_empty_line(cx)
17977 && self.has_blame_entries(cx)
17978 }
17979
17980 fn has_blame_entries(&self, cx: &App) -> bool {
17981 self.blame()
17982 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17983 }
17984
17985 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17986 let cursor_anchor = self.selections.newest_anchor().head();
17987
17988 let snapshot = self.buffer.read(cx).snapshot(cx);
17989 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17990
17991 snapshot.line_len(buffer_row) == 0
17992 }
17993
17994 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17995 let buffer_and_selection = maybe!({
17996 let selection = self.selections.newest::<Point>(cx);
17997 let selection_range = selection.range();
17998
17999 let multi_buffer = self.buffer().read(cx);
18000 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18001 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18002
18003 let (buffer, range, _) = if selection.reversed {
18004 buffer_ranges.first()
18005 } else {
18006 buffer_ranges.last()
18007 }?;
18008
18009 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18010 ..text::ToPoint::to_point(&range.end, &buffer).row;
18011 Some((
18012 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18013 selection,
18014 ))
18015 });
18016
18017 let Some((buffer, selection)) = buffer_and_selection else {
18018 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18019 };
18020
18021 let Some(project) = self.project.as_ref() else {
18022 return Task::ready(Err(anyhow!("editor does not have project")));
18023 };
18024
18025 project.update(cx, |project, cx| {
18026 project.get_permalink_to_line(&buffer, selection, cx)
18027 })
18028 }
18029
18030 pub fn copy_permalink_to_line(
18031 &mut self,
18032 _: &CopyPermalinkToLine,
18033 window: &mut Window,
18034 cx: &mut Context<Self>,
18035 ) {
18036 let permalink_task = self.get_permalink_to_line(cx);
18037 let workspace = self.workspace();
18038
18039 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18040 Ok(permalink) => {
18041 cx.update(|_, cx| {
18042 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18043 })
18044 .ok();
18045 }
18046 Err(err) => {
18047 let message = format!("Failed to copy permalink: {err}");
18048
18049 anyhow::Result::<()>::Err(err).log_err();
18050
18051 if let Some(workspace) = workspace {
18052 workspace
18053 .update_in(cx, |workspace, _, cx| {
18054 struct CopyPermalinkToLine;
18055
18056 workspace.show_toast(
18057 Toast::new(
18058 NotificationId::unique::<CopyPermalinkToLine>(),
18059 message,
18060 ),
18061 cx,
18062 )
18063 })
18064 .ok();
18065 }
18066 }
18067 })
18068 .detach();
18069 }
18070
18071 pub fn copy_file_location(
18072 &mut self,
18073 _: &CopyFileLocation,
18074 _: &mut Window,
18075 cx: &mut Context<Self>,
18076 ) {
18077 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18078 if let Some(file) = self.target_file(cx) {
18079 if let Some(path) = file.path().to_str() {
18080 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18081 }
18082 }
18083 }
18084
18085 pub fn open_permalink_to_line(
18086 &mut self,
18087 _: &OpenPermalinkToLine,
18088 window: &mut Window,
18089 cx: &mut Context<Self>,
18090 ) {
18091 let permalink_task = self.get_permalink_to_line(cx);
18092 let workspace = self.workspace();
18093
18094 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18095 Ok(permalink) => {
18096 cx.update(|_, cx| {
18097 cx.open_url(permalink.as_ref());
18098 })
18099 .ok();
18100 }
18101 Err(err) => {
18102 let message = format!("Failed to open permalink: {err}");
18103
18104 anyhow::Result::<()>::Err(err).log_err();
18105
18106 if let Some(workspace) = workspace {
18107 workspace
18108 .update(cx, |workspace, cx| {
18109 struct OpenPermalinkToLine;
18110
18111 workspace.show_toast(
18112 Toast::new(
18113 NotificationId::unique::<OpenPermalinkToLine>(),
18114 message,
18115 ),
18116 cx,
18117 )
18118 })
18119 .ok();
18120 }
18121 }
18122 })
18123 .detach();
18124 }
18125
18126 pub fn insert_uuid_v4(
18127 &mut self,
18128 _: &InsertUuidV4,
18129 window: &mut Window,
18130 cx: &mut Context<Self>,
18131 ) {
18132 self.insert_uuid(UuidVersion::V4, window, cx);
18133 }
18134
18135 pub fn insert_uuid_v7(
18136 &mut self,
18137 _: &InsertUuidV7,
18138 window: &mut Window,
18139 cx: &mut Context<Self>,
18140 ) {
18141 self.insert_uuid(UuidVersion::V7, window, cx);
18142 }
18143
18144 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18145 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18146 self.transact(window, cx, |this, window, cx| {
18147 let edits = this
18148 .selections
18149 .all::<Point>(cx)
18150 .into_iter()
18151 .map(|selection| {
18152 let uuid = match version {
18153 UuidVersion::V4 => uuid::Uuid::new_v4(),
18154 UuidVersion::V7 => uuid::Uuid::now_v7(),
18155 };
18156
18157 (selection.range(), uuid.to_string())
18158 });
18159 this.edit(edits, cx);
18160 this.refresh_inline_completion(true, false, window, cx);
18161 });
18162 }
18163
18164 pub fn open_selections_in_multibuffer(
18165 &mut self,
18166 _: &OpenSelectionsInMultibuffer,
18167 window: &mut Window,
18168 cx: &mut Context<Self>,
18169 ) {
18170 let multibuffer = self.buffer.read(cx);
18171
18172 let Some(buffer) = multibuffer.as_singleton() else {
18173 return;
18174 };
18175
18176 let Some(workspace) = self.workspace() else {
18177 return;
18178 };
18179
18180 let locations = self
18181 .selections
18182 .disjoint_anchors()
18183 .iter()
18184 .map(|range| Location {
18185 buffer: buffer.clone(),
18186 range: range.start.text_anchor..range.end.text_anchor,
18187 })
18188 .collect::<Vec<_>>();
18189
18190 let title = multibuffer.title(cx).to_string();
18191
18192 cx.spawn_in(window, async move |_, cx| {
18193 workspace.update_in(cx, |workspace, window, cx| {
18194 Self::open_locations_in_multibuffer(
18195 workspace,
18196 locations,
18197 format!("Selections for '{title}'"),
18198 false,
18199 MultibufferSelectionMode::All,
18200 window,
18201 cx,
18202 );
18203 })
18204 })
18205 .detach();
18206 }
18207
18208 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18209 /// last highlight added will be used.
18210 ///
18211 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18212 pub fn highlight_rows<T: 'static>(
18213 &mut self,
18214 range: Range<Anchor>,
18215 color: Hsla,
18216 options: RowHighlightOptions,
18217 cx: &mut Context<Self>,
18218 ) {
18219 let snapshot = self.buffer().read(cx).snapshot(cx);
18220 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18221 let ix = row_highlights.binary_search_by(|highlight| {
18222 Ordering::Equal
18223 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18224 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18225 });
18226
18227 if let Err(mut ix) = ix {
18228 let index = post_inc(&mut self.highlight_order);
18229
18230 // If this range intersects with the preceding highlight, then merge it with
18231 // the preceding highlight. Otherwise insert a new highlight.
18232 let mut merged = false;
18233 if ix > 0 {
18234 let prev_highlight = &mut row_highlights[ix - 1];
18235 if prev_highlight
18236 .range
18237 .end
18238 .cmp(&range.start, &snapshot)
18239 .is_ge()
18240 {
18241 ix -= 1;
18242 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18243 prev_highlight.range.end = range.end;
18244 }
18245 merged = true;
18246 prev_highlight.index = index;
18247 prev_highlight.color = color;
18248 prev_highlight.options = options;
18249 }
18250 }
18251
18252 if !merged {
18253 row_highlights.insert(
18254 ix,
18255 RowHighlight {
18256 range: range.clone(),
18257 index,
18258 color,
18259 options,
18260 type_id: TypeId::of::<T>(),
18261 },
18262 );
18263 }
18264
18265 // If any of the following highlights intersect with this one, merge them.
18266 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18267 let highlight = &row_highlights[ix];
18268 if next_highlight
18269 .range
18270 .start
18271 .cmp(&highlight.range.end, &snapshot)
18272 .is_le()
18273 {
18274 if next_highlight
18275 .range
18276 .end
18277 .cmp(&highlight.range.end, &snapshot)
18278 .is_gt()
18279 {
18280 row_highlights[ix].range.end = next_highlight.range.end;
18281 }
18282 row_highlights.remove(ix + 1);
18283 } else {
18284 break;
18285 }
18286 }
18287 }
18288 }
18289
18290 /// Remove any highlighted row ranges of the given type that intersect the
18291 /// given ranges.
18292 pub fn remove_highlighted_rows<T: 'static>(
18293 &mut self,
18294 ranges_to_remove: Vec<Range<Anchor>>,
18295 cx: &mut Context<Self>,
18296 ) {
18297 let snapshot = self.buffer().read(cx).snapshot(cx);
18298 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18299 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18300 row_highlights.retain(|highlight| {
18301 while let Some(range_to_remove) = ranges_to_remove.peek() {
18302 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18303 Ordering::Less | Ordering::Equal => {
18304 ranges_to_remove.next();
18305 }
18306 Ordering::Greater => {
18307 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18308 Ordering::Less | Ordering::Equal => {
18309 return false;
18310 }
18311 Ordering::Greater => break,
18312 }
18313 }
18314 }
18315 }
18316
18317 true
18318 })
18319 }
18320
18321 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18322 pub fn clear_row_highlights<T: 'static>(&mut self) {
18323 self.highlighted_rows.remove(&TypeId::of::<T>());
18324 }
18325
18326 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18327 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18328 self.highlighted_rows
18329 .get(&TypeId::of::<T>())
18330 .map_or(&[] as &[_], |vec| vec.as_slice())
18331 .iter()
18332 .map(|highlight| (highlight.range.clone(), highlight.color))
18333 }
18334
18335 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18336 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18337 /// Allows to ignore certain kinds of highlights.
18338 pub fn highlighted_display_rows(
18339 &self,
18340 window: &mut Window,
18341 cx: &mut App,
18342 ) -> BTreeMap<DisplayRow, LineHighlight> {
18343 let snapshot = self.snapshot(window, cx);
18344 let mut used_highlight_orders = HashMap::default();
18345 self.highlighted_rows
18346 .iter()
18347 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18348 .fold(
18349 BTreeMap::<DisplayRow, LineHighlight>::new(),
18350 |mut unique_rows, highlight| {
18351 let start = highlight.range.start.to_display_point(&snapshot);
18352 let end = highlight.range.end.to_display_point(&snapshot);
18353 let start_row = start.row().0;
18354 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18355 && end.column() == 0
18356 {
18357 end.row().0.saturating_sub(1)
18358 } else {
18359 end.row().0
18360 };
18361 for row in start_row..=end_row {
18362 let used_index =
18363 used_highlight_orders.entry(row).or_insert(highlight.index);
18364 if highlight.index >= *used_index {
18365 *used_index = highlight.index;
18366 unique_rows.insert(
18367 DisplayRow(row),
18368 LineHighlight {
18369 include_gutter: highlight.options.include_gutter,
18370 border: None,
18371 background: highlight.color.into(),
18372 type_id: Some(highlight.type_id),
18373 },
18374 );
18375 }
18376 }
18377 unique_rows
18378 },
18379 )
18380 }
18381
18382 pub fn highlighted_display_row_for_autoscroll(
18383 &self,
18384 snapshot: &DisplaySnapshot,
18385 ) -> Option<DisplayRow> {
18386 self.highlighted_rows
18387 .values()
18388 .flat_map(|highlighted_rows| highlighted_rows.iter())
18389 .filter_map(|highlight| {
18390 if highlight.options.autoscroll {
18391 Some(highlight.range.start.to_display_point(snapshot).row())
18392 } else {
18393 None
18394 }
18395 })
18396 .min()
18397 }
18398
18399 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18400 self.highlight_background::<SearchWithinRange>(
18401 ranges,
18402 |colors| colors.editor_document_highlight_read_background,
18403 cx,
18404 )
18405 }
18406
18407 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18408 self.breadcrumb_header = Some(new_header);
18409 }
18410
18411 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18412 self.clear_background_highlights::<SearchWithinRange>(cx);
18413 }
18414
18415 pub fn highlight_background<T: 'static>(
18416 &mut self,
18417 ranges: &[Range<Anchor>],
18418 color_fetcher: fn(&ThemeColors) -> Hsla,
18419 cx: &mut Context<Self>,
18420 ) {
18421 self.background_highlights
18422 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18423 self.scrollbar_marker_state.dirty = true;
18424 cx.notify();
18425 }
18426
18427 pub fn clear_background_highlights<T: 'static>(
18428 &mut self,
18429 cx: &mut Context<Self>,
18430 ) -> Option<BackgroundHighlight> {
18431 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18432 if !text_highlights.1.is_empty() {
18433 self.scrollbar_marker_state.dirty = true;
18434 cx.notify();
18435 }
18436 Some(text_highlights)
18437 }
18438
18439 pub fn highlight_gutter<T: 'static>(
18440 &mut self,
18441 ranges: impl Into<Vec<Range<Anchor>>>,
18442 color_fetcher: fn(&App) -> Hsla,
18443 cx: &mut Context<Self>,
18444 ) {
18445 self.gutter_highlights
18446 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18447 cx.notify();
18448 }
18449
18450 pub fn clear_gutter_highlights<T: 'static>(
18451 &mut self,
18452 cx: &mut Context<Self>,
18453 ) -> Option<GutterHighlight> {
18454 cx.notify();
18455 self.gutter_highlights.remove(&TypeId::of::<T>())
18456 }
18457
18458 pub fn insert_gutter_highlight<T: 'static>(
18459 &mut self,
18460 range: Range<Anchor>,
18461 color_fetcher: fn(&App) -> Hsla,
18462 cx: &mut Context<Self>,
18463 ) {
18464 let snapshot = self.buffer().read(cx).snapshot(cx);
18465 let mut highlights = self
18466 .gutter_highlights
18467 .remove(&TypeId::of::<T>())
18468 .map(|(_, highlights)| highlights)
18469 .unwrap_or_default();
18470 let ix = highlights.binary_search_by(|highlight| {
18471 Ordering::Equal
18472 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18473 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18474 });
18475 if let Err(ix) = ix {
18476 highlights.insert(ix, range);
18477 }
18478 self.gutter_highlights
18479 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18480 }
18481
18482 pub fn remove_gutter_highlights<T: 'static>(
18483 &mut self,
18484 ranges_to_remove: Vec<Range<Anchor>>,
18485 cx: &mut Context<Self>,
18486 ) {
18487 let snapshot = self.buffer().read(cx).snapshot(cx);
18488 let Some((color_fetcher, mut gutter_highlights)) =
18489 self.gutter_highlights.remove(&TypeId::of::<T>())
18490 else {
18491 return;
18492 };
18493 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18494 gutter_highlights.retain(|highlight| {
18495 while let Some(range_to_remove) = ranges_to_remove.peek() {
18496 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18497 Ordering::Less | Ordering::Equal => {
18498 ranges_to_remove.next();
18499 }
18500 Ordering::Greater => {
18501 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18502 Ordering::Less | Ordering::Equal => {
18503 return false;
18504 }
18505 Ordering::Greater => break,
18506 }
18507 }
18508 }
18509 }
18510
18511 true
18512 });
18513 self.gutter_highlights
18514 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18515 }
18516
18517 #[cfg(feature = "test-support")]
18518 pub fn all_text_background_highlights(
18519 &self,
18520 window: &mut Window,
18521 cx: &mut Context<Self>,
18522 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18523 let snapshot = self.snapshot(window, cx);
18524 let buffer = &snapshot.buffer_snapshot;
18525 let start = buffer.anchor_before(0);
18526 let end = buffer.anchor_after(buffer.len());
18527 let theme = cx.theme().colors();
18528 self.background_highlights_in_range(start..end, &snapshot, theme)
18529 }
18530
18531 #[cfg(feature = "test-support")]
18532 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18533 let snapshot = self.buffer().read(cx).snapshot(cx);
18534
18535 let highlights = self
18536 .background_highlights
18537 .get(&TypeId::of::<items::BufferSearchHighlights>());
18538
18539 if let Some((_color, ranges)) = highlights {
18540 ranges
18541 .iter()
18542 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18543 .collect_vec()
18544 } else {
18545 vec![]
18546 }
18547 }
18548
18549 fn document_highlights_for_position<'a>(
18550 &'a self,
18551 position: Anchor,
18552 buffer: &'a MultiBufferSnapshot,
18553 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18554 let read_highlights = self
18555 .background_highlights
18556 .get(&TypeId::of::<DocumentHighlightRead>())
18557 .map(|h| &h.1);
18558 let write_highlights = self
18559 .background_highlights
18560 .get(&TypeId::of::<DocumentHighlightWrite>())
18561 .map(|h| &h.1);
18562 let left_position = position.bias_left(buffer);
18563 let right_position = position.bias_right(buffer);
18564 read_highlights
18565 .into_iter()
18566 .chain(write_highlights)
18567 .flat_map(move |ranges| {
18568 let start_ix = match ranges.binary_search_by(|probe| {
18569 let cmp = probe.end.cmp(&left_position, buffer);
18570 if cmp.is_ge() {
18571 Ordering::Greater
18572 } else {
18573 Ordering::Less
18574 }
18575 }) {
18576 Ok(i) | Err(i) => i,
18577 };
18578
18579 ranges[start_ix..]
18580 .iter()
18581 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18582 })
18583 }
18584
18585 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18586 self.background_highlights
18587 .get(&TypeId::of::<T>())
18588 .map_or(false, |(_, highlights)| !highlights.is_empty())
18589 }
18590
18591 pub fn background_highlights_in_range(
18592 &self,
18593 search_range: Range<Anchor>,
18594 display_snapshot: &DisplaySnapshot,
18595 theme: &ThemeColors,
18596 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18597 let mut results = Vec::new();
18598 for (color_fetcher, ranges) in self.background_highlights.values() {
18599 let color = color_fetcher(theme);
18600 let start_ix = match ranges.binary_search_by(|probe| {
18601 let cmp = probe
18602 .end
18603 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18604 if cmp.is_gt() {
18605 Ordering::Greater
18606 } else {
18607 Ordering::Less
18608 }
18609 }) {
18610 Ok(i) | Err(i) => i,
18611 };
18612 for range in &ranges[start_ix..] {
18613 if range
18614 .start
18615 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18616 .is_ge()
18617 {
18618 break;
18619 }
18620
18621 let start = range.start.to_display_point(display_snapshot);
18622 let end = range.end.to_display_point(display_snapshot);
18623 results.push((start..end, color))
18624 }
18625 }
18626 results
18627 }
18628
18629 pub fn background_highlight_row_ranges<T: 'static>(
18630 &self,
18631 search_range: Range<Anchor>,
18632 display_snapshot: &DisplaySnapshot,
18633 count: usize,
18634 ) -> Vec<RangeInclusive<DisplayPoint>> {
18635 let mut results = Vec::new();
18636 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18637 return vec![];
18638 };
18639
18640 let start_ix = match ranges.binary_search_by(|probe| {
18641 let cmp = probe
18642 .end
18643 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18644 if cmp.is_gt() {
18645 Ordering::Greater
18646 } else {
18647 Ordering::Less
18648 }
18649 }) {
18650 Ok(i) | Err(i) => i,
18651 };
18652 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18653 if let (Some(start_display), Some(end_display)) = (start, end) {
18654 results.push(
18655 start_display.to_display_point(display_snapshot)
18656 ..=end_display.to_display_point(display_snapshot),
18657 );
18658 }
18659 };
18660 let mut start_row: Option<Point> = None;
18661 let mut end_row: Option<Point> = None;
18662 if ranges.len() > count {
18663 return Vec::new();
18664 }
18665 for range in &ranges[start_ix..] {
18666 if range
18667 .start
18668 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18669 .is_ge()
18670 {
18671 break;
18672 }
18673 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18674 if let Some(current_row) = &end_row {
18675 if end.row == current_row.row {
18676 continue;
18677 }
18678 }
18679 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18680 if start_row.is_none() {
18681 assert_eq!(end_row, None);
18682 start_row = Some(start);
18683 end_row = Some(end);
18684 continue;
18685 }
18686 if let Some(current_end) = end_row.as_mut() {
18687 if start.row > current_end.row + 1 {
18688 push_region(start_row, end_row);
18689 start_row = Some(start);
18690 end_row = Some(end);
18691 } else {
18692 // Merge two hunks.
18693 *current_end = end;
18694 }
18695 } else {
18696 unreachable!();
18697 }
18698 }
18699 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18700 push_region(start_row, end_row);
18701 results
18702 }
18703
18704 pub fn gutter_highlights_in_range(
18705 &self,
18706 search_range: Range<Anchor>,
18707 display_snapshot: &DisplaySnapshot,
18708 cx: &App,
18709 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18710 let mut results = Vec::new();
18711 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18712 let color = color_fetcher(cx);
18713 let start_ix = match ranges.binary_search_by(|probe| {
18714 let cmp = probe
18715 .end
18716 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18717 if cmp.is_gt() {
18718 Ordering::Greater
18719 } else {
18720 Ordering::Less
18721 }
18722 }) {
18723 Ok(i) | Err(i) => i,
18724 };
18725 for range in &ranges[start_ix..] {
18726 if range
18727 .start
18728 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18729 .is_ge()
18730 {
18731 break;
18732 }
18733
18734 let start = range.start.to_display_point(display_snapshot);
18735 let end = range.end.to_display_point(display_snapshot);
18736 results.push((start..end, color))
18737 }
18738 }
18739 results
18740 }
18741
18742 /// Get the text ranges corresponding to the redaction query
18743 pub fn redacted_ranges(
18744 &self,
18745 search_range: Range<Anchor>,
18746 display_snapshot: &DisplaySnapshot,
18747 cx: &App,
18748 ) -> Vec<Range<DisplayPoint>> {
18749 display_snapshot
18750 .buffer_snapshot
18751 .redacted_ranges(search_range, |file| {
18752 if let Some(file) = file {
18753 file.is_private()
18754 && EditorSettings::get(
18755 Some(SettingsLocation {
18756 worktree_id: file.worktree_id(cx),
18757 path: file.path().as_ref(),
18758 }),
18759 cx,
18760 )
18761 .redact_private_values
18762 } else {
18763 false
18764 }
18765 })
18766 .map(|range| {
18767 range.start.to_display_point(display_snapshot)
18768 ..range.end.to_display_point(display_snapshot)
18769 })
18770 .collect()
18771 }
18772
18773 pub fn highlight_text<T: 'static>(
18774 &mut self,
18775 ranges: Vec<Range<Anchor>>,
18776 style: HighlightStyle,
18777 cx: &mut Context<Self>,
18778 ) {
18779 self.display_map.update(cx, |map, _| {
18780 map.highlight_text(TypeId::of::<T>(), ranges, style)
18781 });
18782 cx.notify();
18783 }
18784
18785 pub(crate) fn highlight_inlays<T: 'static>(
18786 &mut self,
18787 highlights: Vec<InlayHighlight>,
18788 style: HighlightStyle,
18789 cx: &mut Context<Self>,
18790 ) {
18791 self.display_map.update(cx, |map, _| {
18792 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18793 });
18794 cx.notify();
18795 }
18796
18797 pub fn text_highlights<'a, T: 'static>(
18798 &'a self,
18799 cx: &'a App,
18800 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18801 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18802 }
18803
18804 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18805 let cleared = self
18806 .display_map
18807 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18808 if cleared {
18809 cx.notify();
18810 }
18811 }
18812
18813 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18814 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18815 && self.focus_handle.is_focused(window)
18816 }
18817
18818 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18819 self.show_cursor_when_unfocused = is_enabled;
18820 cx.notify();
18821 }
18822
18823 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18824 cx.notify();
18825 }
18826
18827 fn on_debug_session_event(
18828 &mut self,
18829 _session: Entity<Session>,
18830 event: &SessionEvent,
18831 cx: &mut Context<Self>,
18832 ) {
18833 match event {
18834 SessionEvent::InvalidateInlineValue => {
18835 self.refresh_inline_values(cx);
18836 }
18837 _ => {}
18838 }
18839 }
18840
18841 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18842 let Some(project) = self.project.clone() else {
18843 return;
18844 };
18845
18846 if !self.inline_value_cache.enabled {
18847 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18848 self.splice_inlays(&inlays, Vec::new(), cx);
18849 return;
18850 }
18851
18852 let current_execution_position = self
18853 .highlighted_rows
18854 .get(&TypeId::of::<ActiveDebugLine>())
18855 .and_then(|lines| lines.last().map(|line| line.range.start));
18856
18857 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18858 let inline_values = editor
18859 .update(cx, |editor, cx| {
18860 let Some(current_execution_position) = current_execution_position else {
18861 return Some(Task::ready(Ok(Vec::new())));
18862 };
18863
18864 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18865 let snapshot = buffer.snapshot(cx);
18866
18867 let excerpt = snapshot.excerpt_containing(
18868 current_execution_position..current_execution_position,
18869 )?;
18870
18871 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18872 })?;
18873
18874 let range =
18875 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18876
18877 project.inline_values(buffer, range, cx)
18878 })
18879 .ok()
18880 .flatten()?
18881 .await
18882 .context("refreshing debugger inlays")
18883 .log_err()?;
18884
18885 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18886
18887 for (buffer_id, inline_value) in inline_values
18888 .into_iter()
18889 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18890 {
18891 buffer_inline_values
18892 .entry(buffer_id)
18893 .or_default()
18894 .push(inline_value);
18895 }
18896
18897 editor
18898 .update(cx, |editor, cx| {
18899 let snapshot = editor.buffer.read(cx).snapshot(cx);
18900 let mut new_inlays = Vec::default();
18901
18902 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18903 let buffer_id = buffer_snapshot.remote_id();
18904 buffer_inline_values
18905 .get(&buffer_id)
18906 .into_iter()
18907 .flatten()
18908 .for_each(|hint| {
18909 let inlay = Inlay::debugger_hint(
18910 post_inc(&mut editor.next_inlay_id),
18911 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18912 hint.text(),
18913 );
18914
18915 new_inlays.push(inlay);
18916 });
18917 }
18918
18919 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18920 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18921
18922 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18923 })
18924 .ok()?;
18925 Some(())
18926 });
18927 }
18928
18929 fn on_buffer_event(
18930 &mut self,
18931 multibuffer: &Entity<MultiBuffer>,
18932 event: &multi_buffer::Event,
18933 window: &mut Window,
18934 cx: &mut Context<Self>,
18935 ) {
18936 match event {
18937 multi_buffer::Event::Edited {
18938 singleton_buffer_edited,
18939 edited_buffer,
18940 } => {
18941 self.scrollbar_marker_state.dirty = true;
18942 self.active_indent_guides_state.dirty = true;
18943 self.refresh_active_diagnostics(cx);
18944 self.refresh_code_actions(window, cx);
18945 self.refresh_selected_text_highlights(true, window, cx);
18946 refresh_matching_bracket_highlights(self, window, cx);
18947 if self.has_active_inline_completion() {
18948 self.update_visible_inline_completion(window, cx);
18949 }
18950 if let Some(project) = self.project.as_ref() {
18951 if let Some(edited_buffer) = edited_buffer {
18952 project.update(cx, |project, cx| {
18953 self.registered_buffers
18954 .entry(edited_buffer.read(cx).remote_id())
18955 .or_insert_with(|| {
18956 project
18957 .register_buffer_with_language_servers(&edited_buffer, cx)
18958 });
18959 });
18960 if edited_buffer.read(cx).file().is_some() {
18961 self.pull_diagnostics(
18962 Some(edited_buffer.read(cx).remote_id()),
18963 window,
18964 cx,
18965 );
18966 }
18967 }
18968 }
18969 cx.emit(EditorEvent::BufferEdited);
18970 cx.emit(SearchEvent::MatchesInvalidated);
18971 if *singleton_buffer_edited {
18972 if let Some(buffer) = edited_buffer {
18973 if buffer.read(cx).file().is_none() {
18974 cx.emit(EditorEvent::TitleChanged);
18975 }
18976 }
18977 if let Some(project) = &self.project {
18978 #[allow(clippy::mutable_key_type)]
18979 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18980 multibuffer
18981 .all_buffers()
18982 .into_iter()
18983 .filter_map(|buffer| {
18984 buffer.update(cx, |buffer, cx| {
18985 let language = buffer.language()?;
18986 let should_discard = project.update(cx, |project, cx| {
18987 project.is_local()
18988 && !project.has_language_servers_for(buffer, cx)
18989 });
18990 should_discard.not().then_some(language.clone())
18991 })
18992 })
18993 .collect::<HashSet<_>>()
18994 });
18995 if !languages_affected.is_empty() {
18996 self.refresh_inlay_hints(
18997 InlayHintRefreshReason::BufferEdited(languages_affected),
18998 cx,
18999 );
19000 }
19001 }
19002 }
19003
19004 let Some(project) = &self.project else { return };
19005 let (telemetry, is_via_ssh) = {
19006 let project = project.read(cx);
19007 let telemetry = project.client().telemetry().clone();
19008 let is_via_ssh = project.is_via_ssh();
19009 (telemetry, is_via_ssh)
19010 };
19011 refresh_linked_ranges(self, window, cx);
19012 telemetry.log_edit_event("editor", is_via_ssh);
19013 }
19014 multi_buffer::Event::ExcerptsAdded {
19015 buffer,
19016 predecessor,
19017 excerpts,
19018 } => {
19019 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19020 let buffer_id = buffer.read(cx).remote_id();
19021 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19022 if let Some(project) = &self.project {
19023 update_uncommitted_diff_for_buffer(
19024 cx.entity(),
19025 project,
19026 [buffer.clone()],
19027 self.buffer.clone(),
19028 cx,
19029 )
19030 .detach();
19031 }
19032 }
19033 cx.emit(EditorEvent::ExcerptsAdded {
19034 buffer: buffer.clone(),
19035 predecessor: *predecessor,
19036 excerpts: excerpts.clone(),
19037 });
19038 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19039 }
19040 multi_buffer::Event::ExcerptsRemoved {
19041 ids,
19042 removed_buffer_ids,
19043 } => {
19044 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19045 let buffer = self.buffer.read(cx);
19046 self.registered_buffers
19047 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19048 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19049 cx.emit(EditorEvent::ExcerptsRemoved {
19050 ids: ids.clone(),
19051 removed_buffer_ids: removed_buffer_ids.clone(),
19052 })
19053 }
19054 multi_buffer::Event::ExcerptsEdited {
19055 excerpt_ids,
19056 buffer_ids,
19057 } => {
19058 self.display_map.update(cx, |map, cx| {
19059 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19060 });
19061 cx.emit(EditorEvent::ExcerptsEdited {
19062 ids: excerpt_ids.clone(),
19063 })
19064 }
19065 multi_buffer::Event::ExcerptsExpanded { ids } => {
19066 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19067 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19068 }
19069 multi_buffer::Event::Reparsed(buffer_id) => {
19070 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19071 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19072
19073 cx.emit(EditorEvent::Reparsed(*buffer_id));
19074 }
19075 multi_buffer::Event::DiffHunksToggled => {
19076 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19077 }
19078 multi_buffer::Event::LanguageChanged(buffer_id) => {
19079 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19080 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19081 cx.emit(EditorEvent::Reparsed(*buffer_id));
19082 cx.notify();
19083 }
19084 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19085 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19086 multi_buffer::Event::FileHandleChanged
19087 | multi_buffer::Event::Reloaded
19088 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19089 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19090 multi_buffer::Event::DiagnosticsUpdated => {
19091 self.update_diagnostics_state(window, cx);
19092 }
19093 _ => {}
19094 };
19095 }
19096
19097 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19098 self.refresh_active_diagnostics(cx);
19099 self.refresh_inline_diagnostics(true, window, cx);
19100 self.scrollbar_marker_state.dirty = true;
19101 cx.notify();
19102 }
19103
19104 pub fn start_temporary_diff_override(&mut self) {
19105 self.load_diff_task.take();
19106 self.temporary_diff_override = true;
19107 }
19108
19109 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19110 self.temporary_diff_override = false;
19111 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19112 self.buffer.update(cx, |buffer, cx| {
19113 buffer.set_all_diff_hunks_collapsed(cx);
19114 });
19115
19116 if let Some(project) = self.project.clone() {
19117 self.load_diff_task = Some(
19118 update_uncommitted_diff_for_buffer(
19119 cx.entity(),
19120 &project,
19121 self.buffer.read(cx).all_buffers(),
19122 self.buffer.clone(),
19123 cx,
19124 )
19125 .shared(),
19126 );
19127 }
19128 }
19129
19130 fn on_display_map_changed(
19131 &mut self,
19132 _: Entity<DisplayMap>,
19133 _: &mut Window,
19134 cx: &mut Context<Self>,
19135 ) {
19136 cx.notify();
19137 }
19138
19139 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19140 let new_severity = if self.diagnostics_enabled() {
19141 EditorSettings::get_global(cx)
19142 .diagnostics_max_severity
19143 .unwrap_or(DiagnosticSeverity::Hint)
19144 } else {
19145 DiagnosticSeverity::Off
19146 };
19147 self.set_max_diagnostics_severity(new_severity, cx);
19148 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19149 self.update_edit_prediction_settings(cx);
19150 self.refresh_inline_completion(true, false, window, cx);
19151 self.refresh_inlay_hints(
19152 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19153 self.selections.newest_anchor().head(),
19154 &self.buffer.read(cx).snapshot(cx),
19155 cx,
19156 )),
19157 cx,
19158 );
19159
19160 let old_cursor_shape = self.cursor_shape;
19161
19162 {
19163 let editor_settings = EditorSettings::get_global(cx);
19164 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19165 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19166 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19167 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19168 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19169 }
19170
19171 if old_cursor_shape != self.cursor_shape {
19172 cx.emit(EditorEvent::CursorShapeChanged);
19173 }
19174
19175 let project_settings = ProjectSettings::get_global(cx);
19176 self.serialize_dirty_buffers =
19177 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19178
19179 if self.mode.is_full() {
19180 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19181 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19182 if self.show_inline_diagnostics != show_inline_diagnostics {
19183 self.show_inline_diagnostics = show_inline_diagnostics;
19184 self.refresh_inline_diagnostics(false, window, cx);
19185 }
19186
19187 if self.git_blame_inline_enabled != inline_blame_enabled {
19188 self.toggle_git_blame_inline_internal(false, window, cx);
19189 }
19190
19191 let minimap_settings = EditorSettings::get_global(cx).minimap;
19192 if self.minimap_visibility != MinimapVisibility::Disabled {
19193 if self.minimap_visibility.settings_visibility()
19194 != minimap_settings.minimap_enabled()
19195 {
19196 self.set_minimap_visibility(
19197 MinimapVisibility::for_mode(self.mode(), cx),
19198 window,
19199 cx,
19200 );
19201 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19202 minimap_entity.update(cx, |minimap_editor, cx| {
19203 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19204 })
19205 }
19206 }
19207 }
19208
19209 cx.notify();
19210 }
19211
19212 pub fn set_searchable(&mut self, searchable: bool) {
19213 self.searchable = searchable;
19214 }
19215
19216 pub fn searchable(&self) -> bool {
19217 self.searchable
19218 }
19219
19220 fn open_proposed_changes_editor(
19221 &mut self,
19222 _: &OpenProposedChangesEditor,
19223 window: &mut Window,
19224 cx: &mut Context<Self>,
19225 ) {
19226 let Some(workspace) = self.workspace() else {
19227 cx.propagate();
19228 return;
19229 };
19230
19231 let selections = self.selections.all::<usize>(cx);
19232 let multi_buffer = self.buffer.read(cx);
19233 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19234 let mut new_selections_by_buffer = HashMap::default();
19235 for selection in selections {
19236 for (buffer, range, _) in
19237 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19238 {
19239 let mut range = range.to_point(buffer);
19240 range.start.column = 0;
19241 range.end.column = buffer.line_len(range.end.row);
19242 new_selections_by_buffer
19243 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19244 .or_insert(Vec::new())
19245 .push(range)
19246 }
19247 }
19248
19249 let proposed_changes_buffers = new_selections_by_buffer
19250 .into_iter()
19251 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19252 .collect::<Vec<_>>();
19253 let proposed_changes_editor = cx.new(|cx| {
19254 ProposedChangesEditor::new(
19255 "Proposed changes",
19256 proposed_changes_buffers,
19257 self.project.clone(),
19258 window,
19259 cx,
19260 )
19261 });
19262
19263 window.defer(cx, move |window, cx| {
19264 workspace.update(cx, |workspace, cx| {
19265 workspace.active_pane().update(cx, |pane, cx| {
19266 pane.add_item(
19267 Box::new(proposed_changes_editor),
19268 true,
19269 true,
19270 None,
19271 window,
19272 cx,
19273 );
19274 });
19275 });
19276 });
19277 }
19278
19279 pub fn open_excerpts_in_split(
19280 &mut self,
19281 _: &OpenExcerptsSplit,
19282 window: &mut Window,
19283 cx: &mut Context<Self>,
19284 ) {
19285 self.open_excerpts_common(None, true, window, cx)
19286 }
19287
19288 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19289 self.open_excerpts_common(None, false, window, cx)
19290 }
19291
19292 fn open_excerpts_common(
19293 &mut self,
19294 jump_data: Option<JumpData>,
19295 split: bool,
19296 window: &mut Window,
19297 cx: &mut Context<Self>,
19298 ) {
19299 let Some(workspace) = self.workspace() else {
19300 cx.propagate();
19301 return;
19302 };
19303
19304 if self.buffer.read(cx).is_singleton() {
19305 cx.propagate();
19306 return;
19307 }
19308
19309 let mut new_selections_by_buffer = HashMap::default();
19310 match &jump_data {
19311 Some(JumpData::MultiBufferPoint {
19312 excerpt_id,
19313 position,
19314 anchor,
19315 line_offset_from_top,
19316 }) => {
19317 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19318 if let Some(buffer) = multi_buffer_snapshot
19319 .buffer_id_for_excerpt(*excerpt_id)
19320 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19321 {
19322 let buffer_snapshot = buffer.read(cx).snapshot();
19323 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19324 language::ToPoint::to_point(anchor, &buffer_snapshot)
19325 } else {
19326 buffer_snapshot.clip_point(*position, Bias::Left)
19327 };
19328 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19329 new_selections_by_buffer.insert(
19330 buffer,
19331 (
19332 vec![jump_to_offset..jump_to_offset],
19333 Some(*line_offset_from_top),
19334 ),
19335 );
19336 }
19337 }
19338 Some(JumpData::MultiBufferRow {
19339 row,
19340 line_offset_from_top,
19341 }) => {
19342 let point = MultiBufferPoint::new(row.0, 0);
19343 if let Some((buffer, buffer_point, _)) =
19344 self.buffer.read(cx).point_to_buffer_point(point, cx)
19345 {
19346 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19347 new_selections_by_buffer
19348 .entry(buffer)
19349 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19350 .0
19351 .push(buffer_offset..buffer_offset)
19352 }
19353 }
19354 None => {
19355 let selections = self.selections.all::<usize>(cx);
19356 let multi_buffer = self.buffer.read(cx);
19357 for selection in selections {
19358 for (snapshot, range, _, anchor) in multi_buffer
19359 .snapshot(cx)
19360 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19361 {
19362 if let Some(anchor) = anchor {
19363 // selection is in a deleted hunk
19364 let Some(buffer_id) = anchor.buffer_id else {
19365 continue;
19366 };
19367 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19368 continue;
19369 };
19370 let offset = text::ToOffset::to_offset(
19371 &anchor.text_anchor,
19372 &buffer_handle.read(cx).snapshot(),
19373 );
19374 let range = offset..offset;
19375 new_selections_by_buffer
19376 .entry(buffer_handle)
19377 .or_insert((Vec::new(), None))
19378 .0
19379 .push(range)
19380 } else {
19381 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19382 else {
19383 continue;
19384 };
19385 new_selections_by_buffer
19386 .entry(buffer_handle)
19387 .or_insert((Vec::new(), None))
19388 .0
19389 .push(range)
19390 }
19391 }
19392 }
19393 }
19394 }
19395
19396 new_selections_by_buffer
19397 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19398
19399 if new_selections_by_buffer.is_empty() {
19400 return;
19401 }
19402
19403 // We defer the pane interaction because we ourselves are a workspace item
19404 // and activating a new item causes the pane to call a method on us reentrantly,
19405 // which panics if we're on the stack.
19406 window.defer(cx, move |window, cx| {
19407 workspace.update(cx, |workspace, cx| {
19408 let pane = if split {
19409 workspace.adjacent_pane(window, cx)
19410 } else {
19411 workspace.active_pane().clone()
19412 };
19413
19414 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19415 let editor = buffer
19416 .read(cx)
19417 .file()
19418 .is_none()
19419 .then(|| {
19420 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19421 // so `workspace.open_project_item` will never find them, always opening a new editor.
19422 // Instead, we try to activate the existing editor in the pane first.
19423 let (editor, pane_item_index) =
19424 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19425 let editor = item.downcast::<Editor>()?;
19426 let singleton_buffer =
19427 editor.read(cx).buffer().read(cx).as_singleton()?;
19428 if singleton_buffer == buffer {
19429 Some((editor, i))
19430 } else {
19431 None
19432 }
19433 })?;
19434 pane.update(cx, |pane, cx| {
19435 pane.activate_item(pane_item_index, true, true, window, cx)
19436 });
19437 Some(editor)
19438 })
19439 .flatten()
19440 .unwrap_or_else(|| {
19441 workspace.open_project_item::<Self>(
19442 pane.clone(),
19443 buffer,
19444 true,
19445 true,
19446 window,
19447 cx,
19448 )
19449 });
19450
19451 editor.update(cx, |editor, cx| {
19452 let autoscroll = match scroll_offset {
19453 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19454 None => Autoscroll::newest(),
19455 };
19456 let nav_history = editor.nav_history.take();
19457 editor.change_selections(Some(autoscroll), window, cx, |s| {
19458 s.select_ranges(ranges);
19459 });
19460 editor.nav_history = nav_history;
19461 });
19462 }
19463 })
19464 });
19465 }
19466
19467 // For now, don't allow opening excerpts in buffers that aren't backed by
19468 // regular project files.
19469 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19470 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19471 }
19472
19473 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19474 let snapshot = self.buffer.read(cx).read(cx);
19475 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19476 Some(
19477 ranges
19478 .iter()
19479 .map(move |range| {
19480 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19481 })
19482 .collect(),
19483 )
19484 }
19485
19486 fn selection_replacement_ranges(
19487 &self,
19488 range: Range<OffsetUtf16>,
19489 cx: &mut App,
19490 ) -> Vec<Range<OffsetUtf16>> {
19491 let selections = self.selections.all::<OffsetUtf16>(cx);
19492 let newest_selection = selections
19493 .iter()
19494 .max_by_key(|selection| selection.id)
19495 .unwrap();
19496 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19497 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19498 let snapshot = self.buffer.read(cx).read(cx);
19499 selections
19500 .into_iter()
19501 .map(|mut selection| {
19502 selection.start.0 =
19503 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19504 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19505 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19506 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19507 })
19508 .collect()
19509 }
19510
19511 fn report_editor_event(
19512 &self,
19513 event_type: &'static str,
19514 file_extension: Option<String>,
19515 cx: &App,
19516 ) {
19517 if cfg!(any(test, feature = "test-support")) {
19518 return;
19519 }
19520
19521 let Some(project) = &self.project else { return };
19522
19523 // If None, we are in a file without an extension
19524 let file = self
19525 .buffer
19526 .read(cx)
19527 .as_singleton()
19528 .and_then(|b| b.read(cx).file());
19529 let file_extension = file_extension.or(file
19530 .as_ref()
19531 .and_then(|file| Path::new(file.file_name(cx)).extension())
19532 .and_then(|e| e.to_str())
19533 .map(|a| a.to_string()));
19534
19535 let vim_mode = vim_enabled(cx);
19536
19537 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19538 let copilot_enabled = edit_predictions_provider
19539 == language::language_settings::EditPredictionProvider::Copilot;
19540 let copilot_enabled_for_language = self
19541 .buffer
19542 .read(cx)
19543 .language_settings(cx)
19544 .show_edit_predictions;
19545
19546 let project = project.read(cx);
19547 telemetry::event!(
19548 event_type,
19549 file_extension,
19550 vim_mode,
19551 copilot_enabled,
19552 copilot_enabled_for_language,
19553 edit_predictions_provider,
19554 is_via_ssh = project.is_via_ssh(),
19555 );
19556 }
19557
19558 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19559 /// with each line being an array of {text, highlight} objects.
19560 fn copy_highlight_json(
19561 &mut self,
19562 _: &CopyHighlightJson,
19563 window: &mut Window,
19564 cx: &mut Context<Self>,
19565 ) {
19566 #[derive(Serialize)]
19567 struct Chunk<'a> {
19568 text: String,
19569 highlight: Option<&'a str>,
19570 }
19571
19572 let snapshot = self.buffer.read(cx).snapshot(cx);
19573 let range = self
19574 .selected_text_range(false, window, cx)
19575 .and_then(|selection| {
19576 if selection.range.is_empty() {
19577 None
19578 } else {
19579 Some(selection.range)
19580 }
19581 })
19582 .unwrap_or_else(|| 0..snapshot.len());
19583
19584 let chunks = snapshot.chunks(range, true);
19585 let mut lines = Vec::new();
19586 let mut line: VecDeque<Chunk> = VecDeque::new();
19587
19588 let Some(style) = self.style.as_ref() else {
19589 return;
19590 };
19591
19592 for chunk in chunks {
19593 let highlight = chunk
19594 .syntax_highlight_id
19595 .and_then(|id| id.name(&style.syntax));
19596 let mut chunk_lines = chunk.text.split('\n').peekable();
19597 while let Some(text) = chunk_lines.next() {
19598 let mut merged_with_last_token = false;
19599 if let Some(last_token) = line.back_mut() {
19600 if last_token.highlight == highlight {
19601 last_token.text.push_str(text);
19602 merged_with_last_token = true;
19603 }
19604 }
19605
19606 if !merged_with_last_token {
19607 line.push_back(Chunk {
19608 text: text.into(),
19609 highlight,
19610 });
19611 }
19612
19613 if chunk_lines.peek().is_some() {
19614 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19615 line.pop_front();
19616 }
19617 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19618 line.pop_back();
19619 }
19620
19621 lines.push(mem::take(&mut line));
19622 }
19623 }
19624 }
19625
19626 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19627 return;
19628 };
19629 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19630 }
19631
19632 pub fn open_context_menu(
19633 &mut self,
19634 _: &OpenContextMenu,
19635 window: &mut Window,
19636 cx: &mut Context<Self>,
19637 ) {
19638 self.request_autoscroll(Autoscroll::newest(), cx);
19639 let position = self.selections.newest_display(cx).start;
19640 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19641 }
19642
19643 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19644 &self.inlay_hint_cache
19645 }
19646
19647 pub fn replay_insert_event(
19648 &mut self,
19649 text: &str,
19650 relative_utf16_range: Option<Range<isize>>,
19651 window: &mut Window,
19652 cx: &mut Context<Self>,
19653 ) {
19654 if !self.input_enabled {
19655 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19656 return;
19657 }
19658 if let Some(relative_utf16_range) = relative_utf16_range {
19659 let selections = self.selections.all::<OffsetUtf16>(cx);
19660 self.change_selections(None, window, cx, |s| {
19661 let new_ranges = selections.into_iter().map(|range| {
19662 let start = OffsetUtf16(
19663 range
19664 .head()
19665 .0
19666 .saturating_add_signed(relative_utf16_range.start),
19667 );
19668 let end = OffsetUtf16(
19669 range
19670 .head()
19671 .0
19672 .saturating_add_signed(relative_utf16_range.end),
19673 );
19674 start..end
19675 });
19676 s.select_ranges(new_ranges);
19677 });
19678 }
19679
19680 self.handle_input(text, window, cx);
19681 }
19682
19683 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19684 let Some(provider) = self.semantics_provider.as_ref() else {
19685 return false;
19686 };
19687
19688 let mut supports = false;
19689 self.buffer().update(cx, |this, cx| {
19690 this.for_each_buffer(|buffer| {
19691 supports |= provider.supports_inlay_hints(buffer, cx);
19692 });
19693 });
19694
19695 supports
19696 }
19697
19698 pub fn is_focused(&self, window: &Window) -> bool {
19699 self.focus_handle.is_focused(window)
19700 }
19701
19702 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19703 cx.emit(EditorEvent::Focused);
19704
19705 if let Some(descendant) = self
19706 .last_focused_descendant
19707 .take()
19708 .and_then(|descendant| descendant.upgrade())
19709 {
19710 window.focus(&descendant);
19711 } else {
19712 if let Some(blame) = self.blame.as_ref() {
19713 blame.update(cx, GitBlame::focus)
19714 }
19715
19716 self.blink_manager.update(cx, BlinkManager::enable);
19717 self.show_cursor_names(window, cx);
19718 self.buffer.update(cx, |buffer, cx| {
19719 buffer.finalize_last_transaction(cx);
19720 if self.leader_id.is_none() {
19721 buffer.set_active_selections(
19722 &self.selections.disjoint_anchors(),
19723 self.selections.line_mode,
19724 self.cursor_shape,
19725 cx,
19726 );
19727 }
19728 });
19729 }
19730 }
19731
19732 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19733 cx.emit(EditorEvent::FocusedIn)
19734 }
19735
19736 fn handle_focus_out(
19737 &mut self,
19738 event: FocusOutEvent,
19739 _window: &mut Window,
19740 cx: &mut Context<Self>,
19741 ) {
19742 if event.blurred != self.focus_handle {
19743 self.last_focused_descendant = Some(event.blurred);
19744 }
19745 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19746 }
19747
19748 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19749 self.blink_manager.update(cx, BlinkManager::disable);
19750 self.buffer
19751 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19752
19753 if let Some(blame) = self.blame.as_ref() {
19754 blame.update(cx, GitBlame::blur)
19755 }
19756 if !self.hover_state.focused(window, cx) {
19757 hide_hover(self, cx);
19758 }
19759 if !self
19760 .context_menu
19761 .borrow()
19762 .as_ref()
19763 .is_some_and(|context_menu| context_menu.focused(window, cx))
19764 {
19765 self.hide_context_menu(window, cx);
19766 }
19767 self.discard_inline_completion(false, cx);
19768 cx.emit(EditorEvent::Blurred);
19769 cx.notify();
19770 }
19771
19772 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19773 let mut pending: String = window
19774 .pending_input_keystrokes()
19775 .into_iter()
19776 .flatten()
19777 .filter_map(|keystroke| {
19778 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19779 keystroke.key_char.clone()
19780 } else {
19781 None
19782 }
19783 })
19784 .collect();
19785
19786 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19787 pending = "".to_string();
19788 }
19789
19790 let existing_pending = self
19791 .text_highlights::<PendingInput>(cx)
19792 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19793 if existing_pending.is_none() && pending.is_empty() {
19794 return;
19795 }
19796 let transaction =
19797 self.transact(window, cx, |this, window, cx| {
19798 let selections = this.selections.all::<usize>(cx);
19799 let edits = selections
19800 .iter()
19801 .map(|selection| (selection.end..selection.end, pending.clone()));
19802 this.edit(edits, cx);
19803 this.change_selections(None, window, cx, |s| {
19804 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19805 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19806 }));
19807 });
19808 if let Some(existing_ranges) = existing_pending {
19809 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19810 this.edit(edits, cx);
19811 }
19812 });
19813
19814 let snapshot = self.snapshot(window, cx);
19815 let ranges = self
19816 .selections
19817 .all::<usize>(cx)
19818 .into_iter()
19819 .map(|selection| {
19820 snapshot.buffer_snapshot.anchor_after(selection.end)
19821 ..snapshot
19822 .buffer_snapshot
19823 .anchor_before(selection.end + pending.len())
19824 })
19825 .collect();
19826
19827 if pending.is_empty() {
19828 self.clear_highlights::<PendingInput>(cx);
19829 } else {
19830 self.highlight_text::<PendingInput>(
19831 ranges,
19832 HighlightStyle {
19833 underline: Some(UnderlineStyle {
19834 thickness: px(1.),
19835 color: None,
19836 wavy: false,
19837 }),
19838 ..Default::default()
19839 },
19840 cx,
19841 );
19842 }
19843
19844 self.ime_transaction = self.ime_transaction.or(transaction);
19845 if let Some(transaction) = self.ime_transaction {
19846 self.buffer.update(cx, |buffer, cx| {
19847 buffer.group_until_transaction(transaction, cx);
19848 });
19849 }
19850
19851 if self.text_highlights::<PendingInput>(cx).is_none() {
19852 self.ime_transaction.take();
19853 }
19854 }
19855
19856 pub fn register_action_renderer(
19857 &mut self,
19858 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19859 ) -> Subscription {
19860 let id = self.next_editor_action_id.post_inc();
19861 self.editor_actions
19862 .borrow_mut()
19863 .insert(id, Box::new(listener));
19864
19865 let editor_actions = self.editor_actions.clone();
19866 Subscription::new(move || {
19867 editor_actions.borrow_mut().remove(&id);
19868 })
19869 }
19870
19871 pub fn register_action<A: Action>(
19872 &mut self,
19873 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19874 ) -> Subscription {
19875 let id = self.next_editor_action_id.post_inc();
19876 let listener = Arc::new(listener);
19877 self.editor_actions.borrow_mut().insert(
19878 id,
19879 Box::new(move |_, window, _| {
19880 let listener = listener.clone();
19881 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19882 let action = action.downcast_ref().unwrap();
19883 if phase == DispatchPhase::Bubble {
19884 listener(action, window, cx)
19885 }
19886 })
19887 }),
19888 );
19889
19890 let editor_actions = self.editor_actions.clone();
19891 Subscription::new(move || {
19892 editor_actions.borrow_mut().remove(&id);
19893 })
19894 }
19895
19896 pub fn file_header_size(&self) -> u32 {
19897 FILE_HEADER_HEIGHT
19898 }
19899
19900 pub fn restore(
19901 &mut self,
19902 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19903 window: &mut Window,
19904 cx: &mut Context<Self>,
19905 ) {
19906 let workspace = self.workspace();
19907 let project = self.project.as_ref();
19908 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19909 let mut tasks = Vec::new();
19910 for (buffer_id, changes) in revert_changes {
19911 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19912 buffer.update(cx, |buffer, cx| {
19913 buffer.edit(
19914 changes
19915 .into_iter()
19916 .map(|(range, text)| (range, text.to_string())),
19917 None,
19918 cx,
19919 );
19920 });
19921
19922 if let Some(project) =
19923 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19924 {
19925 project.update(cx, |project, cx| {
19926 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19927 })
19928 }
19929 }
19930 }
19931 tasks
19932 });
19933 cx.spawn_in(window, async move |_, cx| {
19934 for (buffer, task) in save_tasks {
19935 let result = task.await;
19936 if result.is_err() {
19937 let Some(path) = buffer
19938 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19939 .ok()
19940 else {
19941 continue;
19942 };
19943 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19944 let Some(task) = cx
19945 .update_window_entity(&workspace, |workspace, window, cx| {
19946 workspace
19947 .open_path_preview(path, None, false, false, false, window, cx)
19948 })
19949 .ok()
19950 else {
19951 continue;
19952 };
19953 task.await.log_err();
19954 }
19955 }
19956 }
19957 })
19958 .detach();
19959 self.change_selections(None, window, cx, |selections| selections.refresh());
19960 }
19961
19962 pub fn to_pixel_point(
19963 &self,
19964 source: multi_buffer::Anchor,
19965 editor_snapshot: &EditorSnapshot,
19966 window: &mut Window,
19967 ) -> Option<gpui::Point<Pixels>> {
19968 let source_point = source.to_display_point(editor_snapshot);
19969 self.display_to_pixel_point(source_point, editor_snapshot, window)
19970 }
19971
19972 pub fn display_to_pixel_point(
19973 &self,
19974 source: DisplayPoint,
19975 editor_snapshot: &EditorSnapshot,
19976 window: &mut Window,
19977 ) -> Option<gpui::Point<Pixels>> {
19978 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19979 let text_layout_details = self.text_layout_details(window);
19980 let scroll_top = text_layout_details
19981 .scroll_anchor
19982 .scroll_position(editor_snapshot)
19983 .y;
19984
19985 if source.row().as_f32() < scroll_top.floor() {
19986 return None;
19987 }
19988 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19989 let source_y = line_height * (source.row().as_f32() - scroll_top);
19990 Some(gpui::Point::new(source_x, source_y))
19991 }
19992
19993 pub fn has_visible_completions_menu(&self) -> bool {
19994 !self.edit_prediction_preview_is_active()
19995 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19996 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19997 })
19998 }
19999
20000 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20001 if self.mode.is_minimap() {
20002 return;
20003 }
20004 self.addons
20005 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20006 }
20007
20008 pub fn unregister_addon<T: Addon>(&mut self) {
20009 self.addons.remove(&std::any::TypeId::of::<T>());
20010 }
20011
20012 pub fn addon<T: Addon>(&self) -> Option<&T> {
20013 let type_id = std::any::TypeId::of::<T>();
20014 self.addons
20015 .get(&type_id)
20016 .and_then(|item| item.to_any().downcast_ref::<T>())
20017 }
20018
20019 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20020 let type_id = std::any::TypeId::of::<T>();
20021 self.addons
20022 .get_mut(&type_id)
20023 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20024 }
20025
20026 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20027 let text_layout_details = self.text_layout_details(window);
20028 let style = &text_layout_details.editor_style;
20029 let font_id = window.text_system().resolve_font(&style.text.font());
20030 let font_size = style.text.font_size.to_pixels(window.rem_size());
20031 let line_height = style.text.line_height_in_pixels(window.rem_size());
20032 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20033
20034 gpui::Size::new(em_width, line_height)
20035 }
20036
20037 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20038 self.load_diff_task.clone()
20039 }
20040
20041 fn read_metadata_from_db(
20042 &mut self,
20043 item_id: u64,
20044 workspace_id: WorkspaceId,
20045 window: &mut Window,
20046 cx: &mut Context<Editor>,
20047 ) {
20048 if self.is_singleton(cx)
20049 && !self.mode.is_minimap()
20050 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20051 {
20052 let buffer_snapshot = OnceCell::new();
20053
20054 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20055 if !folds.is_empty() {
20056 let snapshot =
20057 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20058 self.fold_ranges(
20059 folds
20060 .into_iter()
20061 .map(|(start, end)| {
20062 snapshot.clip_offset(start, Bias::Left)
20063 ..snapshot.clip_offset(end, Bias::Right)
20064 })
20065 .collect(),
20066 false,
20067 window,
20068 cx,
20069 );
20070 }
20071 }
20072
20073 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20074 if !selections.is_empty() {
20075 let snapshot =
20076 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20077 // skip adding the initial selection to selection history
20078 self.selection_history.mode = SelectionHistoryMode::Skipping;
20079 self.change_selections(None, window, cx, |s| {
20080 s.select_ranges(selections.into_iter().map(|(start, end)| {
20081 snapshot.clip_offset(start, Bias::Left)
20082 ..snapshot.clip_offset(end, Bias::Right)
20083 }));
20084 });
20085 self.selection_history.mode = SelectionHistoryMode::Normal;
20086 }
20087 };
20088 }
20089
20090 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20091 }
20092}
20093
20094fn vim_enabled(cx: &App) -> bool {
20095 cx.global::<SettingsStore>()
20096 .raw_user_settings()
20097 .get("vim_mode")
20098 == Some(&serde_json::Value::Bool(true))
20099}
20100
20101fn process_completion_for_edit(
20102 completion: &Completion,
20103 intent: CompletionIntent,
20104 buffer: &Entity<Buffer>,
20105 cursor_position: &text::Anchor,
20106 cx: &mut Context<Editor>,
20107) -> CompletionEdit {
20108 let buffer = buffer.read(cx);
20109 let buffer_snapshot = buffer.snapshot();
20110 let (snippet, new_text) = if completion.is_snippet() {
20111 // Workaround for typescript language server issues so that methods don't expand within
20112 // strings and functions with type expressions. The previous point is used because the query
20113 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20114 let mut snippet_source = completion.new_text.clone();
20115 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20116 previous_point.column = previous_point.column.saturating_sub(1);
20117 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20118 if scope.prefers_label_for_snippet_in_completion() {
20119 if let Some(label) = completion.label() {
20120 if matches!(
20121 completion.kind(),
20122 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20123 ) {
20124 snippet_source = label;
20125 }
20126 }
20127 }
20128 }
20129 match Snippet::parse(&snippet_source).log_err() {
20130 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20131 None => (None, completion.new_text.clone()),
20132 }
20133 } else {
20134 (None, completion.new_text.clone())
20135 };
20136
20137 let mut range_to_replace = {
20138 let replace_range = &completion.replace_range;
20139 if let CompletionSource::Lsp {
20140 insert_range: Some(insert_range),
20141 ..
20142 } = &completion.source
20143 {
20144 debug_assert_eq!(
20145 insert_range.start, replace_range.start,
20146 "insert_range and replace_range should start at the same position"
20147 );
20148 debug_assert!(
20149 insert_range
20150 .start
20151 .cmp(&cursor_position, &buffer_snapshot)
20152 .is_le(),
20153 "insert_range should start before or at cursor position"
20154 );
20155 debug_assert!(
20156 replace_range
20157 .start
20158 .cmp(&cursor_position, &buffer_snapshot)
20159 .is_le(),
20160 "replace_range should start before or at cursor position"
20161 );
20162 debug_assert!(
20163 insert_range
20164 .end
20165 .cmp(&cursor_position, &buffer_snapshot)
20166 .is_le(),
20167 "insert_range should end before or at cursor position"
20168 );
20169
20170 let should_replace = match intent {
20171 CompletionIntent::CompleteWithInsert => false,
20172 CompletionIntent::CompleteWithReplace => true,
20173 CompletionIntent::Complete | CompletionIntent::Compose => {
20174 let insert_mode =
20175 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20176 .completions
20177 .lsp_insert_mode;
20178 match insert_mode {
20179 LspInsertMode::Insert => false,
20180 LspInsertMode::Replace => true,
20181 LspInsertMode::ReplaceSubsequence => {
20182 let mut text_to_replace = buffer.chars_for_range(
20183 buffer.anchor_before(replace_range.start)
20184 ..buffer.anchor_after(replace_range.end),
20185 );
20186 let mut current_needle = text_to_replace.next();
20187 for haystack_ch in completion.label.text.chars() {
20188 if let Some(needle_ch) = current_needle {
20189 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20190 current_needle = text_to_replace.next();
20191 }
20192 }
20193 }
20194 current_needle.is_none()
20195 }
20196 LspInsertMode::ReplaceSuffix => {
20197 if replace_range
20198 .end
20199 .cmp(&cursor_position, &buffer_snapshot)
20200 .is_gt()
20201 {
20202 let range_after_cursor = *cursor_position..replace_range.end;
20203 let text_after_cursor = buffer
20204 .text_for_range(
20205 buffer.anchor_before(range_after_cursor.start)
20206 ..buffer.anchor_after(range_after_cursor.end),
20207 )
20208 .collect::<String>()
20209 .to_ascii_lowercase();
20210 completion
20211 .label
20212 .text
20213 .to_ascii_lowercase()
20214 .ends_with(&text_after_cursor)
20215 } else {
20216 true
20217 }
20218 }
20219 }
20220 }
20221 };
20222
20223 if should_replace {
20224 replace_range.clone()
20225 } else {
20226 insert_range.clone()
20227 }
20228 } else {
20229 replace_range.clone()
20230 }
20231 };
20232
20233 if range_to_replace
20234 .end
20235 .cmp(&cursor_position, &buffer_snapshot)
20236 .is_lt()
20237 {
20238 range_to_replace.end = *cursor_position;
20239 }
20240
20241 CompletionEdit {
20242 new_text,
20243 replace_range: range_to_replace.to_offset(&buffer),
20244 snippet,
20245 }
20246}
20247
20248struct CompletionEdit {
20249 new_text: String,
20250 replace_range: Range<usize>,
20251 snippet: Option<Snippet>,
20252}
20253
20254fn insert_extra_newline_brackets(
20255 buffer: &MultiBufferSnapshot,
20256 range: Range<usize>,
20257 language: &language::LanguageScope,
20258) -> bool {
20259 let leading_whitespace_len = buffer
20260 .reversed_chars_at(range.start)
20261 .take_while(|c| c.is_whitespace() && *c != '\n')
20262 .map(|c| c.len_utf8())
20263 .sum::<usize>();
20264 let trailing_whitespace_len = buffer
20265 .chars_at(range.end)
20266 .take_while(|c| c.is_whitespace() && *c != '\n')
20267 .map(|c| c.len_utf8())
20268 .sum::<usize>();
20269 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20270
20271 language.brackets().any(|(pair, enabled)| {
20272 let pair_start = pair.start.trim_end();
20273 let pair_end = pair.end.trim_start();
20274
20275 enabled
20276 && pair.newline
20277 && buffer.contains_str_at(range.end, pair_end)
20278 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20279 })
20280}
20281
20282fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20283 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20284 [(buffer, range, _)] => (*buffer, range.clone()),
20285 _ => return false,
20286 };
20287 let pair = {
20288 let mut result: Option<BracketMatch> = None;
20289
20290 for pair in buffer
20291 .all_bracket_ranges(range.clone())
20292 .filter(move |pair| {
20293 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20294 })
20295 {
20296 let len = pair.close_range.end - pair.open_range.start;
20297
20298 if let Some(existing) = &result {
20299 let existing_len = existing.close_range.end - existing.open_range.start;
20300 if len > existing_len {
20301 continue;
20302 }
20303 }
20304
20305 result = Some(pair);
20306 }
20307
20308 result
20309 };
20310 let Some(pair) = pair else {
20311 return false;
20312 };
20313 pair.newline_only
20314 && buffer
20315 .chars_for_range(pair.open_range.end..range.start)
20316 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20317 .all(|c| c.is_whitespace() && c != '\n')
20318}
20319
20320fn update_uncommitted_diff_for_buffer(
20321 editor: Entity<Editor>,
20322 project: &Entity<Project>,
20323 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20324 buffer: Entity<MultiBuffer>,
20325 cx: &mut App,
20326) -> Task<()> {
20327 let mut tasks = Vec::new();
20328 project.update(cx, |project, cx| {
20329 for buffer in buffers {
20330 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20331 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20332 }
20333 }
20334 });
20335 cx.spawn(async move |cx| {
20336 let diffs = future::join_all(tasks).await;
20337 if editor
20338 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20339 .unwrap_or(false)
20340 {
20341 return;
20342 }
20343
20344 buffer
20345 .update(cx, |buffer, cx| {
20346 for diff in diffs.into_iter().flatten() {
20347 buffer.add_diff(diff, cx);
20348 }
20349 })
20350 .ok();
20351 })
20352}
20353
20354fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20355 let tab_size = tab_size.get() as usize;
20356 let mut width = offset;
20357
20358 for ch in text.chars() {
20359 width += if ch == '\t' {
20360 tab_size - (width % tab_size)
20361 } else {
20362 1
20363 };
20364 }
20365
20366 width - offset
20367}
20368
20369#[cfg(test)]
20370mod tests {
20371 use super::*;
20372
20373 #[test]
20374 fn test_string_size_with_expanded_tabs() {
20375 let nz = |val| NonZeroU32::new(val).unwrap();
20376 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20377 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20378 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20379 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20380 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20381 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20382 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20383 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20384 }
20385}
20386
20387/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20388struct WordBreakingTokenizer<'a> {
20389 input: &'a str,
20390}
20391
20392impl<'a> WordBreakingTokenizer<'a> {
20393 fn new(input: &'a str) -> Self {
20394 Self { input }
20395 }
20396}
20397
20398fn is_char_ideographic(ch: char) -> bool {
20399 use unicode_script::Script::*;
20400 use unicode_script::UnicodeScript;
20401 matches!(ch.script(), Han | Tangut | Yi)
20402}
20403
20404fn is_grapheme_ideographic(text: &str) -> bool {
20405 text.chars().any(is_char_ideographic)
20406}
20407
20408fn is_grapheme_whitespace(text: &str) -> bool {
20409 text.chars().any(|x| x.is_whitespace())
20410}
20411
20412fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20413 text.chars().next().map_or(false, |ch| {
20414 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20415 })
20416}
20417
20418#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20419enum WordBreakToken<'a> {
20420 Word { token: &'a str, grapheme_len: usize },
20421 InlineWhitespace { token: &'a str, grapheme_len: usize },
20422 Newline,
20423}
20424
20425impl<'a> Iterator for WordBreakingTokenizer<'a> {
20426 /// Yields a span, the count of graphemes in the token, and whether it was
20427 /// whitespace. Note that it also breaks at word boundaries.
20428 type Item = WordBreakToken<'a>;
20429
20430 fn next(&mut self) -> Option<Self::Item> {
20431 use unicode_segmentation::UnicodeSegmentation;
20432 if self.input.is_empty() {
20433 return None;
20434 }
20435
20436 let mut iter = self.input.graphemes(true).peekable();
20437 let mut offset = 0;
20438 let mut grapheme_len = 0;
20439 if let Some(first_grapheme) = iter.next() {
20440 let is_newline = first_grapheme == "\n";
20441 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20442 offset += first_grapheme.len();
20443 grapheme_len += 1;
20444 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20445 if let Some(grapheme) = iter.peek().copied() {
20446 if should_stay_with_preceding_ideograph(grapheme) {
20447 offset += grapheme.len();
20448 grapheme_len += 1;
20449 }
20450 }
20451 } else {
20452 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20453 let mut next_word_bound = words.peek().copied();
20454 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20455 next_word_bound = words.next();
20456 }
20457 while let Some(grapheme) = iter.peek().copied() {
20458 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20459 break;
20460 };
20461 if is_grapheme_whitespace(grapheme) != is_whitespace
20462 || (grapheme == "\n") != is_newline
20463 {
20464 break;
20465 };
20466 offset += grapheme.len();
20467 grapheme_len += 1;
20468 iter.next();
20469 }
20470 }
20471 let token = &self.input[..offset];
20472 self.input = &self.input[offset..];
20473 if token == "\n" {
20474 Some(WordBreakToken::Newline)
20475 } else if is_whitespace {
20476 Some(WordBreakToken::InlineWhitespace {
20477 token,
20478 grapheme_len,
20479 })
20480 } else {
20481 Some(WordBreakToken::Word {
20482 token,
20483 grapheme_len,
20484 })
20485 }
20486 } else {
20487 None
20488 }
20489 }
20490}
20491
20492#[test]
20493fn test_word_breaking_tokenizer() {
20494 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20495 ("", &[]),
20496 (" ", &[whitespace(" ", 2)]),
20497 ("Ʒ", &[word("Ʒ", 1)]),
20498 ("Ǽ", &[word("Ǽ", 1)]),
20499 ("⋑", &[word("⋑", 1)]),
20500 ("⋑⋑", &[word("⋑⋑", 2)]),
20501 (
20502 "原理,进而",
20503 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20504 ),
20505 (
20506 "hello world",
20507 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20508 ),
20509 (
20510 "hello, world",
20511 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20512 ),
20513 (
20514 " hello world",
20515 &[
20516 whitespace(" ", 2),
20517 word("hello", 5),
20518 whitespace(" ", 1),
20519 word("world", 5),
20520 ],
20521 ),
20522 (
20523 "这是什么 \n 钢笔",
20524 &[
20525 word("这", 1),
20526 word("是", 1),
20527 word("什", 1),
20528 word("么", 1),
20529 whitespace(" ", 1),
20530 newline(),
20531 whitespace(" ", 1),
20532 word("钢", 1),
20533 word("笔", 1),
20534 ],
20535 ),
20536 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20537 ];
20538
20539 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20540 WordBreakToken::Word {
20541 token,
20542 grapheme_len,
20543 }
20544 }
20545
20546 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20547 WordBreakToken::InlineWhitespace {
20548 token,
20549 grapheme_len,
20550 }
20551 }
20552
20553 fn newline() -> WordBreakToken<'static> {
20554 WordBreakToken::Newline
20555 }
20556
20557 for (input, result) in tests {
20558 assert_eq!(
20559 WordBreakingTokenizer::new(input)
20560 .collect::<Vec<_>>()
20561 .as_slice(),
20562 *result,
20563 );
20564 }
20565}
20566
20567fn wrap_with_prefix(
20568 line_prefix: String,
20569 unwrapped_text: String,
20570 wrap_column: usize,
20571 tab_size: NonZeroU32,
20572 preserve_existing_whitespace: bool,
20573) -> String {
20574 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20575 let mut wrapped_text = String::new();
20576 let mut current_line = line_prefix.clone();
20577
20578 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20579 let mut current_line_len = line_prefix_len;
20580 let mut in_whitespace = false;
20581 for token in tokenizer {
20582 let have_preceding_whitespace = in_whitespace;
20583 match token {
20584 WordBreakToken::Word {
20585 token,
20586 grapheme_len,
20587 } => {
20588 in_whitespace = false;
20589 if current_line_len + grapheme_len > wrap_column
20590 && current_line_len != line_prefix_len
20591 {
20592 wrapped_text.push_str(current_line.trim_end());
20593 wrapped_text.push('\n');
20594 current_line.truncate(line_prefix.len());
20595 current_line_len = line_prefix_len;
20596 }
20597 current_line.push_str(token);
20598 current_line_len += grapheme_len;
20599 }
20600 WordBreakToken::InlineWhitespace {
20601 mut token,
20602 mut grapheme_len,
20603 } => {
20604 in_whitespace = true;
20605 if have_preceding_whitespace && !preserve_existing_whitespace {
20606 continue;
20607 }
20608 if !preserve_existing_whitespace {
20609 token = " ";
20610 grapheme_len = 1;
20611 }
20612 if current_line_len + grapheme_len > wrap_column {
20613 wrapped_text.push_str(current_line.trim_end());
20614 wrapped_text.push('\n');
20615 current_line.truncate(line_prefix.len());
20616 current_line_len = line_prefix_len;
20617 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20618 current_line.push_str(token);
20619 current_line_len += grapheme_len;
20620 }
20621 }
20622 WordBreakToken::Newline => {
20623 in_whitespace = true;
20624 if preserve_existing_whitespace {
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 have_preceding_whitespace {
20630 continue;
20631 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20632 {
20633 wrapped_text.push_str(current_line.trim_end());
20634 wrapped_text.push('\n');
20635 current_line.truncate(line_prefix.len());
20636 current_line_len = line_prefix_len;
20637 } else if current_line_len != line_prefix_len {
20638 current_line.push(' ');
20639 current_line_len += 1;
20640 }
20641 }
20642 }
20643 }
20644
20645 if !current_line.is_empty() {
20646 wrapped_text.push_str(¤t_line);
20647 }
20648 wrapped_text
20649}
20650
20651#[test]
20652fn test_wrap_with_prefix() {
20653 assert_eq!(
20654 wrap_with_prefix(
20655 "# ".to_string(),
20656 "abcdefg".to_string(),
20657 4,
20658 NonZeroU32::new(4).unwrap(),
20659 false,
20660 ),
20661 "# abcdefg"
20662 );
20663 assert_eq!(
20664 wrap_with_prefix(
20665 "".to_string(),
20666 "\thello world".to_string(),
20667 8,
20668 NonZeroU32::new(4).unwrap(),
20669 false,
20670 ),
20671 "hello\nworld"
20672 );
20673 assert_eq!(
20674 wrap_with_prefix(
20675 "// ".to_string(),
20676 "xx \nyy zz aa bb cc".to_string(),
20677 12,
20678 NonZeroU32::new(4).unwrap(),
20679 false,
20680 ),
20681 "// xx yy zz\n// aa bb cc"
20682 );
20683 assert_eq!(
20684 wrap_with_prefix(
20685 String::new(),
20686 "这是什么 \n 钢笔".to_string(),
20687 3,
20688 NonZeroU32::new(4).unwrap(),
20689 false,
20690 ),
20691 "这是什\n么 钢\n笔"
20692 );
20693}
20694
20695pub trait CollaborationHub {
20696 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20697 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20698 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20699}
20700
20701impl CollaborationHub for Entity<Project> {
20702 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20703 self.read(cx).collaborators()
20704 }
20705
20706 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20707 self.read(cx).user_store().read(cx).participant_indices()
20708 }
20709
20710 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20711 let this = self.read(cx);
20712 let user_ids = this.collaborators().values().map(|c| c.user_id);
20713 this.user_store().read(cx).participant_names(user_ids, cx)
20714 }
20715}
20716
20717pub trait SemanticsProvider {
20718 fn hover(
20719 &self,
20720 buffer: &Entity<Buffer>,
20721 position: text::Anchor,
20722 cx: &mut App,
20723 ) -> Option<Task<Vec<project::Hover>>>;
20724
20725 fn inline_values(
20726 &self,
20727 buffer_handle: Entity<Buffer>,
20728 range: Range<text::Anchor>,
20729 cx: &mut App,
20730 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20731
20732 fn inlay_hints(
20733 &self,
20734 buffer_handle: Entity<Buffer>,
20735 range: Range<text::Anchor>,
20736 cx: &mut App,
20737 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20738
20739 fn resolve_inlay_hint(
20740 &self,
20741 hint: InlayHint,
20742 buffer_handle: Entity<Buffer>,
20743 server_id: LanguageServerId,
20744 cx: &mut App,
20745 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20746
20747 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20748
20749 fn document_highlights(
20750 &self,
20751 buffer: &Entity<Buffer>,
20752 position: text::Anchor,
20753 cx: &mut App,
20754 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20755
20756 fn definitions(
20757 &self,
20758 buffer: &Entity<Buffer>,
20759 position: text::Anchor,
20760 kind: GotoDefinitionKind,
20761 cx: &mut App,
20762 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20763
20764 fn range_for_rename(
20765 &self,
20766 buffer: &Entity<Buffer>,
20767 position: text::Anchor,
20768 cx: &mut App,
20769 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20770
20771 fn perform_rename(
20772 &self,
20773 buffer: &Entity<Buffer>,
20774 position: text::Anchor,
20775 new_name: String,
20776 cx: &mut App,
20777 ) -> Option<Task<Result<ProjectTransaction>>>;
20778
20779 fn pull_diagnostics_for_buffer(
20780 &self,
20781 buffer: Entity<Buffer>,
20782 cx: &mut App,
20783 ) -> Task<anyhow::Result<()>>;
20784}
20785
20786pub trait CompletionProvider {
20787 fn completions(
20788 &self,
20789 excerpt_id: ExcerptId,
20790 buffer: &Entity<Buffer>,
20791 buffer_position: text::Anchor,
20792 trigger: CompletionContext,
20793 window: &mut Window,
20794 cx: &mut Context<Editor>,
20795 ) -> Task<Result<Vec<CompletionResponse>>>;
20796
20797 fn resolve_completions(
20798 &self,
20799 _buffer: Entity<Buffer>,
20800 _completion_indices: Vec<usize>,
20801 _completions: Rc<RefCell<Box<[Completion]>>>,
20802 _cx: &mut Context<Editor>,
20803 ) -> Task<Result<bool>> {
20804 Task::ready(Ok(false))
20805 }
20806
20807 fn apply_additional_edits_for_completion(
20808 &self,
20809 _buffer: Entity<Buffer>,
20810 _completions: Rc<RefCell<Box<[Completion]>>>,
20811 _completion_index: usize,
20812 _push_to_history: bool,
20813 _cx: &mut Context<Editor>,
20814 ) -> Task<Result<Option<language::Transaction>>> {
20815 Task::ready(Ok(None))
20816 }
20817
20818 fn is_completion_trigger(
20819 &self,
20820 buffer: &Entity<Buffer>,
20821 position: language::Anchor,
20822 text: &str,
20823 trigger_in_words: bool,
20824 menu_is_open: bool,
20825 cx: &mut Context<Editor>,
20826 ) -> bool;
20827
20828 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20829
20830 fn sort_completions(&self) -> bool {
20831 true
20832 }
20833
20834 fn filter_completions(&self) -> bool {
20835 true
20836 }
20837}
20838
20839pub trait CodeActionProvider {
20840 fn id(&self) -> Arc<str>;
20841
20842 fn code_actions(
20843 &self,
20844 buffer: &Entity<Buffer>,
20845 range: Range<text::Anchor>,
20846 window: &mut Window,
20847 cx: &mut App,
20848 ) -> Task<Result<Vec<CodeAction>>>;
20849
20850 fn apply_code_action(
20851 &self,
20852 buffer_handle: Entity<Buffer>,
20853 action: CodeAction,
20854 excerpt_id: ExcerptId,
20855 push_to_history: bool,
20856 window: &mut Window,
20857 cx: &mut App,
20858 ) -> Task<Result<ProjectTransaction>>;
20859}
20860
20861impl CodeActionProvider for Entity<Project> {
20862 fn id(&self) -> Arc<str> {
20863 "project".into()
20864 }
20865
20866 fn code_actions(
20867 &self,
20868 buffer: &Entity<Buffer>,
20869 range: Range<text::Anchor>,
20870 _window: &mut Window,
20871 cx: &mut App,
20872 ) -> Task<Result<Vec<CodeAction>>> {
20873 self.update(cx, |project, cx| {
20874 let code_lens = project.code_lens(buffer, range.clone(), cx);
20875 let code_actions = project.code_actions(buffer, range, None, cx);
20876 cx.background_spawn(async move {
20877 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20878 Ok(code_lens
20879 .context("code lens fetch")?
20880 .into_iter()
20881 .chain(code_actions.context("code action fetch")?)
20882 .collect())
20883 })
20884 })
20885 }
20886
20887 fn apply_code_action(
20888 &self,
20889 buffer_handle: Entity<Buffer>,
20890 action: CodeAction,
20891 _excerpt_id: ExcerptId,
20892 push_to_history: bool,
20893 _window: &mut Window,
20894 cx: &mut App,
20895 ) -> Task<Result<ProjectTransaction>> {
20896 self.update(cx, |project, cx| {
20897 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20898 })
20899 }
20900}
20901
20902fn snippet_completions(
20903 project: &Project,
20904 buffer: &Entity<Buffer>,
20905 buffer_position: text::Anchor,
20906 cx: &mut App,
20907) -> Task<Result<CompletionResponse>> {
20908 let languages = buffer.read(cx).languages_at(buffer_position);
20909 let snippet_store = project.snippets().read(cx);
20910
20911 let scopes: Vec<_> = languages
20912 .iter()
20913 .filter_map(|language| {
20914 let language_name = language.lsp_id();
20915 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20916
20917 if snippets.is_empty() {
20918 None
20919 } else {
20920 Some((language.default_scope(), snippets))
20921 }
20922 })
20923 .collect();
20924
20925 if scopes.is_empty() {
20926 return Task::ready(Ok(CompletionResponse {
20927 completions: vec![],
20928 is_incomplete: false,
20929 }));
20930 }
20931
20932 let snapshot = buffer.read(cx).text_snapshot();
20933 let chars: String = snapshot
20934 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20935 .collect();
20936 let executor = cx.background_executor().clone();
20937
20938 cx.background_spawn(async move {
20939 let mut is_incomplete = false;
20940 let mut completions: Vec<Completion> = Vec::new();
20941 for (scope, snippets) in scopes.into_iter() {
20942 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20943 let mut last_word = chars
20944 .chars()
20945 .take_while(|c| classifier.is_word(*c))
20946 .collect::<String>();
20947 last_word = last_word.chars().rev().collect();
20948
20949 if last_word.is_empty() {
20950 return Ok(CompletionResponse {
20951 completions: vec![],
20952 is_incomplete: true,
20953 });
20954 }
20955
20956 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20957 let to_lsp = |point: &text::Anchor| {
20958 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20959 point_to_lsp(end)
20960 };
20961 let lsp_end = to_lsp(&buffer_position);
20962
20963 let candidates = snippets
20964 .iter()
20965 .enumerate()
20966 .flat_map(|(ix, snippet)| {
20967 snippet
20968 .prefix
20969 .iter()
20970 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20971 })
20972 .collect::<Vec<StringMatchCandidate>>();
20973
20974 const MAX_RESULTS: usize = 100;
20975 let mut matches = fuzzy::match_strings(
20976 &candidates,
20977 &last_word,
20978 last_word.chars().any(|c| c.is_uppercase()),
20979 MAX_RESULTS,
20980 &Default::default(),
20981 executor.clone(),
20982 )
20983 .await;
20984
20985 if matches.len() >= MAX_RESULTS {
20986 is_incomplete = true;
20987 }
20988
20989 // Remove all candidates where the query's start does not match the start of any word in the candidate
20990 if let Some(query_start) = last_word.chars().next() {
20991 matches.retain(|string_match| {
20992 split_words(&string_match.string).any(|word| {
20993 // Check that the first codepoint of the word as lowercase matches the first
20994 // codepoint of the query as lowercase
20995 word.chars()
20996 .flat_map(|codepoint| codepoint.to_lowercase())
20997 .zip(query_start.to_lowercase())
20998 .all(|(word_cp, query_cp)| word_cp == query_cp)
20999 })
21000 });
21001 }
21002
21003 let matched_strings = matches
21004 .into_iter()
21005 .map(|m| m.string)
21006 .collect::<HashSet<_>>();
21007
21008 completions.extend(snippets.iter().filter_map(|snippet| {
21009 let matching_prefix = snippet
21010 .prefix
21011 .iter()
21012 .find(|prefix| matched_strings.contains(*prefix))?;
21013 let start = as_offset - last_word.len();
21014 let start = snapshot.anchor_before(start);
21015 let range = start..buffer_position;
21016 let lsp_start = to_lsp(&start);
21017 let lsp_range = lsp::Range {
21018 start: lsp_start,
21019 end: lsp_end,
21020 };
21021 Some(Completion {
21022 replace_range: range,
21023 new_text: snippet.body.clone(),
21024 source: CompletionSource::Lsp {
21025 insert_range: None,
21026 server_id: LanguageServerId(usize::MAX),
21027 resolved: true,
21028 lsp_completion: Box::new(lsp::CompletionItem {
21029 label: snippet.prefix.first().unwrap().clone(),
21030 kind: Some(CompletionItemKind::SNIPPET),
21031 label_details: snippet.description.as_ref().map(|description| {
21032 lsp::CompletionItemLabelDetails {
21033 detail: Some(description.clone()),
21034 description: None,
21035 }
21036 }),
21037 insert_text_format: Some(InsertTextFormat::SNIPPET),
21038 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21039 lsp::InsertReplaceEdit {
21040 new_text: snippet.body.clone(),
21041 insert: lsp_range,
21042 replace: lsp_range,
21043 },
21044 )),
21045 filter_text: Some(snippet.body.clone()),
21046 sort_text: Some(char::MAX.to_string()),
21047 ..lsp::CompletionItem::default()
21048 }),
21049 lsp_defaults: None,
21050 },
21051 label: CodeLabel {
21052 text: matching_prefix.clone(),
21053 runs: Vec::new(),
21054 filter_range: 0..matching_prefix.len(),
21055 },
21056 icon_path: None,
21057 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21058 single_line: snippet.name.clone().into(),
21059 plain_text: snippet
21060 .description
21061 .clone()
21062 .map(|description| description.into()),
21063 }),
21064 insert_text_mode: None,
21065 confirm: None,
21066 })
21067 }))
21068 }
21069
21070 Ok(CompletionResponse {
21071 completions,
21072 is_incomplete,
21073 })
21074 })
21075}
21076
21077impl CompletionProvider for Entity<Project> {
21078 fn completions(
21079 &self,
21080 _excerpt_id: ExcerptId,
21081 buffer: &Entity<Buffer>,
21082 buffer_position: text::Anchor,
21083 options: CompletionContext,
21084 _window: &mut Window,
21085 cx: &mut Context<Editor>,
21086 ) -> Task<Result<Vec<CompletionResponse>>> {
21087 self.update(cx, |project, cx| {
21088 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21089 let project_completions = project.completions(buffer, buffer_position, options, cx);
21090 cx.background_spawn(async move {
21091 let mut responses = project_completions.await?;
21092 let snippets = snippets.await?;
21093 if !snippets.completions.is_empty() {
21094 responses.push(snippets);
21095 }
21096 Ok(responses)
21097 })
21098 })
21099 }
21100
21101 fn resolve_completions(
21102 &self,
21103 buffer: Entity<Buffer>,
21104 completion_indices: Vec<usize>,
21105 completions: Rc<RefCell<Box<[Completion]>>>,
21106 cx: &mut Context<Editor>,
21107 ) -> Task<Result<bool>> {
21108 self.update(cx, |project, cx| {
21109 project.lsp_store().update(cx, |lsp_store, cx| {
21110 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21111 })
21112 })
21113 }
21114
21115 fn apply_additional_edits_for_completion(
21116 &self,
21117 buffer: Entity<Buffer>,
21118 completions: Rc<RefCell<Box<[Completion]>>>,
21119 completion_index: usize,
21120 push_to_history: bool,
21121 cx: &mut Context<Editor>,
21122 ) -> Task<Result<Option<language::Transaction>>> {
21123 self.update(cx, |project, cx| {
21124 project.lsp_store().update(cx, |lsp_store, cx| {
21125 lsp_store.apply_additional_edits_for_completion(
21126 buffer,
21127 completions,
21128 completion_index,
21129 push_to_history,
21130 cx,
21131 )
21132 })
21133 })
21134 }
21135
21136 fn is_completion_trigger(
21137 &self,
21138 buffer: &Entity<Buffer>,
21139 position: language::Anchor,
21140 text: &str,
21141 trigger_in_words: bool,
21142 menu_is_open: bool,
21143 cx: &mut Context<Editor>,
21144 ) -> bool {
21145 let mut chars = text.chars();
21146 let char = if let Some(char) = chars.next() {
21147 char
21148 } else {
21149 return false;
21150 };
21151 if chars.next().is_some() {
21152 return false;
21153 }
21154
21155 let buffer = buffer.read(cx);
21156 let snapshot = buffer.snapshot();
21157 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21158 return false;
21159 }
21160 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21161 if trigger_in_words && classifier.is_word(char) {
21162 return true;
21163 }
21164
21165 buffer.completion_triggers().contains(text)
21166 }
21167}
21168
21169impl SemanticsProvider for Entity<Project> {
21170 fn hover(
21171 &self,
21172 buffer: &Entity<Buffer>,
21173 position: text::Anchor,
21174 cx: &mut App,
21175 ) -> Option<Task<Vec<project::Hover>>> {
21176 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21177 }
21178
21179 fn document_highlights(
21180 &self,
21181 buffer: &Entity<Buffer>,
21182 position: text::Anchor,
21183 cx: &mut App,
21184 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21185 Some(self.update(cx, |project, cx| {
21186 project.document_highlights(buffer, position, cx)
21187 }))
21188 }
21189
21190 fn definitions(
21191 &self,
21192 buffer: &Entity<Buffer>,
21193 position: text::Anchor,
21194 kind: GotoDefinitionKind,
21195 cx: &mut App,
21196 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21197 Some(self.update(cx, |project, cx| match kind {
21198 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21199 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21200 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21201 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21202 }))
21203 }
21204
21205 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21206 // TODO: make this work for remote projects
21207 self.update(cx, |project, cx| {
21208 if project
21209 .active_debug_session(cx)
21210 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21211 {
21212 return true;
21213 }
21214
21215 buffer.update(cx, |buffer, cx| {
21216 project.any_language_server_supports_inlay_hints(buffer, cx)
21217 })
21218 })
21219 }
21220
21221 fn inline_values(
21222 &self,
21223 buffer_handle: Entity<Buffer>,
21224
21225 range: Range<text::Anchor>,
21226 cx: &mut App,
21227 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21228 self.update(cx, |project, cx| {
21229 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21230
21231 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21232 })
21233 }
21234
21235 fn inlay_hints(
21236 &self,
21237 buffer_handle: Entity<Buffer>,
21238 range: Range<text::Anchor>,
21239 cx: &mut App,
21240 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21241 Some(self.update(cx, |project, cx| {
21242 project.inlay_hints(buffer_handle, range, cx)
21243 }))
21244 }
21245
21246 fn resolve_inlay_hint(
21247 &self,
21248 hint: InlayHint,
21249 buffer_handle: Entity<Buffer>,
21250 server_id: LanguageServerId,
21251 cx: &mut App,
21252 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21253 Some(self.update(cx, |project, cx| {
21254 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21255 }))
21256 }
21257
21258 fn range_for_rename(
21259 &self,
21260 buffer: &Entity<Buffer>,
21261 position: text::Anchor,
21262 cx: &mut App,
21263 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21264 Some(self.update(cx, |project, cx| {
21265 let buffer = buffer.clone();
21266 let task = project.prepare_rename(buffer.clone(), position, cx);
21267 cx.spawn(async move |_, cx| {
21268 Ok(match task.await? {
21269 PrepareRenameResponse::Success(range) => Some(range),
21270 PrepareRenameResponse::InvalidPosition => None,
21271 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21272 // Fallback on using TreeSitter info to determine identifier range
21273 buffer.read_with(cx, |buffer, _| {
21274 let snapshot = buffer.snapshot();
21275 let (range, kind) = snapshot.surrounding_word(position);
21276 if kind != Some(CharKind::Word) {
21277 return None;
21278 }
21279 Some(
21280 snapshot.anchor_before(range.start)
21281 ..snapshot.anchor_after(range.end),
21282 )
21283 })?
21284 }
21285 })
21286 })
21287 }))
21288 }
21289
21290 fn perform_rename(
21291 &self,
21292 buffer: &Entity<Buffer>,
21293 position: text::Anchor,
21294 new_name: String,
21295 cx: &mut App,
21296 ) -> Option<Task<Result<ProjectTransaction>>> {
21297 Some(self.update(cx, |project, cx| {
21298 project.perform_rename(buffer.clone(), position, new_name, cx)
21299 }))
21300 }
21301
21302 fn pull_diagnostics_for_buffer(
21303 &self,
21304 buffer: Entity<Buffer>,
21305 cx: &mut App,
21306 ) -> Task<anyhow::Result<()>> {
21307 let diagnostics = self.update(cx, |project, cx| {
21308 project
21309 .lsp_store()
21310 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21311 });
21312 let project = self.clone();
21313 cx.spawn(async move |cx| {
21314 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21315 project.update(cx, |project, cx| {
21316 project.lsp_store().update(cx, |lsp_store, cx| {
21317 for diagnostics_set in diagnostics {
21318 let LspPullDiagnostics::Response {
21319 server_id,
21320 uri,
21321 diagnostics,
21322 } = diagnostics_set
21323 else {
21324 continue;
21325 };
21326
21327 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21328 let disk_based_sources = adapter
21329 .as_ref()
21330 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21331 .unwrap_or(&[]);
21332 match diagnostics {
21333 PulledDiagnostics::Unchanged { result_id } => {
21334 lsp_store
21335 .merge_diagnostics(
21336 server_id,
21337 lsp::PublishDiagnosticsParams {
21338 uri: uri.clone(),
21339 diagnostics: Vec::new(),
21340 version: None,
21341 },
21342 Some(result_id),
21343 DiagnosticSourceKind::Pulled,
21344 disk_based_sources,
21345 |_, _| true,
21346 cx,
21347 )
21348 .log_err();
21349 }
21350 PulledDiagnostics::Changed {
21351 diagnostics,
21352 result_id,
21353 } => {
21354 lsp_store
21355 .merge_diagnostics(
21356 server_id,
21357 lsp::PublishDiagnosticsParams {
21358 uri: uri.clone(),
21359 diagnostics,
21360 version: None,
21361 },
21362 result_id,
21363 DiagnosticSourceKind::Pulled,
21364 disk_based_sources,
21365 |old_diagnostic, _| match old_diagnostic.source_kind {
21366 DiagnosticSourceKind::Pulled => false,
21367 DiagnosticSourceKind::Other
21368 | DiagnosticSourceKind::Pushed => true,
21369 },
21370 cx,
21371 )
21372 .log_err();
21373 }
21374 }
21375 }
21376 })
21377 })
21378 })
21379 }
21380}
21381
21382fn inlay_hint_settings(
21383 location: Anchor,
21384 snapshot: &MultiBufferSnapshot,
21385 cx: &mut Context<Editor>,
21386) -> InlayHintSettings {
21387 let file = snapshot.file_at(location);
21388 let language = snapshot.language_at(location).map(|l| l.name());
21389 language_settings(language, file, cx).inlay_hints
21390}
21391
21392fn consume_contiguous_rows(
21393 contiguous_row_selections: &mut Vec<Selection<Point>>,
21394 selection: &Selection<Point>,
21395 display_map: &DisplaySnapshot,
21396 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21397) -> (MultiBufferRow, MultiBufferRow) {
21398 contiguous_row_selections.push(selection.clone());
21399 let start_row = MultiBufferRow(selection.start.row);
21400 let mut end_row = ending_row(selection, display_map);
21401
21402 while let Some(next_selection) = selections.peek() {
21403 if next_selection.start.row <= end_row.0 {
21404 end_row = ending_row(next_selection, display_map);
21405 contiguous_row_selections.push(selections.next().unwrap().clone());
21406 } else {
21407 break;
21408 }
21409 }
21410 (start_row, end_row)
21411}
21412
21413fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21414 if next_selection.end.column > 0 || next_selection.is_empty() {
21415 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21416 } else {
21417 MultiBufferRow(next_selection.end.row)
21418 }
21419}
21420
21421impl EditorSnapshot {
21422 pub fn remote_selections_in_range<'a>(
21423 &'a self,
21424 range: &'a Range<Anchor>,
21425 collaboration_hub: &dyn CollaborationHub,
21426 cx: &'a App,
21427 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21428 let participant_names = collaboration_hub.user_names(cx);
21429 let participant_indices = collaboration_hub.user_participant_indices(cx);
21430 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21431 let collaborators_by_replica_id = collaborators_by_peer_id
21432 .values()
21433 .map(|collaborator| (collaborator.replica_id, collaborator))
21434 .collect::<HashMap<_, _>>();
21435 self.buffer_snapshot
21436 .selections_in_range(range, false)
21437 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21438 if replica_id == AGENT_REPLICA_ID {
21439 Some(RemoteSelection {
21440 replica_id,
21441 selection,
21442 cursor_shape,
21443 line_mode,
21444 collaborator_id: CollaboratorId::Agent,
21445 user_name: Some("Agent".into()),
21446 color: cx.theme().players().agent(),
21447 })
21448 } else {
21449 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21450 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21451 let user_name = participant_names.get(&collaborator.user_id).cloned();
21452 Some(RemoteSelection {
21453 replica_id,
21454 selection,
21455 cursor_shape,
21456 line_mode,
21457 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21458 user_name,
21459 color: if let Some(index) = participant_index {
21460 cx.theme().players().color_for_participant(index.0)
21461 } else {
21462 cx.theme().players().absent()
21463 },
21464 })
21465 }
21466 })
21467 }
21468
21469 pub fn hunks_for_ranges(
21470 &self,
21471 ranges: impl IntoIterator<Item = Range<Point>>,
21472 ) -> Vec<MultiBufferDiffHunk> {
21473 let mut hunks = Vec::new();
21474 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21475 HashMap::default();
21476 for query_range in ranges {
21477 let query_rows =
21478 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21479 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21480 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21481 ) {
21482 // Include deleted hunks that are adjacent to the query range, because
21483 // otherwise they would be missed.
21484 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21485 if hunk.status().is_deleted() {
21486 intersects_range |= hunk.row_range.start == query_rows.end;
21487 intersects_range |= hunk.row_range.end == query_rows.start;
21488 }
21489 if intersects_range {
21490 if !processed_buffer_rows
21491 .entry(hunk.buffer_id)
21492 .or_default()
21493 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21494 {
21495 continue;
21496 }
21497 hunks.push(hunk);
21498 }
21499 }
21500 }
21501
21502 hunks
21503 }
21504
21505 fn display_diff_hunks_for_rows<'a>(
21506 &'a self,
21507 display_rows: Range<DisplayRow>,
21508 folded_buffers: &'a HashSet<BufferId>,
21509 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21510 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21511 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21512
21513 self.buffer_snapshot
21514 .diff_hunks_in_range(buffer_start..buffer_end)
21515 .filter_map(|hunk| {
21516 if folded_buffers.contains(&hunk.buffer_id) {
21517 return None;
21518 }
21519
21520 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21521 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21522
21523 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21524 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21525
21526 let display_hunk = if hunk_display_start.column() != 0 {
21527 DisplayDiffHunk::Folded {
21528 display_row: hunk_display_start.row(),
21529 }
21530 } else {
21531 let mut end_row = hunk_display_end.row();
21532 if hunk_display_end.column() > 0 {
21533 end_row.0 += 1;
21534 }
21535 let is_created_file = hunk.is_created_file();
21536 DisplayDiffHunk::Unfolded {
21537 status: hunk.status(),
21538 diff_base_byte_range: hunk.diff_base_byte_range,
21539 display_row_range: hunk_display_start.row()..end_row,
21540 multi_buffer_range: Anchor::range_in_buffer(
21541 hunk.excerpt_id,
21542 hunk.buffer_id,
21543 hunk.buffer_range,
21544 ),
21545 is_created_file,
21546 }
21547 };
21548
21549 Some(display_hunk)
21550 })
21551 }
21552
21553 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21554 self.display_snapshot.buffer_snapshot.language_at(position)
21555 }
21556
21557 pub fn is_focused(&self) -> bool {
21558 self.is_focused
21559 }
21560
21561 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21562 self.placeholder_text.as_ref()
21563 }
21564
21565 pub fn scroll_position(&self) -> gpui::Point<f32> {
21566 self.scroll_anchor.scroll_position(&self.display_snapshot)
21567 }
21568
21569 fn gutter_dimensions(
21570 &self,
21571 font_id: FontId,
21572 font_size: Pixels,
21573 max_line_number_width: Pixels,
21574 cx: &App,
21575 ) -> Option<GutterDimensions> {
21576 if !self.show_gutter {
21577 return None;
21578 }
21579
21580 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21581 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21582
21583 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21584 matches!(
21585 ProjectSettings::get_global(cx).git.git_gutter,
21586 Some(GitGutterSetting::TrackedFiles)
21587 )
21588 });
21589 let gutter_settings = EditorSettings::get_global(cx).gutter;
21590 let show_line_numbers = self
21591 .show_line_numbers
21592 .unwrap_or(gutter_settings.line_numbers);
21593 let line_gutter_width = if show_line_numbers {
21594 // Avoid flicker-like gutter resizes when the line number gains another digit by
21595 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21596 let min_width_for_number_on_gutter =
21597 ch_advance * gutter_settings.min_line_number_digits as f32;
21598 max_line_number_width.max(min_width_for_number_on_gutter)
21599 } else {
21600 0.0.into()
21601 };
21602
21603 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21604 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21605
21606 let git_blame_entries_width =
21607 self.git_blame_gutter_max_author_length
21608 .map(|max_author_length| {
21609 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21610 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21611
21612 /// The number of characters to dedicate to gaps and margins.
21613 const SPACING_WIDTH: usize = 4;
21614
21615 let max_char_count = max_author_length.min(renderer.max_author_length())
21616 + ::git::SHORT_SHA_LENGTH
21617 + MAX_RELATIVE_TIMESTAMP.len()
21618 + SPACING_WIDTH;
21619
21620 ch_advance * max_char_count
21621 });
21622
21623 let is_singleton = self.buffer_snapshot.is_singleton();
21624
21625 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21626 left_padding += if !is_singleton {
21627 ch_width * 4.0
21628 } else if show_runnables || show_breakpoints {
21629 ch_width * 3.0
21630 } else if show_git_gutter && show_line_numbers {
21631 ch_width * 2.0
21632 } else if show_git_gutter || show_line_numbers {
21633 ch_width
21634 } else {
21635 px(0.)
21636 };
21637
21638 let shows_folds = is_singleton && gutter_settings.folds;
21639
21640 let right_padding = if shows_folds && show_line_numbers {
21641 ch_width * 4.0
21642 } else if shows_folds || (!is_singleton && show_line_numbers) {
21643 ch_width * 3.0
21644 } else if show_line_numbers {
21645 ch_width
21646 } else {
21647 px(0.)
21648 };
21649
21650 Some(GutterDimensions {
21651 left_padding,
21652 right_padding,
21653 width: line_gutter_width + left_padding + right_padding,
21654 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21655 git_blame_entries_width,
21656 })
21657 }
21658
21659 pub fn render_crease_toggle(
21660 &self,
21661 buffer_row: MultiBufferRow,
21662 row_contains_cursor: bool,
21663 editor: Entity<Editor>,
21664 window: &mut Window,
21665 cx: &mut App,
21666 ) -> Option<AnyElement> {
21667 let folded = self.is_line_folded(buffer_row);
21668 let mut is_foldable = false;
21669
21670 if let Some(crease) = self
21671 .crease_snapshot
21672 .query_row(buffer_row, &self.buffer_snapshot)
21673 {
21674 is_foldable = true;
21675 match crease {
21676 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21677 if let Some(render_toggle) = render_toggle {
21678 let toggle_callback =
21679 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21680 if folded {
21681 editor.update(cx, |editor, cx| {
21682 editor.fold_at(buffer_row, window, cx)
21683 });
21684 } else {
21685 editor.update(cx, |editor, cx| {
21686 editor.unfold_at(buffer_row, window, cx)
21687 });
21688 }
21689 });
21690 return Some((render_toggle)(
21691 buffer_row,
21692 folded,
21693 toggle_callback,
21694 window,
21695 cx,
21696 ));
21697 }
21698 }
21699 }
21700 }
21701
21702 is_foldable |= self.starts_indent(buffer_row);
21703
21704 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21705 Some(
21706 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21707 .toggle_state(folded)
21708 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21709 if folded {
21710 this.unfold_at(buffer_row, window, cx);
21711 } else {
21712 this.fold_at(buffer_row, window, cx);
21713 }
21714 }))
21715 .into_any_element(),
21716 )
21717 } else {
21718 None
21719 }
21720 }
21721
21722 pub fn render_crease_trailer(
21723 &self,
21724 buffer_row: MultiBufferRow,
21725 window: &mut Window,
21726 cx: &mut App,
21727 ) -> Option<AnyElement> {
21728 let folded = self.is_line_folded(buffer_row);
21729 if let Crease::Inline { render_trailer, .. } = self
21730 .crease_snapshot
21731 .query_row(buffer_row, &self.buffer_snapshot)?
21732 {
21733 let render_trailer = render_trailer.as_ref()?;
21734 Some(render_trailer(buffer_row, folded, window, cx))
21735 } else {
21736 None
21737 }
21738 }
21739}
21740
21741impl Deref for EditorSnapshot {
21742 type Target = DisplaySnapshot;
21743
21744 fn deref(&self) -> &Self::Target {
21745 &self.display_snapshot
21746 }
21747}
21748
21749#[derive(Clone, Debug, PartialEq, Eq)]
21750pub enum EditorEvent {
21751 InputIgnored {
21752 text: Arc<str>,
21753 },
21754 InputHandled {
21755 utf16_range_to_replace: Option<Range<isize>>,
21756 text: Arc<str>,
21757 },
21758 ExcerptsAdded {
21759 buffer: Entity<Buffer>,
21760 predecessor: ExcerptId,
21761 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21762 },
21763 ExcerptsRemoved {
21764 ids: Vec<ExcerptId>,
21765 removed_buffer_ids: Vec<BufferId>,
21766 },
21767 BufferFoldToggled {
21768 ids: Vec<ExcerptId>,
21769 folded: bool,
21770 },
21771 ExcerptsEdited {
21772 ids: Vec<ExcerptId>,
21773 },
21774 ExcerptsExpanded {
21775 ids: Vec<ExcerptId>,
21776 },
21777 BufferEdited,
21778 Edited {
21779 transaction_id: clock::Lamport,
21780 },
21781 Reparsed(BufferId),
21782 Focused,
21783 FocusedIn,
21784 Blurred,
21785 DirtyChanged,
21786 Saved,
21787 TitleChanged,
21788 DiffBaseChanged,
21789 SelectionsChanged {
21790 local: bool,
21791 },
21792 ScrollPositionChanged {
21793 local: bool,
21794 autoscroll: bool,
21795 },
21796 Closed,
21797 TransactionUndone {
21798 transaction_id: clock::Lamport,
21799 },
21800 TransactionBegun {
21801 transaction_id: clock::Lamport,
21802 },
21803 Reloaded,
21804 CursorShapeChanged,
21805 PushedToNavHistory {
21806 anchor: Anchor,
21807 is_deactivate: bool,
21808 },
21809}
21810
21811impl EventEmitter<EditorEvent> for Editor {}
21812
21813impl Focusable for Editor {
21814 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21815 self.focus_handle.clone()
21816 }
21817}
21818
21819impl Render for Editor {
21820 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21821 let settings = ThemeSettings::get_global(cx);
21822
21823 let mut text_style = match self.mode {
21824 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21825 color: cx.theme().colors().editor_foreground,
21826 font_family: settings.ui_font.family.clone(),
21827 font_features: settings.ui_font.features.clone(),
21828 font_fallbacks: settings.ui_font.fallbacks.clone(),
21829 font_size: rems(0.875).into(),
21830 font_weight: settings.ui_font.weight,
21831 line_height: relative(settings.buffer_line_height.value()),
21832 ..Default::default()
21833 },
21834 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21835 color: cx.theme().colors().editor_foreground,
21836 font_family: settings.buffer_font.family.clone(),
21837 font_features: settings.buffer_font.features.clone(),
21838 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21839 font_size: settings.buffer_font_size(cx).into(),
21840 font_weight: settings.buffer_font.weight,
21841 line_height: relative(settings.buffer_line_height.value()),
21842 ..Default::default()
21843 },
21844 };
21845 if let Some(text_style_refinement) = &self.text_style_refinement {
21846 text_style.refine(text_style_refinement)
21847 }
21848
21849 let background = match self.mode {
21850 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21851 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21852 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21853 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21854 };
21855
21856 EditorElement::new(
21857 &cx.entity(),
21858 EditorStyle {
21859 background,
21860 local_player: cx.theme().players().local(),
21861 text: text_style,
21862 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21863 syntax: cx.theme().syntax().clone(),
21864 status: cx.theme().status().clone(),
21865 inlay_hints_style: make_inlay_hints_style(cx),
21866 inline_completion_styles: make_suggestion_styles(cx),
21867 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21868 show_underlines: !self.mode.is_minimap(),
21869 },
21870 )
21871 }
21872}
21873
21874impl EntityInputHandler for Editor {
21875 fn text_for_range(
21876 &mut self,
21877 range_utf16: Range<usize>,
21878 adjusted_range: &mut Option<Range<usize>>,
21879 _: &mut Window,
21880 cx: &mut Context<Self>,
21881 ) -> Option<String> {
21882 let snapshot = self.buffer.read(cx).read(cx);
21883 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21884 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21885 if (start.0..end.0) != range_utf16 {
21886 adjusted_range.replace(start.0..end.0);
21887 }
21888 Some(snapshot.text_for_range(start..end).collect())
21889 }
21890
21891 fn selected_text_range(
21892 &mut self,
21893 ignore_disabled_input: bool,
21894 _: &mut Window,
21895 cx: &mut Context<Self>,
21896 ) -> Option<UTF16Selection> {
21897 // Prevent the IME menu from appearing when holding down an alphabetic key
21898 // while input is disabled.
21899 if !ignore_disabled_input && !self.input_enabled {
21900 return None;
21901 }
21902
21903 let selection = self.selections.newest::<OffsetUtf16>(cx);
21904 let range = selection.range();
21905
21906 Some(UTF16Selection {
21907 range: range.start.0..range.end.0,
21908 reversed: selection.reversed,
21909 })
21910 }
21911
21912 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21913 let snapshot = self.buffer.read(cx).read(cx);
21914 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21915 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21916 }
21917
21918 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21919 self.clear_highlights::<InputComposition>(cx);
21920 self.ime_transaction.take();
21921 }
21922
21923 fn replace_text_in_range(
21924 &mut self,
21925 range_utf16: Option<Range<usize>>,
21926 text: &str,
21927 window: &mut Window,
21928 cx: &mut Context<Self>,
21929 ) {
21930 if !self.input_enabled {
21931 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21932 return;
21933 }
21934
21935 self.transact(window, cx, |this, window, cx| {
21936 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21937 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21938 Some(this.selection_replacement_ranges(range_utf16, cx))
21939 } else {
21940 this.marked_text_ranges(cx)
21941 };
21942
21943 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21944 let newest_selection_id = this.selections.newest_anchor().id;
21945 this.selections
21946 .all::<OffsetUtf16>(cx)
21947 .iter()
21948 .zip(ranges_to_replace.iter())
21949 .find_map(|(selection, range)| {
21950 if selection.id == newest_selection_id {
21951 Some(
21952 (range.start.0 as isize - selection.head().0 as isize)
21953 ..(range.end.0 as isize - selection.head().0 as isize),
21954 )
21955 } else {
21956 None
21957 }
21958 })
21959 });
21960
21961 cx.emit(EditorEvent::InputHandled {
21962 utf16_range_to_replace: range_to_replace,
21963 text: text.into(),
21964 });
21965
21966 if let Some(new_selected_ranges) = new_selected_ranges {
21967 this.change_selections(None, window, cx, |selections| {
21968 selections.select_ranges(new_selected_ranges)
21969 });
21970 this.backspace(&Default::default(), window, cx);
21971 }
21972
21973 this.handle_input(text, window, cx);
21974 });
21975
21976 if let Some(transaction) = self.ime_transaction {
21977 self.buffer.update(cx, |buffer, cx| {
21978 buffer.group_until_transaction(transaction, cx);
21979 });
21980 }
21981
21982 self.unmark_text(window, cx);
21983 }
21984
21985 fn replace_and_mark_text_in_range(
21986 &mut self,
21987 range_utf16: Option<Range<usize>>,
21988 text: &str,
21989 new_selected_range_utf16: Option<Range<usize>>,
21990 window: &mut Window,
21991 cx: &mut Context<Self>,
21992 ) {
21993 if !self.input_enabled {
21994 return;
21995 }
21996
21997 let transaction = self.transact(window, cx, |this, window, cx| {
21998 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21999 let snapshot = this.buffer.read(cx).read(cx);
22000 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22001 for marked_range in &mut marked_ranges {
22002 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22003 marked_range.start.0 += relative_range_utf16.start;
22004 marked_range.start =
22005 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22006 marked_range.end =
22007 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22008 }
22009 }
22010 Some(marked_ranges)
22011 } else if let Some(range_utf16) = range_utf16 {
22012 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22013 Some(this.selection_replacement_ranges(range_utf16, cx))
22014 } else {
22015 None
22016 };
22017
22018 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22019 let newest_selection_id = this.selections.newest_anchor().id;
22020 this.selections
22021 .all::<OffsetUtf16>(cx)
22022 .iter()
22023 .zip(ranges_to_replace.iter())
22024 .find_map(|(selection, range)| {
22025 if selection.id == newest_selection_id {
22026 Some(
22027 (range.start.0 as isize - selection.head().0 as isize)
22028 ..(range.end.0 as isize - selection.head().0 as isize),
22029 )
22030 } else {
22031 None
22032 }
22033 })
22034 });
22035
22036 cx.emit(EditorEvent::InputHandled {
22037 utf16_range_to_replace: range_to_replace,
22038 text: text.into(),
22039 });
22040
22041 if let Some(ranges) = ranges_to_replace {
22042 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22043 }
22044
22045 let marked_ranges = {
22046 let snapshot = this.buffer.read(cx).read(cx);
22047 this.selections
22048 .disjoint_anchors()
22049 .iter()
22050 .map(|selection| {
22051 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22052 })
22053 .collect::<Vec<_>>()
22054 };
22055
22056 if text.is_empty() {
22057 this.unmark_text(window, cx);
22058 } else {
22059 this.highlight_text::<InputComposition>(
22060 marked_ranges.clone(),
22061 HighlightStyle {
22062 underline: Some(UnderlineStyle {
22063 thickness: px(1.),
22064 color: None,
22065 wavy: false,
22066 }),
22067 ..Default::default()
22068 },
22069 cx,
22070 );
22071 }
22072
22073 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22074 let use_autoclose = this.use_autoclose;
22075 let use_auto_surround = this.use_auto_surround;
22076 this.set_use_autoclose(false);
22077 this.set_use_auto_surround(false);
22078 this.handle_input(text, window, cx);
22079 this.set_use_autoclose(use_autoclose);
22080 this.set_use_auto_surround(use_auto_surround);
22081
22082 if let Some(new_selected_range) = new_selected_range_utf16 {
22083 let snapshot = this.buffer.read(cx).read(cx);
22084 let new_selected_ranges = marked_ranges
22085 .into_iter()
22086 .map(|marked_range| {
22087 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22088 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22089 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22090 snapshot.clip_offset_utf16(new_start, Bias::Left)
22091 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22092 })
22093 .collect::<Vec<_>>();
22094
22095 drop(snapshot);
22096 this.change_selections(None, window, cx, |selections| {
22097 selections.select_ranges(new_selected_ranges)
22098 });
22099 }
22100 });
22101
22102 self.ime_transaction = self.ime_transaction.or(transaction);
22103 if let Some(transaction) = self.ime_transaction {
22104 self.buffer.update(cx, |buffer, cx| {
22105 buffer.group_until_transaction(transaction, cx);
22106 });
22107 }
22108
22109 if self.text_highlights::<InputComposition>(cx).is_none() {
22110 self.ime_transaction.take();
22111 }
22112 }
22113
22114 fn bounds_for_range(
22115 &mut self,
22116 range_utf16: Range<usize>,
22117 element_bounds: gpui::Bounds<Pixels>,
22118 window: &mut Window,
22119 cx: &mut Context<Self>,
22120 ) -> Option<gpui::Bounds<Pixels>> {
22121 let text_layout_details = self.text_layout_details(window);
22122 let gpui::Size {
22123 width: em_width,
22124 height: line_height,
22125 } = self.character_size(window);
22126
22127 let snapshot = self.snapshot(window, cx);
22128 let scroll_position = snapshot.scroll_position();
22129 let scroll_left = scroll_position.x * em_width;
22130
22131 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22132 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22133 + self.gutter_dimensions.width
22134 + self.gutter_dimensions.margin;
22135 let y = line_height * (start.row().as_f32() - scroll_position.y);
22136
22137 Some(Bounds {
22138 origin: element_bounds.origin + point(x, y),
22139 size: size(em_width, line_height),
22140 })
22141 }
22142
22143 fn character_index_for_point(
22144 &mut self,
22145 point: gpui::Point<Pixels>,
22146 _window: &mut Window,
22147 _cx: &mut Context<Self>,
22148 ) -> Option<usize> {
22149 let position_map = self.last_position_map.as_ref()?;
22150 if !position_map.text_hitbox.contains(&point) {
22151 return None;
22152 }
22153 let display_point = position_map.point_for_position(point).previous_valid;
22154 let anchor = position_map
22155 .snapshot
22156 .display_point_to_anchor(display_point, Bias::Left);
22157 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22158 Some(utf16_offset.0)
22159 }
22160}
22161
22162trait SelectionExt {
22163 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22164 fn spanned_rows(
22165 &self,
22166 include_end_if_at_line_start: bool,
22167 map: &DisplaySnapshot,
22168 ) -> Range<MultiBufferRow>;
22169}
22170
22171impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22172 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22173 let start = self
22174 .start
22175 .to_point(&map.buffer_snapshot)
22176 .to_display_point(map);
22177 let end = self
22178 .end
22179 .to_point(&map.buffer_snapshot)
22180 .to_display_point(map);
22181 if self.reversed {
22182 end..start
22183 } else {
22184 start..end
22185 }
22186 }
22187
22188 fn spanned_rows(
22189 &self,
22190 include_end_if_at_line_start: bool,
22191 map: &DisplaySnapshot,
22192 ) -> Range<MultiBufferRow> {
22193 let start = self.start.to_point(&map.buffer_snapshot);
22194 let mut end = self.end.to_point(&map.buffer_snapshot);
22195 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22196 end.row -= 1;
22197 }
22198
22199 let buffer_start = map.prev_line_boundary(start).0;
22200 let buffer_end = map.next_line_boundary(end).0;
22201 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22202 }
22203}
22204
22205impl<T: InvalidationRegion> InvalidationStack<T> {
22206 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22207 where
22208 S: Clone + ToOffset,
22209 {
22210 while let Some(region) = self.last() {
22211 let all_selections_inside_invalidation_ranges =
22212 if selections.len() == region.ranges().len() {
22213 selections
22214 .iter()
22215 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22216 .all(|(selection, invalidation_range)| {
22217 let head = selection.head().to_offset(buffer);
22218 invalidation_range.start <= head && invalidation_range.end >= head
22219 })
22220 } else {
22221 false
22222 };
22223
22224 if all_selections_inside_invalidation_ranges {
22225 break;
22226 } else {
22227 self.pop();
22228 }
22229 }
22230 }
22231}
22232
22233impl<T> Default for InvalidationStack<T> {
22234 fn default() -> Self {
22235 Self(Default::default())
22236 }
22237}
22238
22239impl<T> Deref for InvalidationStack<T> {
22240 type Target = Vec<T>;
22241
22242 fn deref(&self) -> &Self::Target {
22243 &self.0
22244 }
22245}
22246
22247impl<T> DerefMut for InvalidationStack<T> {
22248 fn deref_mut(&mut self) -> &mut Self::Target {
22249 &mut self.0
22250 }
22251}
22252
22253impl InvalidationRegion for SnippetState {
22254 fn ranges(&self) -> &[Range<Anchor>] {
22255 &self.ranges[self.active_index]
22256 }
22257}
22258
22259fn inline_completion_edit_text(
22260 current_snapshot: &BufferSnapshot,
22261 edits: &[(Range<Anchor>, String)],
22262 edit_preview: &EditPreview,
22263 include_deletions: bool,
22264 cx: &App,
22265) -> HighlightedText {
22266 let edits = edits
22267 .iter()
22268 .map(|(anchor, text)| {
22269 (
22270 anchor.start.text_anchor..anchor.end.text_anchor,
22271 text.clone(),
22272 )
22273 })
22274 .collect::<Vec<_>>();
22275
22276 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22277}
22278
22279pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22280 match severity {
22281 lsp::DiagnosticSeverity::ERROR => colors.error,
22282 lsp::DiagnosticSeverity::WARNING => colors.warning,
22283 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22284 lsp::DiagnosticSeverity::HINT => colors.info,
22285 _ => colors.ignored,
22286 }
22287}
22288
22289pub fn styled_runs_for_code_label<'a>(
22290 label: &'a CodeLabel,
22291 syntax_theme: &'a theme::SyntaxTheme,
22292) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22293 let fade_out = HighlightStyle {
22294 fade_out: Some(0.35),
22295 ..Default::default()
22296 };
22297
22298 let mut prev_end = label.filter_range.end;
22299 label
22300 .runs
22301 .iter()
22302 .enumerate()
22303 .flat_map(move |(ix, (range, highlight_id))| {
22304 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22305 style
22306 } else {
22307 return Default::default();
22308 };
22309 let mut muted_style = style;
22310 muted_style.highlight(fade_out);
22311
22312 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22313 if range.start >= label.filter_range.end {
22314 if range.start > prev_end {
22315 runs.push((prev_end..range.start, fade_out));
22316 }
22317 runs.push((range.clone(), muted_style));
22318 } else if range.end <= label.filter_range.end {
22319 runs.push((range.clone(), style));
22320 } else {
22321 runs.push((range.start..label.filter_range.end, style));
22322 runs.push((label.filter_range.end..range.end, muted_style));
22323 }
22324 prev_end = cmp::max(prev_end, range.end);
22325
22326 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22327 runs.push((prev_end..label.text.len(), fade_out));
22328 }
22329
22330 runs
22331 })
22332}
22333
22334pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22335 let mut prev_index = 0;
22336 let mut prev_codepoint: Option<char> = None;
22337 text.char_indices()
22338 .chain([(text.len(), '\0')])
22339 .filter_map(move |(index, codepoint)| {
22340 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22341 let is_boundary = index == text.len()
22342 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22343 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22344 if is_boundary {
22345 let chunk = &text[prev_index..index];
22346 prev_index = index;
22347 Some(chunk)
22348 } else {
22349 None
22350 }
22351 })
22352}
22353
22354pub trait RangeToAnchorExt: Sized {
22355 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22356
22357 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22358 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22359 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22360 }
22361}
22362
22363impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22364 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22365 let start_offset = self.start.to_offset(snapshot);
22366 let end_offset = self.end.to_offset(snapshot);
22367 if start_offset == end_offset {
22368 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22369 } else {
22370 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22371 }
22372 }
22373}
22374
22375pub trait RowExt {
22376 fn as_f32(&self) -> f32;
22377
22378 fn next_row(&self) -> Self;
22379
22380 fn previous_row(&self) -> Self;
22381
22382 fn minus(&self, other: Self) -> u32;
22383}
22384
22385impl RowExt for DisplayRow {
22386 fn as_f32(&self) -> f32 {
22387 self.0 as f32
22388 }
22389
22390 fn next_row(&self) -> Self {
22391 Self(self.0 + 1)
22392 }
22393
22394 fn previous_row(&self) -> Self {
22395 Self(self.0.saturating_sub(1))
22396 }
22397
22398 fn minus(&self, other: Self) -> u32 {
22399 self.0 - other.0
22400 }
22401}
22402
22403impl RowExt for MultiBufferRow {
22404 fn as_f32(&self) -> f32 {
22405 self.0 as f32
22406 }
22407
22408 fn next_row(&self) -> Self {
22409 Self(self.0 + 1)
22410 }
22411
22412 fn previous_row(&self) -> Self {
22413 Self(self.0.saturating_sub(1))
22414 }
22415
22416 fn minus(&self, other: Self) -> u32 {
22417 self.0 - other.0
22418 }
22419}
22420
22421trait RowRangeExt {
22422 type Row;
22423
22424 fn len(&self) -> usize;
22425
22426 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22427}
22428
22429impl RowRangeExt for Range<MultiBufferRow> {
22430 type Row = MultiBufferRow;
22431
22432 fn len(&self) -> usize {
22433 (self.end.0 - self.start.0) as usize
22434 }
22435
22436 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22437 (self.start.0..self.end.0).map(MultiBufferRow)
22438 }
22439}
22440
22441impl RowRangeExt for Range<DisplayRow> {
22442 type Row = DisplayRow;
22443
22444 fn len(&self) -> usize {
22445 (self.end.0 - self.start.0) as usize
22446 }
22447
22448 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22449 (self.start.0..self.end.0).map(DisplayRow)
22450 }
22451}
22452
22453/// If select range has more than one line, we
22454/// just point the cursor to range.start.
22455fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22456 if range.start.row == range.end.row {
22457 range
22458 } else {
22459 range.start..range.start
22460 }
22461}
22462pub struct KillRing(ClipboardItem);
22463impl Global for KillRing {}
22464
22465const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22466
22467enum BreakpointPromptEditAction {
22468 Log,
22469 Condition,
22470 HitCondition,
22471}
22472
22473struct BreakpointPromptEditor {
22474 pub(crate) prompt: Entity<Editor>,
22475 editor: WeakEntity<Editor>,
22476 breakpoint_anchor: Anchor,
22477 breakpoint: Breakpoint,
22478 edit_action: BreakpointPromptEditAction,
22479 block_ids: HashSet<CustomBlockId>,
22480 editor_margins: Arc<Mutex<EditorMargins>>,
22481 _subscriptions: Vec<Subscription>,
22482}
22483
22484impl BreakpointPromptEditor {
22485 const MAX_LINES: u8 = 4;
22486
22487 fn new(
22488 editor: WeakEntity<Editor>,
22489 breakpoint_anchor: Anchor,
22490 breakpoint: Breakpoint,
22491 edit_action: BreakpointPromptEditAction,
22492 window: &mut Window,
22493 cx: &mut Context<Self>,
22494 ) -> Self {
22495 let base_text = match edit_action {
22496 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22497 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22498 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22499 }
22500 .map(|msg| msg.to_string())
22501 .unwrap_or_default();
22502
22503 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22504 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22505
22506 let prompt = cx.new(|cx| {
22507 let mut prompt = Editor::new(
22508 EditorMode::AutoHeight {
22509 max_lines: Self::MAX_LINES as usize,
22510 },
22511 buffer,
22512 None,
22513 window,
22514 cx,
22515 );
22516 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22517 prompt.set_show_cursor_when_unfocused(false, cx);
22518 prompt.set_placeholder_text(
22519 match edit_action {
22520 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22521 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22522 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22523 },
22524 cx,
22525 );
22526
22527 prompt
22528 });
22529
22530 Self {
22531 prompt,
22532 editor,
22533 breakpoint_anchor,
22534 breakpoint,
22535 edit_action,
22536 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22537 block_ids: Default::default(),
22538 _subscriptions: vec![],
22539 }
22540 }
22541
22542 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22543 self.block_ids.extend(block_ids)
22544 }
22545
22546 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22547 if let Some(editor) = self.editor.upgrade() {
22548 let message = self
22549 .prompt
22550 .read(cx)
22551 .buffer
22552 .read(cx)
22553 .as_singleton()
22554 .expect("A multi buffer in breakpoint prompt isn't possible")
22555 .read(cx)
22556 .as_rope()
22557 .to_string();
22558
22559 editor.update(cx, |editor, cx| {
22560 editor.edit_breakpoint_at_anchor(
22561 self.breakpoint_anchor,
22562 self.breakpoint.clone(),
22563 match self.edit_action {
22564 BreakpointPromptEditAction::Log => {
22565 BreakpointEditAction::EditLogMessage(message.into())
22566 }
22567 BreakpointPromptEditAction::Condition => {
22568 BreakpointEditAction::EditCondition(message.into())
22569 }
22570 BreakpointPromptEditAction::HitCondition => {
22571 BreakpointEditAction::EditHitCondition(message.into())
22572 }
22573 },
22574 cx,
22575 );
22576
22577 editor.remove_blocks(self.block_ids.clone(), None, cx);
22578 cx.focus_self(window);
22579 });
22580 }
22581 }
22582
22583 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22584 self.editor
22585 .update(cx, |editor, cx| {
22586 editor.remove_blocks(self.block_ids.clone(), None, cx);
22587 window.focus(&editor.focus_handle);
22588 })
22589 .log_err();
22590 }
22591
22592 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22593 let settings = ThemeSettings::get_global(cx);
22594 let text_style = TextStyle {
22595 color: if self.prompt.read(cx).read_only(cx) {
22596 cx.theme().colors().text_disabled
22597 } else {
22598 cx.theme().colors().text
22599 },
22600 font_family: settings.buffer_font.family.clone(),
22601 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22602 font_size: settings.buffer_font_size(cx).into(),
22603 font_weight: settings.buffer_font.weight,
22604 line_height: relative(settings.buffer_line_height.value()),
22605 ..Default::default()
22606 };
22607 EditorElement::new(
22608 &self.prompt,
22609 EditorStyle {
22610 background: cx.theme().colors().editor_background,
22611 local_player: cx.theme().players().local(),
22612 text: text_style,
22613 ..Default::default()
22614 },
22615 )
22616 }
22617}
22618
22619impl Render for BreakpointPromptEditor {
22620 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22621 let editor_margins = *self.editor_margins.lock();
22622 let gutter_dimensions = editor_margins.gutter;
22623 h_flex()
22624 .key_context("Editor")
22625 .bg(cx.theme().colors().editor_background)
22626 .border_y_1()
22627 .border_color(cx.theme().status().info_border)
22628 .size_full()
22629 .py(window.line_height() / 2.5)
22630 .on_action(cx.listener(Self::confirm))
22631 .on_action(cx.listener(Self::cancel))
22632 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22633 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22634 }
22635}
22636
22637impl Focusable for BreakpointPromptEditor {
22638 fn focus_handle(&self, cx: &App) -> FocusHandle {
22639 self.prompt.focus_handle(cx)
22640 }
22641}
22642
22643fn all_edits_insertions_or_deletions(
22644 edits: &Vec<(Range<Anchor>, String)>,
22645 snapshot: &MultiBufferSnapshot,
22646) -> bool {
22647 let mut all_insertions = true;
22648 let mut all_deletions = true;
22649
22650 for (range, new_text) in edits.iter() {
22651 let range_is_empty = range.to_offset(&snapshot).is_empty();
22652 let text_is_empty = new_text.is_empty();
22653
22654 if range_is_empty != text_is_empty {
22655 if range_is_empty {
22656 all_deletions = false;
22657 } else {
22658 all_insertions = false;
22659 }
22660 } else {
22661 return false;
22662 }
22663
22664 if !all_insertions && !all_deletions {
22665 return false;
22666 }
22667 }
22668 all_insertions || all_deletions
22669}
22670
22671struct MissingEditPredictionKeybindingTooltip;
22672
22673impl Render for MissingEditPredictionKeybindingTooltip {
22674 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22675 ui::tooltip_container(window, cx, |container, _, cx| {
22676 container
22677 .flex_shrink_0()
22678 .max_w_80()
22679 .min_h(rems_from_px(124.))
22680 .justify_between()
22681 .child(
22682 v_flex()
22683 .flex_1()
22684 .text_ui_sm(cx)
22685 .child(Label::new("Conflict with Accept Keybinding"))
22686 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22687 )
22688 .child(
22689 h_flex()
22690 .pb_1()
22691 .gap_1()
22692 .items_end()
22693 .w_full()
22694 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22695 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22696 }))
22697 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22698 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22699 })),
22700 )
22701 })
22702 }
22703}
22704
22705#[derive(Debug, Clone, Copy, PartialEq)]
22706pub struct LineHighlight {
22707 pub background: Background,
22708 pub border: Option<gpui::Hsla>,
22709 pub include_gutter: bool,
22710 pub type_id: Option<TypeId>,
22711}
22712
22713fn render_diff_hunk_controls(
22714 row: u32,
22715 status: &DiffHunkStatus,
22716 hunk_range: Range<Anchor>,
22717 is_created_file: bool,
22718 line_height: Pixels,
22719 editor: &Entity<Editor>,
22720 _window: &mut Window,
22721 cx: &mut App,
22722) -> AnyElement {
22723 h_flex()
22724 .h(line_height)
22725 .mr_1()
22726 .gap_1()
22727 .px_0p5()
22728 .pb_1()
22729 .border_x_1()
22730 .border_b_1()
22731 .border_color(cx.theme().colors().border_variant)
22732 .rounded_b_lg()
22733 .bg(cx.theme().colors().editor_background)
22734 .gap_1()
22735 .block_mouse_except_scroll()
22736 .shadow_md()
22737 .child(if status.has_secondary_hunk() {
22738 Button::new(("stage", row as u64), "Stage")
22739 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22740 .tooltip({
22741 let focus_handle = editor.focus_handle(cx);
22742 move |window, cx| {
22743 Tooltip::for_action_in(
22744 "Stage Hunk",
22745 &::git::ToggleStaged,
22746 &focus_handle,
22747 window,
22748 cx,
22749 )
22750 }
22751 })
22752 .on_click({
22753 let editor = editor.clone();
22754 move |_event, _window, cx| {
22755 editor.update(cx, |editor, cx| {
22756 editor.stage_or_unstage_diff_hunks(
22757 true,
22758 vec![hunk_range.start..hunk_range.start],
22759 cx,
22760 );
22761 });
22762 }
22763 })
22764 } else {
22765 Button::new(("unstage", row as u64), "Unstage")
22766 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22767 .tooltip({
22768 let focus_handle = editor.focus_handle(cx);
22769 move |window, cx| {
22770 Tooltip::for_action_in(
22771 "Unstage Hunk",
22772 &::git::ToggleStaged,
22773 &focus_handle,
22774 window,
22775 cx,
22776 )
22777 }
22778 })
22779 .on_click({
22780 let editor = editor.clone();
22781 move |_event, _window, cx| {
22782 editor.update(cx, |editor, cx| {
22783 editor.stage_or_unstage_diff_hunks(
22784 false,
22785 vec![hunk_range.start..hunk_range.start],
22786 cx,
22787 );
22788 });
22789 }
22790 })
22791 })
22792 .child(
22793 Button::new(("restore", row as u64), "Restore")
22794 .tooltip({
22795 let focus_handle = editor.focus_handle(cx);
22796 move |window, cx| {
22797 Tooltip::for_action_in(
22798 "Restore Hunk",
22799 &::git::Restore,
22800 &focus_handle,
22801 window,
22802 cx,
22803 )
22804 }
22805 })
22806 .on_click({
22807 let editor = editor.clone();
22808 move |_event, window, cx| {
22809 editor.update(cx, |editor, cx| {
22810 let snapshot = editor.snapshot(window, cx);
22811 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22812 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22813 });
22814 }
22815 })
22816 .disabled(is_created_file),
22817 )
22818 .when(
22819 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22820 |el| {
22821 el.child(
22822 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22823 .shape(IconButtonShape::Square)
22824 .icon_size(IconSize::Small)
22825 // .disabled(!has_multiple_hunks)
22826 .tooltip({
22827 let focus_handle = editor.focus_handle(cx);
22828 move |window, cx| {
22829 Tooltip::for_action_in(
22830 "Next Hunk",
22831 &GoToHunk,
22832 &focus_handle,
22833 window,
22834 cx,
22835 )
22836 }
22837 })
22838 .on_click({
22839 let editor = editor.clone();
22840 move |_event, window, cx| {
22841 editor.update(cx, |editor, cx| {
22842 let snapshot = editor.snapshot(window, cx);
22843 let position =
22844 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22845 editor.go_to_hunk_before_or_after_position(
22846 &snapshot,
22847 position,
22848 Direction::Next,
22849 window,
22850 cx,
22851 );
22852 editor.expand_selected_diff_hunks(cx);
22853 });
22854 }
22855 }),
22856 )
22857 .child(
22858 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22859 .shape(IconButtonShape::Square)
22860 .icon_size(IconSize::Small)
22861 // .disabled(!has_multiple_hunks)
22862 .tooltip({
22863 let focus_handle = editor.focus_handle(cx);
22864 move |window, cx| {
22865 Tooltip::for_action_in(
22866 "Previous Hunk",
22867 &GoToPreviousHunk,
22868 &focus_handle,
22869 window,
22870 cx,
22871 )
22872 }
22873 })
22874 .on_click({
22875 let editor = editor.clone();
22876 move |_event, window, cx| {
22877 editor.update(cx, |editor, cx| {
22878 let snapshot = editor.snapshot(window, cx);
22879 let point =
22880 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22881 editor.go_to_hunk_before_or_after_position(
22882 &snapshot,
22883 point,
22884 Direction::Prev,
22885 window,
22886 cx,
22887 );
22888 editor.expand_selected_diff_hunks(cx);
22889 });
22890 }
22891 }),
22892 )
22893 },
22894 )
22895 .into_any_element()
22896}