1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 show_task: Option<Task<()>>,
903 hide_task: Option<Task<()>>,
904 popover_bounds: Option<Bounds<Pixels>>,
905 popover_state: InlineBlamePopoverState,
906}
907
908enum SelectionDragState {
909 /// State when no drag related activity is detected.
910 None,
911 /// State when the mouse is down on a selection that is about to be dragged.
912 ReadyToDrag {
913 selection: Selection<Anchor>,
914 click_position: gpui::Point<Pixels>,
915 },
916 /// State when the mouse is dragging the selection in the editor.
917 Dragging {
918 selection: Selection<Anchor>,
919 drop_cursor: Selection<Anchor>,
920 hide_drop_cursor: bool,
921 },
922}
923
924/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
925/// a breakpoint on them.
926#[derive(Clone, Copy, Debug, PartialEq, Eq)]
927struct PhantomBreakpointIndicator {
928 display_row: DisplayRow,
929 /// There's a small debounce between hovering over the line and showing the indicator.
930 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
931 is_active: bool,
932 collides_with_existing_breakpoint: bool,
933}
934
935/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
936///
937/// See the [module level documentation](self) for more information.
938pub struct Editor {
939 focus_handle: FocusHandle,
940 last_focused_descendant: Option<WeakFocusHandle>,
941 /// The text buffer being edited
942 buffer: Entity<MultiBuffer>,
943 /// Map of how text in the buffer should be displayed.
944 /// Handles soft wraps, folds, fake inlay text insertions, etc.
945 pub display_map: Entity<DisplayMap>,
946 pub selections: SelectionsCollection,
947 pub scroll_manager: ScrollManager,
948 /// When inline assist editors are linked, they all render cursors because
949 /// typing enters text into each of them, even the ones that aren't focused.
950 pub(crate) show_cursor_when_unfocused: bool,
951 columnar_selection_tail: Option<Anchor>,
952 columnar_display_point: Option<DisplayPoint>,
953 add_selections_state: Option<AddSelectionsState>,
954 select_next_state: Option<SelectNextState>,
955 select_prev_state: Option<SelectNextState>,
956 selection_history: SelectionHistory,
957 defer_selection_effects: bool,
958 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
959 autoclose_regions: Vec<AutocloseRegion>,
960 snippet_stack: InvalidationStack<SnippetState>,
961 select_syntax_node_history: SelectSyntaxNodeHistory,
962 ime_transaction: Option<TransactionId>,
963 pub diagnostics_max_severity: DiagnosticSeverity,
964 active_diagnostics: ActiveDiagnostic,
965 show_inline_diagnostics: bool,
966 inline_diagnostics_update: Task<()>,
967 inline_diagnostics_enabled: bool,
968 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
969 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
970 hard_wrap: Option<usize>,
971
972 // TODO: make this a access method
973 pub project: Option<Entity<Project>>,
974 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
975 completion_provider: Option<Rc<dyn CompletionProvider>>,
976 collaboration_hub: Option<Box<dyn CollaborationHub>>,
977 blink_manager: Entity<BlinkManager>,
978 show_cursor_names: bool,
979 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
980 pub show_local_selections: bool,
981 mode: EditorMode,
982 show_breadcrumbs: bool,
983 show_gutter: bool,
984 show_scrollbars: ScrollbarAxes,
985 minimap_visibility: MinimapVisibility,
986 offset_content: bool,
987 disable_expand_excerpt_buttons: bool,
988 show_line_numbers: Option<bool>,
989 use_relative_line_numbers: Option<bool>,
990 show_git_diff_gutter: Option<bool>,
991 show_code_actions: Option<bool>,
992 show_runnables: Option<bool>,
993 show_breakpoints: Option<bool>,
994 show_wrap_guides: Option<bool>,
995 show_indent_guides: Option<bool>,
996 placeholder_text: Option<Arc<str>>,
997 highlight_order: usize,
998 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
999 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1000 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1001 scrollbar_marker_state: ScrollbarMarkerState,
1002 active_indent_guides_state: ActiveIndentGuidesState,
1003 nav_history: Option<ItemNavHistory>,
1004 context_menu: RefCell<Option<CodeContextMenu>>,
1005 context_menu_options: Option<ContextMenuOptions>,
1006 mouse_context_menu: Option<MouseContextMenu>,
1007 completion_tasks: Vec<(CompletionId, Task<()>)>,
1008 inline_blame_popover: Option<InlineBlamePopover>,
1009 signature_help_state: SignatureHelpState,
1010 auto_signature_help: Option<bool>,
1011 find_all_references_task_sources: Vec<Anchor>,
1012 next_completion_id: CompletionId,
1013 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1014 code_actions_task: Option<Task<Result<()>>>,
1015 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1016 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1017 document_highlights_task: Option<Task<()>>,
1018 linked_editing_range_task: Option<Task<Option<()>>>,
1019 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1020 pending_rename: Option<RenameState>,
1021 searchable: bool,
1022 cursor_shape: CursorShape,
1023 current_line_highlight: Option<CurrentLineHighlight>,
1024 collapse_matches: bool,
1025 autoindent_mode: Option<AutoindentMode>,
1026 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1027 input_enabled: bool,
1028 use_modal_editing: bool,
1029 read_only: bool,
1030 leader_id: Option<CollaboratorId>,
1031 remote_id: Option<ViewId>,
1032 pub hover_state: HoverState,
1033 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1034 gutter_hovered: bool,
1035 hovered_link_state: Option<HoveredLinkState>,
1036 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1037 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1038 active_inline_completion: Option<InlineCompletionState>,
1039 /// Used to prevent flickering as the user types while the menu is open
1040 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1041 edit_prediction_settings: EditPredictionSettings,
1042 inline_completions_hidden_for_vim_mode: bool,
1043 show_inline_completions_override: Option<bool>,
1044 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1045 edit_prediction_preview: EditPredictionPreview,
1046 edit_prediction_indent_conflict: bool,
1047 edit_prediction_requires_modifier_in_indent_conflict: bool,
1048 inlay_hint_cache: InlayHintCache,
1049 next_inlay_id: usize,
1050 _subscriptions: Vec<Subscription>,
1051 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1052 gutter_dimensions: GutterDimensions,
1053 style: Option<EditorStyle>,
1054 text_style_refinement: Option<TextStyleRefinement>,
1055 next_editor_action_id: EditorActionId,
1056 editor_actions: Rc<
1057 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1058 >,
1059 use_autoclose: bool,
1060 use_auto_surround: bool,
1061 auto_replace_emoji_shortcode: bool,
1062 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1063 show_git_blame_gutter: bool,
1064 show_git_blame_inline: bool,
1065 show_git_blame_inline_delay_task: Option<Task<()>>,
1066 git_blame_inline_enabled: bool,
1067 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1068 serialize_dirty_buffers: bool,
1069 show_selection_menu: Option<bool>,
1070 blame: Option<Entity<GitBlame>>,
1071 blame_subscription: Option<Subscription>,
1072 custom_context_menu: Option<
1073 Box<
1074 dyn 'static
1075 + Fn(
1076 &mut Self,
1077 DisplayPoint,
1078 &mut Window,
1079 &mut Context<Self>,
1080 ) -> Option<Entity<ui::ContextMenu>>,
1081 >,
1082 >,
1083 last_bounds: Option<Bounds<Pixels>>,
1084 last_position_map: Option<Rc<PositionMap>>,
1085 expect_bounds_change: Option<Bounds<Pixels>>,
1086 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1087 tasks_update_task: Option<Task<()>>,
1088 breakpoint_store: Option<Entity<BreakpointStore>>,
1089 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1090 pull_diagnostics_task: Task<()>,
1091 in_project_search: bool,
1092 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1093 breadcrumb_header: Option<String>,
1094 focused_block: Option<FocusedBlock>,
1095 next_scroll_position: NextScrollCursorCenterTopBottom,
1096 addons: HashMap<TypeId, Box<dyn Addon>>,
1097 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1098 load_diff_task: Option<Shared<Task<()>>>,
1099 /// Whether we are temporarily displaying a diff other than git's
1100 temporary_diff_override: bool,
1101 selection_mark_mode: bool,
1102 toggle_fold_multiple_buffers: Task<()>,
1103 _scroll_cursor_center_top_bottom_task: Task<()>,
1104 serialize_selections: Task<()>,
1105 serialize_folds: Task<()>,
1106 mouse_cursor_hidden: bool,
1107 minimap: Option<Entity<Self>>,
1108 hide_mouse_mode: HideMouseMode,
1109 pub change_list: ChangeList,
1110 inline_value_cache: InlineValueCache,
1111 selection_drag_state: SelectionDragState,
1112 drag_and_drop_selection_enabled: bool,
1113}
1114
1115#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1116enum NextScrollCursorCenterTopBottom {
1117 #[default]
1118 Center,
1119 Top,
1120 Bottom,
1121}
1122
1123impl NextScrollCursorCenterTopBottom {
1124 fn next(&self) -> Self {
1125 match self {
1126 Self::Center => Self::Top,
1127 Self::Top => Self::Bottom,
1128 Self::Bottom => Self::Center,
1129 }
1130 }
1131}
1132
1133#[derive(Clone)]
1134pub struct EditorSnapshot {
1135 pub mode: EditorMode,
1136 show_gutter: bool,
1137 show_line_numbers: Option<bool>,
1138 show_git_diff_gutter: Option<bool>,
1139 show_code_actions: Option<bool>,
1140 show_runnables: Option<bool>,
1141 show_breakpoints: Option<bool>,
1142 git_blame_gutter_max_author_length: Option<usize>,
1143 pub display_snapshot: DisplaySnapshot,
1144 pub placeholder_text: Option<Arc<str>>,
1145 is_focused: bool,
1146 scroll_anchor: ScrollAnchor,
1147 ongoing_scroll: OngoingScroll,
1148 current_line_highlight: CurrentLineHighlight,
1149 gutter_hovered: bool,
1150}
1151
1152#[derive(Default, Debug, Clone, Copy)]
1153pub struct GutterDimensions {
1154 pub left_padding: Pixels,
1155 pub right_padding: Pixels,
1156 pub width: Pixels,
1157 pub margin: Pixels,
1158 pub git_blame_entries_width: Option<Pixels>,
1159}
1160
1161impl GutterDimensions {
1162 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1163 Self {
1164 margin: Self::default_gutter_margin(font_id, font_size, cx),
1165 ..Default::default()
1166 }
1167 }
1168
1169 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1170 -cx.text_system().descent(font_id, font_size)
1171 }
1172 /// The full width of the space taken up by the gutter.
1173 pub fn full_width(&self) -> Pixels {
1174 self.margin + self.width
1175 }
1176
1177 /// The width of the space reserved for the fold indicators,
1178 /// use alongside 'justify_end' and `gutter_width` to
1179 /// right align content with the line numbers
1180 pub fn fold_area_width(&self) -> Pixels {
1181 self.margin + self.right_padding
1182 }
1183}
1184
1185#[derive(Debug)]
1186pub struct RemoteSelection {
1187 pub replica_id: ReplicaId,
1188 pub selection: Selection<Anchor>,
1189 pub cursor_shape: CursorShape,
1190 pub collaborator_id: CollaboratorId,
1191 pub line_mode: bool,
1192 pub user_name: Option<SharedString>,
1193 pub color: PlayerColor,
1194}
1195
1196#[derive(Clone, Debug)]
1197struct SelectionHistoryEntry {
1198 selections: Arc<[Selection<Anchor>]>,
1199 select_next_state: Option<SelectNextState>,
1200 select_prev_state: Option<SelectNextState>,
1201 add_selections_state: Option<AddSelectionsState>,
1202}
1203
1204#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1205enum SelectionHistoryMode {
1206 Normal,
1207 Undoing,
1208 Redoing,
1209 Skipping,
1210}
1211
1212#[derive(Clone, PartialEq, Eq, Hash)]
1213struct HoveredCursor {
1214 replica_id: u16,
1215 selection_id: usize,
1216}
1217
1218impl Default for SelectionHistoryMode {
1219 fn default() -> Self {
1220 Self::Normal
1221 }
1222}
1223
1224struct DeferredSelectionEffectsState {
1225 changed: bool,
1226 should_update_completions: bool,
1227 autoscroll: Option<Autoscroll>,
1228 old_cursor_position: Anchor,
1229 history_entry: SelectionHistoryEntry,
1230}
1231
1232#[derive(Default)]
1233struct SelectionHistory {
1234 #[allow(clippy::type_complexity)]
1235 selections_by_transaction:
1236 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1237 mode: SelectionHistoryMode,
1238 undo_stack: VecDeque<SelectionHistoryEntry>,
1239 redo_stack: VecDeque<SelectionHistoryEntry>,
1240}
1241
1242impl SelectionHistory {
1243 #[track_caller]
1244 fn insert_transaction(
1245 &mut self,
1246 transaction_id: TransactionId,
1247 selections: Arc<[Selection<Anchor>]>,
1248 ) {
1249 if selections.is_empty() {
1250 log::error!(
1251 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1252 std::panic::Location::caller()
1253 );
1254 return;
1255 }
1256 self.selections_by_transaction
1257 .insert(transaction_id, (selections, None));
1258 }
1259
1260 #[allow(clippy::type_complexity)]
1261 fn transaction(
1262 &self,
1263 transaction_id: TransactionId,
1264 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1265 self.selections_by_transaction.get(&transaction_id)
1266 }
1267
1268 #[allow(clippy::type_complexity)]
1269 fn transaction_mut(
1270 &mut self,
1271 transaction_id: TransactionId,
1272 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1273 self.selections_by_transaction.get_mut(&transaction_id)
1274 }
1275
1276 fn push(&mut self, entry: SelectionHistoryEntry) {
1277 if !entry.selections.is_empty() {
1278 match self.mode {
1279 SelectionHistoryMode::Normal => {
1280 self.push_undo(entry);
1281 self.redo_stack.clear();
1282 }
1283 SelectionHistoryMode::Undoing => self.push_redo(entry),
1284 SelectionHistoryMode::Redoing => self.push_undo(entry),
1285 SelectionHistoryMode::Skipping => {}
1286 }
1287 }
1288 }
1289
1290 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1291 if self
1292 .undo_stack
1293 .back()
1294 .map_or(true, |e| e.selections != entry.selections)
1295 {
1296 self.undo_stack.push_back(entry);
1297 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1298 self.undo_stack.pop_front();
1299 }
1300 }
1301 }
1302
1303 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1304 if self
1305 .redo_stack
1306 .back()
1307 .map_or(true, |e| e.selections != entry.selections)
1308 {
1309 self.redo_stack.push_back(entry);
1310 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1311 self.redo_stack.pop_front();
1312 }
1313 }
1314 }
1315}
1316
1317#[derive(Clone, Copy)]
1318pub struct RowHighlightOptions {
1319 pub autoscroll: bool,
1320 pub include_gutter: bool,
1321}
1322
1323impl Default for RowHighlightOptions {
1324 fn default() -> Self {
1325 Self {
1326 autoscroll: Default::default(),
1327 include_gutter: true,
1328 }
1329 }
1330}
1331
1332struct RowHighlight {
1333 index: usize,
1334 range: Range<Anchor>,
1335 color: Hsla,
1336 options: RowHighlightOptions,
1337 type_id: TypeId,
1338}
1339
1340#[derive(Clone, Debug)]
1341struct AddSelectionsState {
1342 groups: Vec<AddSelectionsGroup>,
1343}
1344
1345#[derive(Clone, Debug)]
1346struct AddSelectionsGroup {
1347 above: bool,
1348 stack: Vec<usize>,
1349}
1350
1351#[derive(Clone)]
1352struct SelectNextState {
1353 query: AhoCorasick,
1354 wordwise: bool,
1355 done: bool,
1356}
1357
1358impl std::fmt::Debug for SelectNextState {
1359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1360 f.debug_struct(std::any::type_name::<Self>())
1361 .field("wordwise", &self.wordwise)
1362 .field("done", &self.done)
1363 .finish()
1364 }
1365}
1366
1367#[derive(Debug)]
1368struct AutocloseRegion {
1369 selection_id: usize,
1370 range: Range<Anchor>,
1371 pair: BracketPair,
1372}
1373
1374#[derive(Debug)]
1375struct SnippetState {
1376 ranges: Vec<Vec<Range<Anchor>>>,
1377 active_index: usize,
1378 choices: Vec<Option<Vec<String>>>,
1379}
1380
1381#[doc(hidden)]
1382pub struct RenameState {
1383 pub range: Range<Anchor>,
1384 pub old_name: Arc<str>,
1385 pub editor: Entity<Editor>,
1386 block_id: CustomBlockId,
1387}
1388
1389struct InvalidationStack<T>(Vec<T>);
1390
1391struct RegisteredInlineCompletionProvider {
1392 provider: Arc<dyn InlineCompletionProviderHandle>,
1393 _subscription: Subscription,
1394}
1395
1396#[derive(Debug, PartialEq, Eq)]
1397pub struct ActiveDiagnosticGroup {
1398 pub active_range: Range<Anchor>,
1399 pub active_message: String,
1400 pub group_id: usize,
1401 pub blocks: HashSet<CustomBlockId>,
1402}
1403
1404#[derive(Debug, PartialEq, Eq)]
1405
1406pub(crate) enum ActiveDiagnostic {
1407 None,
1408 All,
1409 Group(ActiveDiagnosticGroup),
1410}
1411
1412#[derive(Serialize, Deserialize, Clone, Debug)]
1413pub struct ClipboardSelection {
1414 /// The number of bytes in this selection.
1415 pub len: usize,
1416 /// Whether this was a full-line selection.
1417 pub is_entire_line: bool,
1418 /// The indentation of the first line when this content was originally copied.
1419 pub first_line_indent: u32,
1420}
1421
1422// selections, scroll behavior, was newest selection reversed
1423type SelectSyntaxNodeHistoryState = (
1424 Box<[Selection<usize>]>,
1425 SelectSyntaxNodeScrollBehavior,
1426 bool,
1427);
1428
1429#[derive(Default)]
1430struct SelectSyntaxNodeHistory {
1431 stack: Vec<SelectSyntaxNodeHistoryState>,
1432 // disable temporarily to allow changing selections without losing the stack
1433 pub disable_clearing: bool,
1434}
1435
1436impl SelectSyntaxNodeHistory {
1437 pub fn try_clear(&mut self) {
1438 if !self.disable_clearing {
1439 self.stack.clear();
1440 }
1441 }
1442
1443 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1444 self.stack.push(selection);
1445 }
1446
1447 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1448 self.stack.pop()
1449 }
1450}
1451
1452enum SelectSyntaxNodeScrollBehavior {
1453 CursorTop,
1454 FitSelection,
1455 CursorBottom,
1456}
1457
1458#[derive(Debug)]
1459pub(crate) struct NavigationData {
1460 cursor_anchor: Anchor,
1461 cursor_position: Point,
1462 scroll_anchor: ScrollAnchor,
1463 scroll_top_row: u32,
1464}
1465
1466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1467pub enum GotoDefinitionKind {
1468 Symbol,
1469 Declaration,
1470 Type,
1471 Implementation,
1472}
1473
1474#[derive(Debug, Clone)]
1475enum InlayHintRefreshReason {
1476 ModifiersChanged(bool),
1477 Toggle(bool),
1478 SettingsChange(InlayHintSettings),
1479 NewLinesShown,
1480 BufferEdited(HashSet<Arc<Language>>),
1481 RefreshRequested,
1482 ExcerptsRemoved(Vec<ExcerptId>),
1483}
1484
1485impl InlayHintRefreshReason {
1486 fn description(&self) -> &'static str {
1487 match self {
1488 Self::ModifiersChanged(_) => "modifiers changed",
1489 Self::Toggle(_) => "toggle",
1490 Self::SettingsChange(_) => "settings change",
1491 Self::NewLinesShown => "new lines shown",
1492 Self::BufferEdited(_) => "buffer edited",
1493 Self::RefreshRequested => "refresh requested",
1494 Self::ExcerptsRemoved(_) => "excerpts removed",
1495 }
1496 }
1497}
1498
1499pub enum FormatTarget {
1500 Buffers,
1501 Ranges(Vec<Range<MultiBufferPoint>>),
1502}
1503
1504pub(crate) struct FocusedBlock {
1505 id: BlockId,
1506 focus_handle: WeakFocusHandle,
1507}
1508
1509#[derive(Clone)]
1510enum JumpData {
1511 MultiBufferRow {
1512 row: MultiBufferRow,
1513 line_offset_from_top: u32,
1514 },
1515 MultiBufferPoint {
1516 excerpt_id: ExcerptId,
1517 position: Point,
1518 anchor: text::Anchor,
1519 line_offset_from_top: u32,
1520 },
1521}
1522
1523pub enum MultibufferSelectionMode {
1524 First,
1525 All,
1526}
1527
1528#[derive(Clone, Copy, Debug, Default)]
1529pub struct RewrapOptions {
1530 pub override_language_settings: bool,
1531 pub preserve_existing_whitespace: bool,
1532}
1533
1534impl Editor {
1535 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1536 let buffer = cx.new(|cx| Buffer::local("", cx));
1537 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1538 Self::new(
1539 EditorMode::SingleLine { auto_width: false },
1540 buffer,
1541 None,
1542 window,
1543 cx,
1544 )
1545 }
1546
1547 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1548 let buffer = cx.new(|cx| Buffer::local("", cx));
1549 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1550 Self::new(EditorMode::full(), buffer, None, window, cx)
1551 }
1552
1553 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1554 let buffer = cx.new(|cx| Buffer::local("", cx));
1555 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1556 Self::new(
1557 EditorMode::SingleLine { auto_width: true },
1558 buffer,
1559 None,
1560 window,
1561 cx,
1562 )
1563 }
1564
1565 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1566 let buffer = cx.new(|cx| Buffer::local("", cx));
1567 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1568 Self::new(
1569 EditorMode::AutoHeight { max_lines },
1570 buffer,
1571 None,
1572 window,
1573 cx,
1574 )
1575 }
1576
1577 pub fn for_buffer(
1578 buffer: Entity<Buffer>,
1579 project: Option<Entity<Project>>,
1580 window: &mut Window,
1581 cx: &mut Context<Self>,
1582 ) -> Self {
1583 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1584 Self::new(EditorMode::full(), buffer, project, window, cx)
1585 }
1586
1587 pub fn for_multibuffer(
1588 buffer: Entity<MultiBuffer>,
1589 project: Option<Entity<Project>>,
1590 window: &mut Window,
1591 cx: &mut Context<Self>,
1592 ) -> Self {
1593 Self::new(EditorMode::full(), buffer, project, window, cx)
1594 }
1595
1596 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1597 let mut clone = Self::new(
1598 self.mode.clone(),
1599 self.buffer.clone(),
1600 self.project.clone(),
1601 window,
1602 cx,
1603 );
1604 self.display_map.update(cx, |display_map, cx| {
1605 let snapshot = display_map.snapshot(cx);
1606 clone.display_map.update(cx, |display_map, cx| {
1607 display_map.set_state(&snapshot, cx);
1608 });
1609 });
1610 clone.folds_did_change(cx);
1611 clone.selections.clone_state(&self.selections);
1612 clone.scroll_manager.clone_state(&self.scroll_manager);
1613 clone.searchable = self.searchable;
1614 clone.read_only = self.read_only;
1615 clone
1616 }
1617
1618 pub fn new(
1619 mode: EditorMode,
1620 buffer: Entity<MultiBuffer>,
1621 project: Option<Entity<Project>>,
1622 window: &mut Window,
1623 cx: &mut Context<Self>,
1624 ) -> Self {
1625 Editor::new_internal(mode, buffer, project, None, window, cx)
1626 }
1627
1628 fn new_internal(
1629 mode: EditorMode,
1630 buffer: Entity<MultiBuffer>,
1631 project: Option<Entity<Project>>,
1632 display_map: Option<Entity<DisplayMap>>,
1633 window: &mut Window,
1634 cx: &mut Context<Self>,
1635 ) -> Self {
1636 debug_assert!(
1637 display_map.is_none() || mode.is_minimap(),
1638 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1639 );
1640
1641 let full_mode = mode.is_full();
1642 let diagnostics_max_severity = if full_mode {
1643 EditorSettings::get_global(cx)
1644 .diagnostics_max_severity
1645 .unwrap_or(DiagnosticSeverity::Hint)
1646 } else {
1647 DiagnosticSeverity::Off
1648 };
1649 let style = window.text_style();
1650 let font_size = style.font_size.to_pixels(window.rem_size());
1651 let editor = cx.entity().downgrade();
1652 let fold_placeholder = FoldPlaceholder {
1653 constrain_width: true,
1654 render: Arc::new(move |fold_id, fold_range, cx| {
1655 let editor = editor.clone();
1656 div()
1657 .id(fold_id)
1658 .bg(cx.theme().colors().ghost_element_background)
1659 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1660 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1661 .rounded_xs()
1662 .size_full()
1663 .cursor_pointer()
1664 .child("⋯")
1665 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1666 .on_click(move |_, _window, cx| {
1667 editor
1668 .update(cx, |editor, cx| {
1669 editor.unfold_ranges(
1670 &[fold_range.start..fold_range.end],
1671 true,
1672 false,
1673 cx,
1674 );
1675 cx.stop_propagation();
1676 })
1677 .ok();
1678 })
1679 .into_any()
1680 }),
1681 merge_adjacent: true,
1682 ..FoldPlaceholder::default()
1683 };
1684 let display_map = display_map.unwrap_or_else(|| {
1685 cx.new(|cx| {
1686 DisplayMap::new(
1687 buffer.clone(),
1688 style.font(),
1689 font_size,
1690 None,
1691 FILE_HEADER_HEIGHT,
1692 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1693 fold_placeholder,
1694 diagnostics_max_severity,
1695 cx,
1696 )
1697 })
1698 });
1699
1700 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1701
1702 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1703
1704 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1705 .then(|| language_settings::SoftWrap::None);
1706
1707 let mut project_subscriptions = Vec::new();
1708 if mode.is_full() {
1709 if let Some(project) = project.as_ref() {
1710 project_subscriptions.push(cx.subscribe_in(
1711 project,
1712 window,
1713 |editor, _, event, window, cx| match event {
1714 project::Event::RefreshCodeLens => {
1715 // we always query lens with actions, without storing them, always refreshing them
1716 }
1717 project::Event::RefreshInlayHints => {
1718 editor
1719 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1720 }
1721 project::Event::LanguageServerAdded(..)
1722 | project::Event::LanguageServerRemoved(..) => {
1723 if editor.tasks_update_task.is_none() {
1724 editor.tasks_update_task =
1725 Some(editor.refresh_runnables(window, cx));
1726 }
1727 editor.pull_diagnostics(None, window, cx);
1728 }
1729 project::Event::SnippetEdit(id, snippet_edits) => {
1730 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1731 let focus_handle = editor.focus_handle(cx);
1732 if focus_handle.is_focused(window) {
1733 let snapshot = buffer.read(cx).snapshot();
1734 for (range, snippet) in snippet_edits {
1735 let editor_range =
1736 language::range_from_lsp(*range).to_offset(&snapshot);
1737 editor
1738 .insert_snippet(
1739 &[editor_range],
1740 snippet.clone(),
1741 window,
1742 cx,
1743 )
1744 .ok();
1745 }
1746 }
1747 }
1748 }
1749 _ => {}
1750 },
1751 ));
1752 if let Some(task_inventory) = project
1753 .read(cx)
1754 .task_store()
1755 .read(cx)
1756 .task_inventory()
1757 .cloned()
1758 {
1759 project_subscriptions.push(cx.observe_in(
1760 &task_inventory,
1761 window,
1762 |editor, _, window, cx| {
1763 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1764 },
1765 ));
1766 };
1767
1768 project_subscriptions.push(cx.subscribe_in(
1769 &project.read(cx).breakpoint_store(),
1770 window,
1771 |editor, _, event, window, cx| match event {
1772 BreakpointStoreEvent::ClearDebugLines => {
1773 editor.clear_row_highlights::<ActiveDebugLine>();
1774 editor.refresh_inline_values(cx);
1775 }
1776 BreakpointStoreEvent::SetDebugLine => {
1777 if editor.go_to_active_debug_line(window, cx) {
1778 cx.stop_propagation();
1779 }
1780
1781 editor.refresh_inline_values(cx);
1782 }
1783 _ => {}
1784 },
1785 ));
1786 }
1787 }
1788
1789 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1790
1791 let inlay_hint_settings =
1792 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1793 let focus_handle = cx.focus_handle();
1794 cx.on_focus(&focus_handle, window, Self::handle_focus)
1795 .detach();
1796 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1797 .detach();
1798 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1799 .detach();
1800 cx.on_blur(&focus_handle, window, Self::handle_blur)
1801 .detach();
1802 cx.observe_pending_input(window, Self::observe_pending_input)
1803 .detach();
1804
1805 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1806 Some(false)
1807 } else {
1808 None
1809 };
1810
1811 let breakpoint_store = match (&mode, project.as_ref()) {
1812 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1813 _ => None,
1814 };
1815
1816 let mut code_action_providers = Vec::new();
1817 let mut load_uncommitted_diff = None;
1818 if let Some(project) = project.clone() {
1819 load_uncommitted_diff = Some(
1820 update_uncommitted_diff_for_buffer(
1821 cx.entity(),
1822 &project,
1823 buffer.read(cx).all_buffers(),
1824 buffer.clone(),
1825 cx,
1826 )
1827 .shared(),
1828 );
1829 code_action_providers.push(Rc::new(project) as Rc<_>);
1830 }
1831
1832 let mut editor = Self {
1833 focus_handle,
1834 show_cursor_when_unfocused: false,
1835 last_focused_descendant: None,
1836 buffer: buffer.clone(),
1837 display_map: display_map.clone(),
1838 selections,
1839 scroll_manager: ScrollManager::new(cx),
1840 columnar_selection_tail: None,
1841 columnar_display_point: None,
1842 add_selections_state: None,
1843 select_next_state: None,
1844 select_prev_state: None,
1845 selection_history: SelectionHistory::default(),
1846 defer_selection_effects: false,
1847 deferred_selection_effects_state: None,
1848 autoclose_regions: Vec::new(),
1849 snippet_stack: InvalidationStack::default(),
1850 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1851 ime_transaction: None,
1852 active_diagnostics: ActiveDiagnostic::None,
1853 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1854 inline_diagnostics_update: Task::ready(()),
1855 inline_diagnostics: Vec::new(),
1856 soft_wrap_mode_override,
1857 diagnostics_max_severity,
1858 hard_wrap: None,
1859 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1860 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1861 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1862 project,
1863 blink_manager: blink_manager.clone(),
1864 show_local_selections: true,
1865 show_scrollbars: ScrollbarAxes {
1866 horizontal: full_mode,
1867 vertical: full_mode,
1868 },
1869 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1870 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1871 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1872 show_gutter: mode.is_full(),
1873 show_line_numbers: None,
1874 use_relative_line_numbers: None,
1875 disable_expand_excerpt_buttons: false,
1876 show_git_diff_gutter: None,
1877 show_code_actions: None,
1878 show_runnables: None,
1879 show_breakpoints: None,
1880 show_wrap_guides: None,
1881 show_indent_guides,
1882 placeholder_text: None,
1883 highlight_order: 0,
1884 highlighted_rows: HashMap::default(),
1885 background_highlights: TreeMap::default(),
1886 gutter_highlights: TreeMap::default(),
1887 scrollbar_marker_state: ScrollbarMarkerState::default(),
1888 active_indent_guides_state: ActiveIndentGuidesState::default(),
1889 nav_history: None,
1890 context_menu: RefCell::new(None),
1891 context_menu_options: None,
1892 mouse_context_menu: None,
1893 completion_tasks: Vec::new(),
1894 inline_blame_popover: None,
1895 signature_help_state: SignatureHelpState::default(),
1896 auto_signature_help: None,
1897 find_all_references_task_sources: Vec::new(),
1898 next_completion_id: 0,
1899 next_inlay_id: 0,
1900 code_action_providers,
1901 available_code_actions: None,
1902 code_actions_task: None,
1903 quick_selection_highlight_task: None,
1904 debounced_selection_highlight_task: None,
1905 document_highlights_task: None,
1906 linked_editing_range_task: None,
1907 pending_rename: None,
1908 searchable: true,
1909 cursor_shape: EditorSettings::get_global(cx)
1910 .cursor_shape
1911 .unwrap_or_default(),
1912 current_line_highlight: None,
1913 autoindent_mode: Some(AutoindentMode::EachLine),
1914 collapse_matches: false,
1915 workspace: None,
1916 input_enabled: true,
1917 use_modal_editing: mode.is_full(),
1918 read_only: mode.is_minimap(),
1919 use_autoclose: true,
1920 use_auto_surround: true,
1921 auto_replace_emoji_shortcode: false,
1922 jsx_tag_auto_close_enabled_in_any_buffer: false,
1923 leader_id: None,
1924 remote_id: None,
1925 hover_state: HoverState::default(),
1926 pending_mouse_down: None,
1927 hovered_link_state: None,
1928 edit_prediction_provider: None,
1929 active_inline_completion: None,
1930 stale_inline_completion_in_menu: None,
1931 edit_prediction_preview: EditPredictionPreview::Inactive {
1932 released_too_fast: false,
1933 },
1934 inline_diagnostics_enabled: mode.is_full(),
1935 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1936 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1937
1938 gutter_hovered: false,
1939 pixel_position_of_newest_cursor: None,
1940 last_bounds: None,
1941 last_position_map: None,
1942 expect_bounds_change: None,
1943 gutter_dimensions: GutterDimensions::default(),
1944 style: None,
1945 show_cursor_names: false,
1946 hovered_cursors: HashMap::default(),
1947 next_editor_action_id: EditorActionId::default(),
1948 editor_actions: Rc::default(),
1949 inline_completions_hidden_for_vim_mode: false,
1950 show_inline_completions_override: None,
1951 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1952 edit_prediction_settings: EditPredictionSettings::Disabled,
1953 edit_prediction_indent_conflict: false,
1954 edit_prediction_requires_modifier_in_indent_conflict: true,
1955 custom_context_menu: None,
1956 show_git_blame_gutter: false,
1957 show_git_blame_inline: false,
1958 show_selection_menu: None,
1959 show_git_blame_inline_delay_task: None,
1960 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1961 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1962 serialize_dirty_buffers: !mode.is_minimap()
1963 && ProjectSettings::get_global(cx)
1964 .session
1965 .restore_unsaved_buffers,
1966 blame: None,
1967 blame_subscription: None,
1968 tasks: BTreeMap::default(),
1969
1970 breakpoint_store,
1971 gutter_breakpoint_indicator: (None, None),
1972 _subscriptions: vec![
1973 cx.observe(&buffer, Self::on_buffer_changed),
1974 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1975 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1976 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1977 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1978 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1979 cx.observe_window_activation(window, |editor, window, cx| {
1980 let active = window.is_window_active();
1981 editor.blink_manager.update(cx, |blink_manager, cx| {
1982 if active {
1983 blink_manager.enable(cx);
1984 } else {
1985 blink_manager.disable(cx);
1986 }
1987 });
1988 if active {
1989 editor.show_mouse_cursor();
1990 }
1991 }),
1992 ],
1993 tasks_update_task: None,
1994 pull_diagnostics_task: Task::ready(()),
1995 linked_edit_ranges: Default::default(),
1996 in_project_search: false,
1997 previous_search_ranges: None,
1998 breadcrumb_header: None,
1999 focused_block: None,
2000 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2001 addons: HashMap::default(),
2002 registered_buffers: HashMap::default(),
2003 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2004 selection_mark_mode: false,
2005 toggle_fold_multiple_buffers: Task::ready(()),
2006 serialize_selections: Task::ready(()),
2007 serialize_folds: Task::ready(()),
2008 text_style_refinement: None,
2009 load_diff_task: load_uncommitted_diff,
2010 temporary_diff_override: false,
2011 mouse_cursor_hidden: false,
2012 minimap: None,
2013 hide_mouse_mode: EditorSettings::get_global(cx)
2014 .hide_mouse
2015 .unwrap_or_default(),
2016 change_list: ChangeList::new(),
2017 mode,
2018 selection_drag_state: SelectionDragState::None,
2019 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2020 };
2021 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2022 editor
2023 ._subscriptions
2024 .push(cx.observe(breakpoints, |_, _, cx| {
2025 cx.notify();
2026 }));
2027 }
2028 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2029 editor._subscriptions.extend(project_subscriptions);
2030
2031 editor._subscriptions.push(cx.subscribe_in(
2032 &cx.entity(),
2033 window,
2034 |editor, _, e: &EditorEvent, window, cx| match e {
2035 EditorEvent::ScrollPositionChanged { local, .. } => {
2036 if *local {
2037 let new_anchor = editor.scroll_manager.anchor();
2038 let snapshot = editor.snapshot(window, cx);
2039 editor.update_restoration_data(cx, move |data| {
2040 data.scroll_position = (
2041 new_anchor.top_row(&snapshot.buffer_snapshot),
2042 new_anchor.offset,
2043 );
2044 });
2045 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2046 editor.inline_blame_popover.take();
2047 }
2048 }
2049 EditorEvent::Edited { .. } => {
2050 if !vim_enabled(cx) {
2051 let (map, selections) = editor.selections.all_adjusted_display(cx);
2052 let pop_state = editor
2053 .change_list
2054 .last()
2055 .map(|previous| {
2056 previous.len() == selections.len()
2057 && previous.iter().enumerate().all(|(ix, p)| {
2058 p.to_display_point(&map).row()
2059 == selections[ix].head().row()
2060 })
2061 })
2062 .unwrap_or(false);
2063 let new_positions = selections
2064 .into_iter()
2065 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2066 .collect();
2067 editor
2068 .change_list
2069 .push_to_change_list(pop_state, new_positions);
2070 }
2071 }
2072 _ => (),
2073 },
2074 ));
2075
2076 if let Some(dap_store) = editor
2077 .project
2078 .as_ref()
2079 .map(|project| project.read(cx).dap_store())
2080 {
2081 let weak_editor = cx.weak_entity();
2082
2083 editor
2084 ._subscriptions
2085 .push(
2086 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2087 let session_entity = cx.entity();
2088 weak_editor
2089 .update(cx, |editor, cx| {
2090 editor._subscriptions.push(
2091 cx.subscribe(&session_entity, Self::on_debug_session_event),
2092 );
2093 })
2094 .ok();
2095 }),
2096 );
2097
2098 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2099 editor
2100 ._subscriptions
2101 .push(cx.subscribe(&session, Self::on_debug_session_event));
2102 }
2103 }
2104
2105 // skip adding the initial selection to selection history
2106 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2107 editor.end_selection(window, cx);
2108 editor.selection_history.mode = SelectionHistoryMode::Normal;
2109
2110 editor.scroll_manager.show_scrollbars(window, cx);
2111 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2112
2113 if full_mode {
2114 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2115 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2116
2117 if editor.git_blame_inline_enabled {
2118 editor.start_git_blame_inline(false, window, cx);
2119 }
2120
2121 editor.go_to_active_debug_line(window, cx);
2122
2123 if let Some(buffer) = buffer.read(cx).as_singleton() {
2124 if let Some(project) = editor.project.as_ref() {
2125 let handle = project.update(cx, |project, cx| {
2126 project.register_buffer_with_language_servers(&buffer, cx)
2127 });
2128 editor
2129 .registered_buffers
2130 .insert(buffer.read(cx).remote_id(), handle);
2131 }
2132 }
2133
2134 editor.minimap =
2135 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2136 editor.pull_diagnostics(None, window, cx);
2137 }
2138
2139 editor.report_editor_event("Editor Opened", None, cx);
2140 editor
2141 }
2142
2143 pub fn deploy_mouse_context_menu(
2144 &mut self,
2145 position: gpui::Point<Pixels>,
2146 context_menu: Entity<ContextMenu>,
2147 window: &mut Window,
2148 cx: &mut Context<Self>,
2149 ) {
2150 self.mouse_context_menu = Some(MouseContextMenu::new(
2151 self,
2152 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2153 context_menu,
2154 window,
2155 cx,
2156 ));
2157 }
2158
2159 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2160 self.mouse_context_menu
2161 .as_ref()
2162 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2163 }
2164
2165 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2166 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2167 }
2168
2169 fn key_context_internal(
2170 &self,
2171 has_active_edit_prediction: bool,
2172 window: &Window,
2173 cx: &App,
2174 ) -> KeyContext {
2175 let mut key_context = KeyContext::new_with_defaults();
2176 key_context.add("Editor");
2177 let mode = match self.mode {
2178 EditorMode::SingleLine { .. } => "single_line",
2179 EditorMode::AutoHeight { .. } => "auto_height",
2180 EditorMode::Minimap { .. } => "minimap",
2181 EditorMode::Full { .. } => "full",
2182 };
2183
2184 if EditorSettings::jupyter_enabled(cx) {
2185 key_context.add("jupyter");
2186 }
2187
2188 key_context.set("mode", mode);
2189 if self.pending_rename.is_some() {
2190 key_context.add("renaming");
2191 }
2192
2193 match self.context_menu.borrow().as_ref() {
2194 Some(CodeContextMenu::Completions(_)) => {
2195 key_context.add("menu");
2196 key_context.add("showing_completions");
2197 }
2198 Some(CodeContextMenu::CodeActions(_)) => {
2199 key_context.add("menu");
2200 key_context.add("showing_code_actions")
2201 }
2202 None => {}
2203 }
2204
2205 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2206 if !self.focus_handle(cx).contains_focused(window, cx)
2207 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2208 {
2209 for addon in self.addons.values() {
2210 addon.extend_key_context(&mut key_context, cx)
2211 }
2212 }
2213
2214 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2215 if let Some(extension) = singleton_buffer
2216 .read(cx)
2217 .file()
2218 .and_then(|file| file.path().extension()?.to_str())
2219 {
2220 key_context.set("extension", extension.to_string());
2221 }
2222 } else {
2223 key_context.add("multibuffer");
2224 }
2225
2226 if has_active_edit_prediction {
2227 if self.edit_prediction_in_conflict() {
2228 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2229 } else {
2230 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2231 key_context.add("copilot_suggestion");
2232 }
2233 }
2234
2235 if self.selection_mark_mode {
2236 key_context.add("selection_mode");
2237 }
2238
2239 key_context
2240 }
2241
2242 fn show_mouse_cursor(&mut self) {
2243 self.mouse_cursor_hidden = false;
2244 }
2245
2246 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2247 self.mouse_cursor_hidden = match origin {
2248 HideMouseCursorOrigin::TypingAction => {
2249 matches!(
2250 self.hide_mouse_mode,
2251 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2252 )
2253 }
2254 HideMouseCursorOrigin::MovementAction => {
2255 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2256 }
2257 };
2258 }
2259
2260 pub fn edit_prediction_in_conflict(&self) -> bool {
2261 if !self.show_edit_predictions_in_menu() {
2262 return false;
2263 }
2264
2265 let showing_completions = self
2266 .context_menu
2267 .borrow()
2268 .as_ref()
2269 .map_or(false, |context| {
2270 matches!(context, CodeContextMenu::Completions(_))
2271 });
2272
2273 showing_completions
2274 || self.edit_prediction_requires_modifier()
2275 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2276 // bindings to insert tab characters.
2277 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2278 }
2279
2280 pub fn accept_edit_prediction_keybind(
2281 &self,
2282 accept_partial: bool,
2283 window: &Window,
2284 cx: &App,
2285 ) -> AcceptEditPredictionBinding {
2286 let key_context = self.key_context_internal(true, window, cx);
2287 let in_conflict = self.edit_prediction_in_conflict();
2288
2289 let bindings = if accept_partial {
2290 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2291 } else {
2292 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2293 };
2294
2295 // TODO: if the binding contains multiple keystrokes, display all of them, not
2296 // just the first one.
2297 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2298 !in_conflict
2299 || binding
2300 .keystrokes()
2301 .first()
2302 .map_or(false, |keystroke| keystroke.modifiers.modified())
2303 }))
2304 }
2305
2306 pub fn new_file(
2307 workspace: &mut Workspace,
2308 _: &workspace::NewFile,
2309 window: &mut Window,
2310 cx: &mut Context<Workspace>,
2311 ) {
2312 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2313 "Failed to create buffer",
2314 window,
2315 cx,
2316 |e, _, _| match e.error_code() {
2317 ErrorCode::RemoteUpgradeRequired => Some(format!(
2318 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2319 e.error_tag("required").unwrap_or("the latest version")
2320 )),
2321 _ => None,
2322 },
2323 );
2324 }
2325
2326 pub fn new_in_workspace(
2327 workspace: &mut Workspace,
2328 window: &mut Window,
2329 cx: &mut Context<Workspace>,
2330 ) -> Task<Result<Entity<Editor>>> {
2331 let project = workspace.project().clone();
2332 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2333
2334 cx.spawn_in(window, async move |workspace, cx| {
2335 let buffer = create.await?;
2336 workspace.update_in(cx, |workspace, window, cx| {
2337 let editor =
2338 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2339 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2340 editor
2341 })
2342 })
2343 }
2344
2345 fn new_file_vertical(
2346 workspace: &mut Workspace,
2347 _: &workspace::NewFileSplitVertical,
2348 window: &mut Window,
2349 cx: &mut Context<Workspace>,
2350 ) {
2351 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2352 }
2353
2354 fn new_file_horizontal(
2355 workspace: &mut Workspace,
2356 _: &workspace::NewFileSplitHorizontal,
2357 window: &mut Window,
2358 cx: &mut Context<Workspace>,
2359 ) {
2360 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2361 }
2362
2363 fn new_file_in_direction(
2364 workspace: &mut Workspace,
2365 direction: SplitDirection,
2366 window: &mut Window,
2367 cx: &mut Context<Workspace>,
2368 ) {
2369 let project = workspace.project().clone();
2370 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2371
2372 cx.spawn_in(window, async move |workspace, cx| {
2373 let buffer = create.await?;
2374 workspace.update_in(cx, move |workspace, window, cx| {
2375 workspace.split_item(
2376 direction,
2377 Box::new(
2378 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2379 ),
2380 window,
2381 cx,
2382 )
2383 })?;
2384 anyhow::Ok(())
2385 })
2386 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2387 match e.error_code() {
2388 ErrorCode::RemoteUpgradeRequired => Some(format!(
2389 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2390 e.error_tag("required").unwrap_or("the latest version")
2391 )),
2392 _ => None,
2393 }
2394 });
2395 }
2396
2397 pub fn leader_id(&self) -> Option<CollaboratorId> {
2398 self.leader_id
2399 }
2400
2401 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2402 &self.buffer
2403 }
2404
2405 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2406 self.workspace.as_ref()?.0.upgrade()
2407 }
2408
2409 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2410 self.buffer().read(cx).title(cx)
2411 }
2412
2413 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2414 let git_blame_gutter_max_author_length = self
2415 .render_git_blame_gutter(cx)
2416 .then(|| {
2417 if let Some(blame) = self.blame.as_ref() {
2418 let max_author_length =
2419 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2420 Some(max_author_length)
2421 } else {
2422 None
2423 }
2424 })
2425 .flatten();
2426
2427 EditorSnapshot {
2428 mode: self.mode.clone(),
2429 show_gutter: self.show_gutter,
2430 show_line_numbers: self.show_line_numbers,
2431 show_git_diff_gutter: self.show_git_diff_gutter,
2432 show_code_actions: self.show_code_actions,
2433 show_runnables: self.show_runnables,
2434 show_breakpoints: self.show_breakpoints,
2435 git_blame_gutter_max_author_length,
2436 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2437 scroll_anchor: self.scroll_manager.anchor(),
2438 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2439 placeholder_text: self.placeholder_text.clone(),
2440 is_focused: self.focus_handle.is_focused(window),
2441 current_line_highlight: self
2442 .current_line_highlight
2443 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2444 gutter_hovered: self.gutter_hovered,
2445 }
2446 }
2447
2448 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2449 self.buffer.read(cx).language_at(point, cx)
2450 }
2451
2452 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2453 self.buffer.read(cx).read(cx).file_at(point).cloned()
2454 }
2455
2456 pub fn active_excerpt(
2457 &self,
2458 cx: &App,
2459 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2460 self.buffer
2461 .read(cx)
2462 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2463 }
2464
2465 pub fn mode(&self) -> &EditorMode {
2466 &self.mode
2467 }
2468
2469 pub fn set_mode(&mut self, mode: EditorMode) {
2470 self.mode = mode;
2471 }
2472
2473 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2474 self.collaboration_hub.as_deref()
2475 }
2476
2477 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2478 self.collaboration_hub = Some(hub);
2479 }
2480
2481 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2482 self.in_project_search = in_project_search;
2483 }
2484
2485 pub fn set_custom_context_menu(
2486 &mut self,
2487 f: impl 'static
2488 + Fn(
2489 &mut Self,
2490 DisplayPoint,
2491 &mut Window,
2492 &mut Context<Self>,
2493 ) -> Option<Entity<ui::ContextMenu>>,
2494 ) {
2495 self.custom_context_menu = Some(Box::new(f))
2496 }
2497
2498 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2499 self.completion_provider = provider;
2500 }
2501
2502 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2503 self.semantics_provider.clone()
2504 }
2505
2506 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2507 self.semantics_provider = provider;
2508 }
2509
2510 pub fn set_edit_prediction_provider<T>(
2511 &mut self,
2512 provider: Option<Entity<T>>,
2513 window: &mut Window,
2514 cx: &mut Context<Self>,
2515 ) where
2516 T: EditPredictionProvider,
2517 {
2518 self.edit_prediction_provider =
2519 provider.map(|provider| RegisteredInlineCompletionProvider {
2520 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2521 if this.focus_handle.is_focused(window) {
2522 this.update_visible_inline_completion(window, cx);
2523 }
2524 }),
2525 provider: Arc::new(provider),
2526 });
2527 self.update_edit_prediction_settings(cx);
2528 self.refresh_inline_completion(false, false, window, cx);
2529 }
2530
2531 pub fn placeholder_text(&self) -> Option<&str> {
2532 self.placeholder_text.as_deref()
2533 }
2534
2535 pub fn set_placeholder_text(
2536 &mut self,
2537 placeholder_text: impl Into<Arc<str>>,
2538 cx: &mut Context<Self>,
2539 ) {
2540 let placeholder_text = Some(placeholder_text.into());
2541 if self.placeholder_text != placeholder_text {
2542 self.placeholder_text = placeholder_text;
2543 cx.notify();
2544 }
2545 }
2546
2547 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2548 self.cursor_shape = cursor_shape;
2549
2550 // Disrupt blink for immediate user feedback that the cursor shape has changed
2551 self.blink_manager.update(cx, BlinkManager::show_cursor);
2552
2553 cx.notify();
2554 }
2555
2556 pub fn set_current_line_highlight(
2557 &mut self,
2558 current_line_highlight: Option<CurrentLineHighlight>,
2559 ) {
2560 self.current_line_highlight = current_line_highlight;
2561 }
2562
2563 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2564 self.collapse_matches = collapse_matches;
2565 }
2566
2567 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2568 let buffers = self.buffer.read(cx).all_buffers();
2569 let Some(project) = self.project.as_ref() else {
2570 return;
2571 };
2572 project.update(cx, |project, cx| {
2573 for buffer in buffers {
2574 self.registered_buffers
2575 .entry(buffer.read(cx).remote_id())
2576 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2577 }
2578 })
2579 }
2580
2581 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2582 if self.collapse_matches {
2583 return range.start..range.start;
2584 }
2585 range.clone()
2586 }
2587
2588 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2589 if self.display_map.read(cx).clip_at_line_ends != clip {
2590 self.display_map
2591 .update(cx, |map, _| map.clip_at_line_ends = clip);
2592 }
2593 }
2594
2595 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2596 self.input_enabled = input_enabled;
2597 }
2598
2599 pub fn set_inline_completions_hidden_for_vim_mode(
2600 &mut self,
2601 hidden: bool,
2602 window: &mut Window,
2603 cx: &mut Context<Self>,
2604 ) {
2605 if hidden != self.inline_completions_hidden_for_vim_mode {
2606 self.inline_completions_hidden_for_vim_mode = hidden;
2607 if hidden {
2608 self.update_visible_inline_completion(window, cx);
2609 } else {
2610 self.refresh_inline_completion(true, false, window, cx);
2611 }
2612 }
2613 }
2614
2615 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2616 self.menu_inline_completions_policy = value;
2617 }
2618
2619 pub fn set_autoindent(&mut self, autoindent: bool) {
2620 if autoindent {
2621 self.autoindent_mode = Some(AutoindentMode::EachLine);
2622 } else {
2623 self.autoindent_mode = None;
2624 }
2625 }
2626
2627 pub fn read_only(&self, cx: &App) -> bool {
2628 self.read_only || self.buffer.read(cx).read_only()
2629 }
2630
2631 pub fn set_read_only(&mut self, read_only: bool) {
2632 self.read_only = read_only;
2633 }
2634
2635 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2636 self.use_autoclose = autoclose;
2637 }
2638
2639 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2640 self.use_auto_surround = auto_surround;
2641 }
2642
2643 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2644 self.auto_replace_emoji_shortcode = auto_replace;
2645 }
2646
2647 pub fn toggle_edit_predictions(
2648 &mut self,
2649 _: &ToggleEditPrediction,
2650 window: &mut Window,
2651 cx: &mut Context<Self>,
2652 ) {
2653 if self.show_inline_completions_override.is_some() {
2654 self.set_show_edit_predictions(None, window, cx);
2655 } else {
2656 let show_edit_predictions = !self.edit_predictions_enabled();
2657 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2658 }
2659 }
2660
2661 pub fn set_show_edit_predictions(
2662 &mut self,
2663 show_edit_predictions: Option<bool>,
2664 window: &mut Window,
2665 cx: &mut Context<Self>,
2666 ) {
2667 self.show_inline_completions_override = show_edit_predictions;
2668 self.update_edit_prediction_settings(cx);
2669
2670 if let Some(false) = show_edit_predictions {
2671 self.discard_inline_completion(false, cx);
2672 } else {
2673 self.refresh_inline_completion(false, true, window, cx);
2674 }
2675 }
2676
2677 fn inline_completions_disabled_in_scope(
2678 &self,
2679 buffer: &Entity<Buffer>,
2680 buffer_position: language::Anchor,
2681 cx: &App,
2682 ) -> bool {
2683 let snapshot = buffer.read(cx).snapshot();
2684 let settings = snapshot.settings_at(buffer_position, cx);
2685
2686 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2687 return false;
2688 };
2689
2690 scope.override_name().map_or(false, |scope_name| {
2691 settings
2692 .edit_predictions_disabled_in
2693 .iter()
2694 .any(|s| s == scope_name)
2695 })
2696 }
2697
2698 pub fn set_use_modal_editing(&mut self, to: bool) {
2699 self.use_modal_editing = to;
2700 }
2701
2702 pub fn use_modal_editing(&self) -> bool {
2703 self.use_modal_editing
2704 }
2705
2706 fn selections_did_change(
2707 &mut self,
2708 local: bool,
2709 old_cursor_position: &Anchor,
2710 should_update_completions: bool,
2711 window: &mut Window,
2712 cx: &mut Context<Self>,
2713 ) {
2714 window.invalidate_character_coordinates();
2715
2716 // Copy selections to primary selection buffer
2717 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2718 if local {
2719 let selections = self.selections.all::<usize>(cx);
2720 let buffer_handle = self.buffer.read(cx).read(cx);
2721
2722 let mut text = String::new();
2723 for (index, selection) in selections.iter().enumerate() {
2724 let text_for_selection = buffer_handle
2725 .text_for_range(selection.start..selection.end)
2726 .collect::<String>();
2727
2728 text.push_str(&text_for_selection);
2729 if index != selections.len() - 1 {
2730 text.push('\n');
2731 }
2732 }
2733
2734 if !text.is_empty() {
2735 cx.write_to_primary(ClipboardItem::new_string(text));
2736 }
2737 }
2738
2739 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2740 self.buffer.update(cx, |buffer, cx| {
2741 buffer.set_active_selections(
2742 &self.selections.disjoint_anchors(),
2743 self.selections.line_mode,
2744 self.cursor_shape,
2745 cx,
2746 )
2747 });
2748 }
2749 let display_map = self
2750 .display_map
2751 .update(cx, |display_map, cx| display_map.snapshot(cx));
2752 let buffer = &display_map.buffer_snapshot;
2753 if self.selections.count() == 1 {
2754 self.add_selections_state = None;
2755 }
2756 self.select_next_state = None;
2757 self.select_prev_state = None;
2758 self.select_syntax_node_history.try_clear();
2759 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2760 self.snippet_stack
2761 .invalidate(&self.selections.disjoint_anchors(), buffer);
2762 self.take_rename(false, window, cx);
2763
2764 let newest_selection = self.selections.newest_anchor();
2765 let new_cursor_position = newest_selection.head();
2766 let selection_start = newest_selection.start;
2767
2768 self.push_to_nav_history(
2769 *old_cursor_position,
2770 Some(new_cursor_position.to_point(buffer)),
2771 false,
2772 cx,
2773 );
2774
2775 if local {
2776 if let Some(buffer_id) = new_cursor_position.buffer_id {
2777 if !self.registered_buffers.contains_key(&buffer_id) {
2778 if let Some(project) = self.project.as_ref() {
2779 project.update(cx, |project, cx| {
2780 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2781 return;
2782 };
2783 self.registered_buffers.insert(
2784 buffer_id,
2785 project.register_buffer_with_language_servers(&buffer, cx),
2786 );
2787 })
2788 }
2789 }
2790 }
2791
2792 let mut context_menu = self.context_menu.borrow_mut();
2793 let completion_menu = match context_menu.as_ref() {
2794 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2795 Some(CodeContextMenu::CodeActions(_)) => {
2796 *context_menu = None;
2797 None
2798 }
2799 None => None,
2800 };
2801 let completion_position = completion_menu.map(|menu| menu.initial_position);
2802 drop(context_menu);
2803
2804 if should_update_completions {
2805 if let Some(completion_position) = completion_position {
2806 let start_offset = selection_start.to_offset(buffer);
2807 let position_matches = start_offset == completion_position.to_offset(buffer);
2808 let continue_showing = if position_matches {
2809 if self.snippet_stack.is_empty() {
2810 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2811 } else {
2812 // Snippet choices can be shown even when the cursor is in whitespace.
2813 // Dismissing the menu when actions like backspace
2814 true
2815 }
2816 } else {
2817 false
2818 };
2819
2820 if continue_showing {
2821 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2822 } else {
2823 self.hide_context_menu(window, cx);
2824 }
2825 }
2826 }
2827
2828 hide_hover(self, cx);
2829
2830 if old_cursor_position.to_display_point(&display_map).row()
2831 != new_cursor_position.to_display_point(&display_map).row()
2832 {
2833 self.available_code_actions.take();
2834 }
2835 self.refresh_code_actions(window, cx);
2836 self.refresh_document_highlights(cx);
2837 self.refresh_selected_text_highlights(false, window, cx);
2838 refresh_matching_bracket_highlights(self, window, cx);
2839 self.update_visible_inline_completion(window, cx);
2840 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2841 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2842 self.inline_blame_popover.take();
2843 if self.git_blame_inline_enabled {
2844 self.start_inline_blame_timer(window, cx);
2845 }
2846 }
2847
2848 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2849 cx.emit(EditorEvent::SelectionsChanged { local });
2850
2851 let selections = &self.selections.disjoint;
2852 if selections.len() == 1 {
2853 cx.emit(SearchEvent::ActiveMatchChanged)
2854 }
2855 if local {
2856 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2857 let inmemory_selections = selections
2858 .iter()
2859 .map(|s| {
2860 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2861 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2862 })
2863 .collect();
2864 self.update_restoration_data(cx, |data| {
2865 data.selections = inmemory_selections;
2866 });
2867
2868 if WorkspaceSettings::get(None, cx).restore_on_startup
2869 != RestoreOnStartupBehavior::None
2870 {
2871 if let Some(workspace_id) =
2872 self.workspace.as_ref().and_then(|workspace| workspace.1)
2873 {
2874 let snapshot = self.buffer().read(cx).snapshot(cx);
2875 let selections = selections.clone();
2876 let background_executor = cx.background_executor().clone();
2877 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2878 self.serialize_selections = cx.background_spawn(async move {
2879 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2880 let db_selections = selections
2881 .iter()
2882 .map(|selection| {
2883 (
2884 selection.start.to_offset(&snapshot),
2885 selection.end.to_offset(&snapshot),
2886 )
2887 })
2888 .collect();
2889
2890 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2891 .await
2892 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2893 .log_err();
2894 });
2895 }
2896 }
2897 }
2898 }
2899
2900 cx.notify();
2901 }
2902
2903 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2904 use text::ToOffset as _;
2905 use text::ToPoint as _;
2906
2907 if self.mode.is_minimap()
2908 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2909 {
2910 return;
2911 }
2912
2913 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2914 return;
2915 };
2916
2917 let snapshot = singleton.read(cx).snapshot();
2918 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2919 let display_snapshot = display_map.snapshot(cx);
2920
2921 display_snapshot
2922 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2923 .map(|fold| {
2924 fold.range.start.text_anchor.to_point(&snapshot)
2925 ..fold.range.end.text_anchor.to_point(&snapshot)
2926 })
2927 .collect()
2928 });
2929 self.update_restoration_data(cx, |data| {
2930 data.folds = inmemory_folds;
2931 });
2932
2933 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2934 return;
2935 };
2936 let background_executor = cx.background_executor().clone();
2937 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2938 let db_folds = self.display_map.update(cx, |display_map, cx| {
2939 display_map
2940 .snapshot(cx)
2941 .folds_in_range(0..snapshot.len())
2942 .map(|fold| {
2943 (
2944 fold.range.start.text_anchor.to_offset(&snapshot),
2945 fold.range.end.text_anchor.to_offset(&snapshot),
2946 )
2947 })
2948 .collect()
2949 });
2950 self.serialize_folds = cx.background_spawn(async move {
2951 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2952 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2953 .await
2954 .with_context(|| {
2955 format!(
2956 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2957 )
2958 })
2959 .log_err();
2960 });
2961 }
2962
2963 pub fn sync_selections(
2964 &mut self,
2965 other: Entity<Editor>,
2966 cx: &mut Context<Self>,
2967 ) -> gpui::Subscription {
2968 let other_selections = other.read(cx).selections.disjoint.to_vec();
2969 self.selections.change_with(cx, |selections| {
2970 selections.select_anchors(other_selections);
2971 });
2972
2973 let other_subscription =
2974 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2975 EditorEvent::SelectionsChanged { local: true } => {
2976 let other_selections = other.read(cx).selections.disjoint.to_vec();
2977 if other_selections.is_empty() {
2978 return;
2979 }
2980 this.selections.change_with(cx, |selections| {
2981 selections.select_anchors(other_selections);
2982 });
2983 }
2984 _ => {}
2985 });
2986
2987 let this_subscription =
2988 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2989 EditorEvent::SelectionsChanged { local: true } => {
2990 let these_selections = this.selections.disjoint.to_vec();
2991 if these_selections.is_empty() {
2992 return;
2993 }
2994 other.update(cx, |other_editor, cx| {
2995 other_editor.selections.change_with(cx, |selections| {
2996 selections.select_anchors(these_selections);
2997 })
2998 });
2999 }
3000 _ => {}
3001 });
3002
3003 Subscription::join(other_subscription, this_subscription)
3004 }
3005
3006 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3007 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3008 /// effects of selection change occur at the end of the transaction.
3009 pub fn change_selections<R>(
3010 &mut self,
3011 autoscroll: Option<Autoscroll>,
3012 window: &mut Window,
3013 cx: &mut Context<Self>,
3014 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3015 ) -> R {
3016 self.change_selections_inner(true, autoscroll, window, cx, change)
3017 }
3018
3019 pub(crate) fn change_selections_without_updating_completions<R>(
3020 &mut self,
3021 autoscroll: Option<Autoscroll>,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3025 ) -> R {
3026 self.change_selections_inner(false, autoscroll, window, cx, change)
3027 }
3028
3029 fn change_selections_inner<R>(
3030 &mut self,
3031 should_update_completions: bool,
3032 autoscroll: Option<Autoscroll>,
3033 window: &mut Window,
3034 cx: &mut Context<Self>,
3035 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3036 ) -> R {
3037 if let Some(state) = &mut self.deferred_selection_effects_state {
3038 state.autoscroll = autoscroll.or(state.autoscroll);
3039 state.should_update_completions = should_update_completions;
3040 let (changed, result) = self.selections.change_with(cx, change);
3041 state.changed |= changed;
3042 return result;
3043 }
3044 let mut state = DeferredSelectionEffectsState {
3045 changed: false,
3046 should_update_completions,
3047 autoscroll,
3048 old_cursor_position: self.selections.newest_anchor().head(),
3049 history_entry: SelectionHistoryEntry {
3050 selections: self.selections.disjoint_anchors(),
3051 select_next_state: self.select_next_state.clone(),
3052 select_prev_state: self.select_prev_state.clone(),
3053 add_selections_state: self.add_selections_state.clone(),
3054 },
3055 };
3056 let (changed, result) = self.selections.change_with(cx, change);
3057 state.changed = state.changed || changed;
3058 if self.defer_selection_effects {
3059 self.deferred_selection_effects_state = Some(state);
3060 } else {
3061 self.apply_selection_effects(state, window, cx);
3062 }
3063 result
3064 }
3065
3066 /// Defers the effects of selection change, so that the effects of multiple calls to
3067 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3068 /// to selection history and the state of popovers based on selection position aren't
3069 /// erroneously updated.
3070 pub fn with_selection_effects_deferred<R>(
3071 &mut self,
3072 window: &mut Window,
3073 cx: &mut Context<Self>,
3074 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3075 ) -> R {
3076 let already_deferred = self.defer_selection_effects;
3077 self.defer_selection_effects = true;
3078 let result = update(self, window, cx);
3079 if !already_deferred {
3080 self.defer_selection_effects = false;
3081 if let Some(state) = self.deferred_selection_effects_state.take() {
3082 self.apply_selection_effects(state, window, cx);
3083 }
3084 }
3085 result
3086 }
3087
3088 fn apply_selection_effects(
3089 &mut self,
3090 state: DeferredSelectionEffectsState,
3091 window: &mut Window,
3092 cx: &mut Context<Self>,
3093 ) {
3094 if state.changed {
3095 self.selection_history.push(state.history_entry);
3096
3097 if let Some(autoscroll) = state.autoscroll {
3098 self.request_autoscroll(autoscroll, cx);
3099 }
3100
3101 let old_cursor_position = &state.old_cursor_position;
3102
3103 self.selections_did_change(
3104 true,
3105 &old_cursor_position,
3106 state.should_update_completions,
3107 window,
3108 cx,
3109 );
3110
3111 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3112 self.show_signature_help(&ShowSignatureHelp, window, cx);
3113 }
3114 }
3115 }
3116
3117 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3118 where
3119 I: IntoIterator<Item = (Range<S>, T)>,
3120 S: ToOffset,
3121 T: Into<Arc<str>>,
3122 {
3123 if self.read_only(cx) {
3124 return;
3125 }
3126
3127 self.buffer
3128 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3129 }
3130
3131 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3132 where
3133 I: IntoIterator<Item = (Range<S>, T)>,
3134 S: ToOffset,
3135 T: Into<Arc<str>>,
3136 {
3137 if self.read_only(cx) {
3138 return;
3139 }
3140
3141 self.buffer.update(cx, |buffer, cx| {
3142 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3143 });
3144 }
3145
3146 pub fn edit_with_block_indent<I, S, T>(
3147 &mut self,
3148 edits: I,
3149 original_indent_columns: Vec<Option<u32>>,
3150 cx: &mut Context<Self>,
3151 ) where
3152 I: IntoIterator<Item = (Range<S>, T)>,
3153 S: ToOffset,
3154 T: Into<Arc<str>>,
3155 {
3156 if self.read_only(cx) {
3157 return;
3158 }
3159
3160 self.buffer.update(cx, |buffer, cx| {
3161 buffer.edit(
3162 edits,
3163 Some(AutoindentMode::Block {
3164 original_indent_columns,
3165 }),
3166 cx,
3167 )
3168 });
3169 }
3170
3171 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3172 self.hide_context_menu(window, cx);
3173
3174 match phase {
3175 SelectPhase::Begin {
3176 position,
3177 add,
3178 click_count,
3179 } => self.begin_selection(position, add, click_count, window, cx),
3180 SelectPhase::BeginColumnar {
3181 position,
3182 goal_column,
3183 reset,
3184 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3185 SelectPhase::Extend {
3186 position,
3187 click_count,
3188 } => self.extend_selection(position, click_count, window, cx),
3189 SelectPhase::Update {
3190 position,
3191 goal_column,
3192 scroll_delta,
3193 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3194 SelectPhase::End => self.end_selection(window, cx),
3195 }
3196 }
3197
3198 fn extend_selection(
3199 &mut self,
3200 position: DisplayPoint,
3201 click_count: usize,
3202 window: &mut Window,
3203 cx: &mut Context<Self>,
3204 ) {
3205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3206 let tail = self.selections.newest::<usize>(cx).tail();
3207 self.begin_selection(position, false, click_count, window, cx);
3208
3209 let position = position.to_offset(&display_map, Bias::Left);
3210 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3211
3212 let mut pending_selection = self
3213 .selections
3214 .pending_anchor()
3215 .expect("extend_selection not called with pending selection");
3216 if position >= tail {
3217 pending_selection.start = tail_anchor;
3218 } else {
3219 pending_selection.end = tail_anchor;
3220 pending_selection.reversed = true;
3221 }
3222
3223 let mut pending_mode = self.selections.pending_mode().unwrap();
3224 match &mut pending_mode {
3225 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3226 _ => {}
3227 }
3228
3229 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3230
3231 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3232 s.set_pending(pending_selection, pending_mode)
3233 });
3234 }
3235
3236 fn begin_selection(
3237 &mut self,
3238 position: DisplayPoint,
3239 add: bool,
3240 click_count: usize,
3241 window: &mut Window,
3242 cx: &mut Context<Self>,
3243 ) {
3244 if !self.focus_handle.is_focused(window) {
3245 self.last_focused_descendant = None;
3246 window.focus(&self.focus_handle);
3247 }
3248
3249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3250 let buffer = &display_map.buffer_snapshot;
3251 let position = display_map.clip_point(position, Bias::Left);
3252
3253 let start;
3254 let end;
3255 let mode;
3256 let mut auto_scroll;
3257 match click_count {
3258 1 => {
3259 start = buffer.anchor_before(position.to_point(&display_map));
3260 end = start;
3261 mode = SelectMode::Character;
3262 auto_scroll = true;
3263 }
3264 2 => {
3265 let range = movement::surrounding_word(&display_map, position);
3266 start = buffer.anchor_before(range.start.to_point(&display_map));
3267 end = buffer.anchor_before(range.end.to_point(&display_map));
3268 mode = SelectMode::Word(start..end);
3269 auto_scroll = true;
3270 }
3271 3 => {
3272 let position = display_map
3273 .clip_point(position, Bias::Left)
3274 .to_point(&display_map);
3275 let line_start = display_map.prev_line_boundary(position).0;
3276 let next_line_start = buffer.clip_point(
3277 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3278 Bias::Left,
3279 );
3280 start = buffer.anchor_before(line_start);
3281 end = buffer.anchor_before(next_line_start);
3282 mode = SelectMode::Line(start..end);
3283 auto_scroll = true;
3284 }
3285 _ => {
3286 start = buffer.anchor_before(0);
3287 end = buffer.anchor_before(buffer.len());
3288 mode = SelectMode::All;
3289 auto_scroll = false;
3290 }
3291 }
3292 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3293
3294 let point_to_delete: Option<usize> = {
3295 let selected_points: Vec<Selection<Point>> =
3296 self.selections.disjoint_in_range(start..end, cx);
3297
3298 if !add || click_count > 1 {
3299 None
3300 } else if !selected_points.is_empty() {
3301 Some(selected_points[0].id)
3302 } else {
3303 let clicked_point_already_selected =
3304 self.selections.disjoint.iter().find(|selection| {
3305 selection.start.to_point(buffer) == start.to_point(buffer)
3306 || selection.end.to_point(buffer) == end.to_point(buffer)
3307 });
3308
3309 clicked_point_already_selected.map(|selection| selection.id)
3310 }
3311 };
3312
3313 let selections_count = self.selections.count();
3314
3315 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3316 if let Some(point_to_delete) = point_to_delete {
3317 s.delete(point_to_delete);
3318
3319 if selections_count == 1 {
3320 s.set_pending_anchor_range(start..end, mode);
3321 }
3322 } else {
3323 if !add {
3324 s.clear_disjoint();
3325 }
3326
3327 s.set_pending_anchor_range(start..end, mode);
3328 }
3329 });
3330 }
3331
3332 fn begin_columnar_selection(
3333 &mut self,
3334 position: DisplayPoint,
3335 goal_column: u32,
3336 reset: bool,
3337 window: &mut Window,
3338 cx: &mut Context<Self>,
3339 ) {
3340 if !self.focus_handle.is_focused(window) {
3341 self.last_focused_descendant = None;
3342 window.focus(&self.focus_handle);
3343 }
3344
3345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3346
3347 if reset {
3348 let pointer_position = display_map
3349 .buffer_snapshot
3350 .anchor_before(position.to_point(&display_map));
3351
3352 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3353 s.clear_disjoint();
3354 s.set_pending_anchor_range(
3355 pointer_position..pointer_position,
3356 SelectMode::Character,
3357 );
3358 });
3359 if position.column() != goal_column {
3360 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3361 } else {
3362 self.columnar_display_point = None;
3363 }
3364 }
3365
3366 let tail = self.selections.newest::<Point>(cx).tail();
3367 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3368
3369 if !reset {
3370 self.columnar_display_point = None;
3371 self.select_columns(
3372 tail.to_display_point(&display_map),
3373 position,
3374 goal_column,
3375 &display_map,
3376 window,
3377 cx,
3378 );
3379 }
3380 }
3381
3382 fn update_selection(
3383 &mut self,
3384 position: DisplayPoint,
3385 goal_column: u32,
3386 scroll_delta: gpui::Point<f32>,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 ) {
3390 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3391
3392 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3393 let tail = self
3394 .columnar_display_point
3395 .unwrap_or_else(|| tail.to_display_point(&display_map));
3396 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3397 } else if let Some(mut pending) = self.selections.pending_anchor() {
3398 let buffer = self.buffer.read(cx).snapshot(cx);
3399 let head;
3400 let tail;
3401 let mode = self.selections.pending_mode().unwrap();
3402 match &mode {
3403 SelectMode::Character => {
3404 head = position.to_point(&display_map);
3405 tail = pending.tail().to_point(&buffer);
3406 }
3407 SelectMode::Word(original_range) => {
3408 let original_display_range = original_range.start.to_display_point(&display_map)
3409 ..original_range.end.to_display_point(&display_map);
3410 let original_buffer_range = original_display_range.start.to_point(&display_map)
3411 ..original_display_range.end.to_point(&display_map);
3412 if movement::is_inside_word(&display_map, position)
3413 || original_display_range.contains(&position)
3414 {
3415 let word_range = movement::surrounding_word(&display_map, position);
3416 if word_range.start < original_display_range.start {
3417 head = word_range.start.to_point(&display_map);
3418 } else {
3419 head = word_range.end.to_point(&display_map);
3420 }
3421 } else {
3422 head = position.to_point(&display_map);
3423 }
3424
3425 if head <= original_buffer_range.start {
3426 tail = original_buffer_range.end;
3427 } else {
3428 tail = original_buffer_range.start;
3429 }
3430 }
3431 SelectMode::Line(original_range) => {
3432 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3433
3434 let position = display_map
3435 .clip_point(position, Bias::Left)
3436 .to_point(&display_map);
3437 let line_start = display_map.prev_line_boundary(position).0;
3438 let next_line_start = buffer.clip_point(
3439 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3440 Bias::Left,
3441 );
3442
3443 if line_start < original_range.start {
3444 head = line_start
3445 } else {
3446 head = next_line_start
3447 }
3448
3449 if head <= original_range.start {
3450 tail = original_range.end;
3451 } else {
3452 tail = original_range.start;
3453 }
3454 }
3455 SelectMode::All => {
3456 return;
3457 }
3458 };
3459
3460 if head < tail {
3461 pending.start = buffer.anchor_before(head);
3462 pending.end = buffer.anchor_before(tail);
3463 pending.reversed = true;
3464 } else {
3465 pending.start = buffer.anchor_before(tail);
3466 pending.end = buffer.anchor_before(head);
3467 pending.reversed = false;
3468 }
3469
3470 self.change_selections(None, window, cx, |s| {
3471 s.set_pending(pending, mode);
3472 });
3473 } else {
3474 log::error!("update_selection dispatched with no pending selection");
3475 return;
3476 }
3477
3478 self.apply_scroll_delta(scroll_delta, window, cx);
3479 cx.notify();
3480 }
3481
3482 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3483 self.columnar_selection_tail.take();
3484 if self.selections.pending_anchor().is_some() {
3485 let selections = self.selections.all::<usize>(cx);
3486 self.change_selections(None, window, cx, |s| {
3487 s.select(selections);
3488 s.clear_pending();
3489 });
3490 }
3491 }
3492
3493 fn select_columns(
3494 &mut self,
3495 tail: DisplayPoint,
3496 head: DisplayPoint,
3497 goal_column: u32,
3498 display_map: &DisplaySnapshot,
3499 window: &mut Window,
3500 cx: &mut Context<Self>,
3501 ) {
3502 let start_row = cmp::min(tail.row(), head.row());
3503 let end_row = cmp::max(tail.row(), head.row());
3504 let start_column = cmp::min(tail.column(), goal_column);
3505 let end_column = cmp::max(tail.column(), goal_column);
3506 let reversed = start_column < tail.column();
3507
3508 let selection_ranges = (start_row.0..=end_row.0)
3509 .map(DisplayRow)
3510 .filter_map(|row| {
3511 if !display_map.is_block_line(row) {
3512 let start = display_map
3513 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3514 .to_point(display_map);
3515 let end = display_map
3516 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3517 .to_point(display_map);
3518 if reversed {
3519 Some(end..start)
3520 } else {
3521 Some(start..end)
3522 }
3523 } else {
3524 None
3525 }
3526 })
3527 .collect::<Vec<_>>();
3528
3529 let mut non_empty_ranges = selection_ranges
3530 .iter()
3531 .filter(|selection_range| selection_range.start != selection_range.end)
3532 .peekable();
3533
3534 let ranges = if non_empty_ranges.peek().is_some() {
3535 non_empty_ranges.cloned().collect()
3536 } else {
3537 selection_ranges
3538 };
3539
3540 self.change_selections(None, window, cx, |s| {
3541 s.select_ranges(ranges);
3542 });
3543 cx.notify();
3544 }
3545
3546 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3547 self.selections
3548 .all_adjusted(cx)
3549 .iter()
3550 .any(|selection| !selection.is_empty())
3551 }
3552
3553 pub fn has_pending_nonempty_selection(&self) -> bool {
3554 let pending_nonempty_selection = match self.selections.pending_anchor() {
3555 Some(Selection { start, end, .. }) => start != end,
3556 None => false,
3557 };
3558
3559 pending_nonempty_selection
3560 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3561 }
3562
3563 pub fn has_pending_selection(&self) -> bool {
3564 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3565 }
3566
3567 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3568 self.selection_mark_mode = false;
3569 self.selection_drag_state = SelectionDragState::None;
3570
3571 if self.clear_expanded_diff_hunks(cx) {
3572 cx.notify();
3573 return;
3574 }
3575 if self.dismiss_menus_and_popups(true, window, cx) {
3576 return;
3577 }
3578
3579 if self.mode.is_full()
3580 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3581 {
3582 return;
3583 }
3584
3585 cx.propagate();
3586 }
3587
3588 pub fn dismiss_menus_and_popups(
3589 &mut self,
3590 is_user_requested: bool,
3591 window: &mut Window,
3592 cx: &mut Context<Self>,
3593 ) -> bool {
3594 if self.take_rename(false, window, cx).is_some() {
3595 return true;
3596 }
3597
3598 if hide_hover(self, cx) {
3599 return true;
3600 }
3601
3602 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3603 return true;
3604 }
3605
3606 if self.hide_context_menu(window, cx).is_some() {
3607 return true;
3608 }
3609
3610 if self.mouse_context_menu.take().is_some() {
3611 return true;
3612 }
3613
3614 if is_user_requested && self.discard_inline_completion(true, cx) {
3615 return true;
3616 }
3617
3618 if self.snippet_stack.pop().is_some() {
3619 return true;
3620 }
3621
3622 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3623 self.dismiss_diagnostics(cx);
3624 return true;
3625 }
3626
3627 false
3628 }
3629
3630 fn linked_editing_ranges_for(
3631 &self,
3632 selection: Range<text::Anchor>,
3633 cx: &App,
3634 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3635 if self.linked_edit_ranges.is_empty() {
3636 return None;
3637 }
3638 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3639 selection.end.buffer_id.and_then(|end_buffer_id| {
3640 if selection.start.buffer_id != Some(end_buffer_id) {
3641 return None;
3642 }
3643 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3644 let snapshot = buffer.read(cx).snapshot();
3645 self.linked_edit_ranges
3646 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3647 .map(|ranges| (ranges, snapshot, buffer))
3648 })?;
3649 use text::ToOffset as TO;
3650 // find offset from the start of current range to current cursor position
3651 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3652
3653 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3654 let start_difference = start_offset - start_byte_offset;
3655 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3656 let end_difference = end_offset - start_byte_offset;
3657 // Current range has associated linked ranges.
3658 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3659 for range in linked_ranges.iter() {
3660 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3661 let end_offset = start_offset + end_difference;
3662 let start_offset = start_offset + start_difference;
3663 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3664 continue;
3665 }
3666 if self.selections.disjoint_anchor_ranges().any(|s| {
3667 if s.start.buffer_id != selection.start.buffer_id
3668 || s.end.buffer_id != selection.end.buffer_id
3669 {
3670 return false;
3671 }
3672 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3673 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3674 }) {
3675 continue;
3676 }
3677 let start = buffer_snapshot.anchor_after(start_offset);
3678 let end = buffer_snapshot.anchor_after(end_offset);
3679 linked_edits
3680 .entry(buffer.clone())
3681 .or_default()
3682 .push(start..end);
3683 }
3684 Some(linked_edits)
3685 }
3686
3687 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3688 let text: Arc<str> = text.into();
3689
3690 if self.read_only(cx) {
3691 return;
3692 }
3693
3694 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3695
3696 let selections = self.selections.all_adjusted(cx);
3697 let mut bracket_inserted = false;
3698 let mut edits = Vec::new();
3699 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3700 let mut new_selections = Vec::with_capacity(selections.len());
3701 let mut new_autoclose_regions = Vec::new();
3702 let snapshot = self.buffer.read(cx).read(cx);
3703 let mut clear_linked_edit_ranges = false;
3704
3705 for (selection, autoclose_region) in
3706 self.selections_with_autoclose_regions(selections, &snapshot)
3707 {
3708 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3709 // Determine if the inserted text matches the opening or closing
3710 // bracket of any of this language's bracket pairs.
3711 let mut bracket_pair = None;
3712 let mut is_bracket_pair_start = false;
3713 let mut is_bracket_pair_end = false;
3714 if !text.is_empty() {
3715 let mut bracket_pair_matching_end = None;
3716 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3717 // and they are removing the character that triggered IME popup.
3718 for (pair, enabled) in scope.brackets() {
3719 if !pair.close && !pair.surround {
3720 continue;
3721 }
3722
3723 if enabled && pair.start.ends_with(text.as_ref()) {
3724 let prefix_len = pair.start.len() - text.len();
3725 let preceding_text_matches_prefix = prefix_len == 0
3726 || (selection.start.column >= (prefix_len as u32)
3727 && snapshot.contains_str_at(
3728 Point::new(
3729 selection.start.row,
3730 selection.start.column - (prefix_len as u32),
3731 ),
3732 &pair.start[..prefix_len],
3733 ));
3734 if preceding_text_matches_prefix {
3735 bracket_pair = Some(pair.clone());
3736 is_bracket_pair_start = true;
3737 break;
3738 }
3739 }
3740 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3741 {
3742 // take first bracket pair matching end, but don't break in case a later bracket
3743 // pair matches start
3744 bracket_pair_matching_end = Some(pair.clone());
3745 }
3746 }
3747 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3748 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3749 is_bracket_pair_end = true;
3750 }
3751 }
3752
3753 if let Some(bracket_pair) = bracket_pair {
3754 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3755 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3756 let auto_surround =
3757 self.use_auto_surround && snapshot_settings.use_auto_surround;
3758 if selection.is_empty() {
3759 if is_bracket_pair_start {
3760 // If the inserted text is a suffix of an opening bracket and the
3761 // selection is preceded by the rest of the opening bracket, then
3762 // insert the closing bracket.
3763 let following_text_allows_autoclose = snapshot
3764 .chars_at(selection.start)
3765 .next()
3766 .map_or(true, |c| scope.should_autoclose_before(c));
3767
3768 let preceding_text_allows_autoclose = selection.start.column == 0
3769 || snapshot.reversed_chars_at(selection.start).next().map_or(
3770 true,
3771 |c| {
3772 bracket_pair.start != bracket_pair.end
3773 || !snapshot
3774 .char_classifier_at(selection.start)
3775 .is_word(c)
3776 },
3777 );
3778
3779 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3780 && bracket_pair.start.len() == 1
3781 {
3782 let target = bracket_pair.start.chars().next().unwrap();
3783 let current_line_count = snapshot
3784 .reversed_chars_at(selection.start)
3785 .take_while(|&c| c != '\n')
3786 .filter(|&c| c == target)
3787 .count();
3788 current_line_count % 2 == 1
3789 } else {
3790 false
3791 };
3792
3793 if autoclose
3794 && bracket_pair.close
3795 && following_text_allows_autoclose
3796 && preceding_text_allows_autoclose
3797 && !is_closing_quote
3798 {
3799 let anchor = snapshot.anchor_before(selection.end);
3800 new_selections.push((selection.map(|_| anchor), text.len()));
3801 new_autoclose_regions.push((
3802 anchor,
3803 text.len(),
3804 selection.id,
3805 bracket_pair.clone(),
3806 ));
3807 edits.push((
3808 selection.range(),
3809 format!("{}{}", text, bracket_pair.end).into(),
3810 ));
3811 bracket_inserted = true;
3812 continue;
3813 }
3814 }
3815
3816 if let Some(region) = autoclose_region {
3817 // If the selection is followed by an auto-inserted closing bracket,
3818 // then don't insert that closing bracket again; just move the selection
3819 // past the closing bracket.
3820 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3821 && text.as_ref() == region.pair.end.as_str();
3822 if should_skip {
3823 let anchor = snapshot.anchor_after(selection.end);
3824 new_selections
3825 .push((selection.map(|_| anchor), region.pair.end.len()));
3826 continue;
3827 }
3828 }
3829
3830 let always_treat_brackets_as_autoclosed = snapshot
3831 .language_settings_at(selection.start, cx)
3832 .always_treat_brackets_as_autoclosed;
3833 if always_treat_brackets_as_autoclosed
3834 && is_bracket_pair_end
3835 && snapshot.contains_str_at(selection.end, text.as_ref())
3836 {
3837 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3838 // and the inserted text is a closing bracket and the selection is followed
3839 // by the closing bracket then move the selection past the closing bracket.
3840 let anchor = snapshot.anchor_after(selection.end);
3841 new_selections.push((selection.map(|_| anchor), text.len()));
3842 continue;
3843 }
3844 }
3845 // If an opening bracket is 1 character long and is typed while
3846 // text is selected, then surround that text with the bracket pair.
3847 else if auto_surround
3848 && bracket_pair.surround
3849 && is_bracket_pair_start
3850 && bracket_pair.start.chars().count() == 1
3851 {
3852 edits.push((selection.start..selection.start, text.clone()));
3853 edits.push((
3854 selection.end..selection.end,
3855 bracket_pair.end.as_str().into(),
3856 ));
3857 bracket_inserted = true;
3858 new_selections.push((
3859 Selection {
3860 id: selection.id,
3861 start: snapshot.anchor_after(selection.start),
3862 end: snapshot.anchor_before(selection.end),
3863 reversed: selection.reversed,
3864 goal: selection.goal,
3865 },
3866 0,
3867 ));
3868 continue;
3869 }
3870 }
3871 }
3872
3873 if self.auto_replace_emoji_shortcode
3874 && selection.is_empty()
3875 && text.as_ref().ends_with(':')
3876 {
3877 if let Some(possible_emoji_short_code) =
3878 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3879 {
3880 if !possible_emoji_short_code.is_empty() {
3881 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3882 let emoji_shortcode_start = Point::new(
3883 selection.start.row,
3884 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3885 );
3886
3887 // Remove shortcode from buffer
3888 edits.push((
3889 emoji_shortcode_start..selection.start,
3890 "".to_string().into(),
3891 ));
3892 new_selections.push((
3893 Selection {
3894 id: selection.id,
3895 start: snapshot.anchor_after(emoji_shortcode_start),
3896 end: snapshot.anchor_before(selection.start),
3897 reversed: selection.reversed,
3898 goal: selection.goal,
3899 },
3900 0,
3901 ));
3902
3903 // Insert emoji
3904 let selection_start_anchor = snapshot.anchor_after(selection.start);
3905 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3906 edits.push((selection.start..selection.end, emoji.to_string().into()));
3907
3908 continue;
3909 }
3910 }
3911 }
3912 }
3913
3914 // If not handling any auto-close operation, then just replace the selected
3915 // text with the given input and move the selection to the end of the
3916 // newly inserted text.
3917 let anchor = snapshot.anchor_after(selection.end);
3918 if !self.linked_edit_ranges.is_empty() {
3919 let start_anchor = snapshot.anchor_before(selection.start);
3920
3921 let is_word_char = text.chars().next().map_or(true, |char| {
3922 let classifier = snapshot
3923 .char_classifier_at(start_anchor.to_offset(&snapshot))
3924 .ignore_punctuation(true);
3925 classifier.is_word(char)
3926 });
3927
3928 if is_word_char {
3929 if let Some(ranges) = self
3930 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3931 {
3932 for (buffer, edits) in ranges {
3933 linked_edits
3934 .entry(buffer.clone())
3935 .or_default()
3936 .extend(edits.into_iter().map(|range| (range, text.clone())));
3937 }
3938 }
3939 } else {
3940 clear_linked_edit_ranges = true;
3941 }
3942 }
3943
3944 new_selections.push((selection.map(|_| anchor), 0));
3945 edits.push((selection.start..selection.end, text.clone()));
3946 }
3947
3948 drop(snapshot);
3949
3950 self.transact(window, cx, |this, window, cx| {
3951 if clear_linked_edit_ranges {
3952 this.linked_edit_ranges.clear();
3953 }
3954 let initial_buffer_versions =
3955 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3956
3957 this.buffer.update(cx, |buffer, cx| {
3958 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3959 });
3960 for (buffer, edits) in linked_edits {
3961 buffer.update(cx, |buffer, cx| {
3962 let snapshot = buffer.snapshot();
3963 let edits = edits
3964 .into_iter()
3965 .map(|(range, text)| {
3966 use text::ToPoint as TP;
3967 let end_point = TP::to_point(&range.end, &snapshot);
3968 let start_point = TP::to_point(&range.start, &snapshot);
3969 (start_point..end_point, text)
3970 })
3971 .sorted_by_key(|(range, _)| range.start);
3972 buffer.edit(edits, None, cx);
3973 })
3974 }
3975 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3976 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3977 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3978 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3979 .zip(new_selection_deltas)
3980 .map(|(selection, delta)| Selection {
3981 id: selection.id,
3982 start: selection.start + delta,
3983 end: selection.end + delta,
3984 reversed: selection.reversed,
3985 goal: SelectionGoal::None,
3986 })
3987 .collect::<Vec<_>>();
3988
3989 let mut i = 0;
3990 for (position, delta, selection_id, pair) in new_autoclose_regions {
3991 let position = position.to_offset(&map.buffer_snapshot) + delta;
3992 let start = map.buffer_snapshot.anchor_before(position);
3993 let end = map.buffer_snapshot.anchor_after(position);
3994 while let Some(existing_state) = this.autoclose_regions.get(i) {
3995 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3996 Ordering::Less => i += 1,
3997 Ordering::Greater => break,
3998 Ordering::Equal => {
3999 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4000 Ordering::Less => i += 1,
4001 Ordering::Equal => break,
4002 Ordering::Greater => break,
4003 }
4004 }
4005 }
4006 }
4007 this.autoclose_regions.insert(
4008 i,
4009 AutocloseRegion {
4010 selection_id,
4011 range: start..end,
4012 pair,
4013 },
4014 );
4015 }
4016
4017 let had_active_inline_completion = this.has_active_inline_completion();
4018 this.change_selections_without_updating_completions(
4019 Some(Autoscroll::fit()),
4020 window,
4021 cx,
4022 |s| s.select(new_selections),
4023 );
4024
4025 if !bracket_inserted {
4026 if let Some(on_type_format_task) =
4027 this.trigger_on_type_formatting(text.to_string(), window, cx)
4028 {
4029 on_type_format_task.detach_and_log_err(cx);
4030 }
4031 }
4032
4033 let editor_settings = EditorSettings::get_global(cx);
4034 if bracket_inserted
4035 && (editor_settings.auto_signature_help
4036 || editor_settings.show_signature_help_after_edits)
4037 {
4038 this.show_signature_help(&ShowSignatureHelp, window, cx);
4039 }
4040
4041 let trigger_in_words =
4042 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4043 if this.hard_wrap.is_some() {
4044 let latest: Range<Point> = this.selections.newest(cx).range();
4045 if latest.is_empty()
4046 && this
4047 .buffer()
4048 .read(cx)
4049 .snapshot(cx)
4050 .line_len(MultiBufferRow(latest.start.row))
4051 == latest.start.column
4052 {
4053 this.rewrap_impl(
4054 RewrapOptions {
4055 override_language_settings: true,
4056 preserve_existing_whitespace: true,
4057 },
4058 cx,
4059 )
4060 }
4061 }
4062 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4063 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4064 this.refresh_inline_completion(true, false, window, cx);
4065 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4066 });
4067 }
4068
4069 fn find_possible_emoji_shortcode_at_position(
4070 snapshot: &MultiBufferSnapshot,
4071 position: Point,
4072 ) -> Option<String> {
4073 let mut chars = Vec::new();
4074 let mut found_colon = false;
4075 for char in snapshot.reversed_chars_at(position).take(100) {
4076 // Found a possible emoji shortcode in the middle of the buffer
4077 if found_colon {
4078 if char.is_whitespace() {
4079 chars.reverse();
4080 return Some(chars.iter().collect());
4081 }
4082 // If the previous character is not a whitespace, we are in the middle of a word
4083 // and we only want to complete the shortcode if the word is made up of other emojis
4084 let mut containing_word = String::new();
4085 for ch in snapshot
4086 .reversed_chars_at(position)
4087 .skip(chars.len() + 1)
4088 .take(100)
4089 {
4090 if ch.is_whitespace() {
4091 break;
4092 }
4093 containing_word.push(ch);
4094 }
4095 let containing_word = containing_word.chars().rev().collect::<String>();
4096 if util::word_consists_of_emojis(containing_word.as_str()) {
4097 chars.reverse();
4098 return Some(chars.iter().collect());
4099 }
4100 }
4101
4102 if char.is_whitespace() || !char.is_ascii() {
4103 return None;
4104 }
4105 if char == ':' {
4106 found_colon = true;
4107 } else {
4108 chars.push(char);
4109 }
4110 }
4111 // Found a possible emoji shortcode at the beginning of the buffer
4112 chars.reverse();
4113 Some(chars.iter().collect())
4114 }
4115
4116 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4118 self.transact(window, cx, |this, window, cx| {
4119 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4120 let selections = this.selections.all::<usize>(cx);
4121 let multi_buffer = this.buffer.read(cx);
4122 let buffer = multi_buffer.snapshot(cx);
4123 selections
4124 .iter()
4125 .map(|selection| {
4126 let start_point = selection.start.to_point(&buffer);
4127 let mut existing_indent =
4128 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4129 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4130 let start = selection.start;
4131 let end = selection.end;
4132 let selection_is_empty = start == end;
4133 let language_scope = buffer.language_scope_at(start);
4134 let (
4135 comment_delimiter,
4136 doc_delimiter,
4137 insert_extra_newline,
4138 indent_on_newline,
4139 indent_on_extra_newline,
4140 ) = if let Some(language) = &language_scope {
4141 let mut insert_extra_newline =
4142 insert_extra_newline_brackets(&buffer, start..end, language)
4143 || insert_extra_newline_tree_sitter(&buffer, start..end);
4144
4145 // Comment extension on newline is allowed only for cursor selections
4146 let comment_delimiter = maybe!({
4147 if !selection_is_empty {
4148 return None;
4149 }
4150
4151 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4152 return None;
4153 }
4154
4155 let delimiters = language.line_comment_prefixes();
4156 let max_len_of_delimiter =
4157 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4158 let (snapshot, range) =
4159 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4160
4161 let num_of_whitespaces = snapshot
4162 .chars_for_range(range.clone())
4163 .take_while(|c| c.is_whitespace())
4164 .count();
4165 let comment_candidate = snapshot
4166 .chars_for_range(range)
4167 .skip(num_of_whitespaces)
4168 .take(max_len_of_delimiter)
4169 .collect::<String>();
4170 let (delimiter, trimmed_len) = delimiters
4171 .iter()
4172 .filter_map(|delimiter| {
4173 let prefix = delimiter.trim_end();
4174 if comment_candidate.starts_with(prefix) {
4175 Some((delimiter, prefix.len()))
4176 } else {
4177 None
4178 }
4179 })
4180 .max_by_key(|(_, len)| *len)?;
4181
4182 let cursor_is_placed_after_comment_marker =
4183 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4184 if cursor_is_placed_after_comment_marker {
4185 Some(delimiter.clone())
4186 } else {
4187 None
4188 }
4189 });
4190
4191 let mut indent_on_newline = IndentSize::spaces(0);
4192 let mut indent_on_extra_newline = IndentSize::spaces(0);
4193
4194 let doc_delimiter = maybe!({
4195 if !selection_is_empty {
4196 return None;
4197 }
4198
4199 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4200 return None;
4201 }
4202
4203 let DocumentationConfig {
4204 start: start_tag,
4205 end: end_tag,
4206 prefix: delimiter,
4207 tab_size: len,
4208 } = language.documentation()?;
4209
4210 let is_within_block_comment = buffer
4211 .language_scope_at(start_point)
4212 .is_some_and(|scope| scope.override_name() == Some("comment"));
4213 if !is_within_block_comment {
4214 return None;
4215 }
4216
4217 let (snapshot, range) =
4218 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4219
4220 let num_of_whitespaces = snapshot
4221 .chars_for_range(range.clone())
4222 .take_while(|c| c.is_whitespace())
4223 .count();
4224
4225 // 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.
4226 let column = start_point.column;
4227 let cursor_is_after_start_tag = {
4228 let start_tag_len = start_tag.len();
4229 let start_tag_line = snapshot
4230 .chars_for_range(range.clone())
4231 .skip(num_of_whitespaces)
4232 .take(start_tag_len)
4233 .collect::<String>();
4234 if start_tag_line.starts_with(start_tag.as_ref()) {
4235 num_of_whitespaces + start_tag_len <= column as usize
4236 } else {
4237 false
4238 }
4239 };
4240
4241 let cursor_is_after_delimiter = {
4242 let delimiter_trim = delimiter.trim_end();
4243 let delimiter_line = snapshot
4244 .chars_for_range(range.clone())
4245 .skip(num_of_whitespaces)
4246 .take(delimiter_trim.len())
4247 .collect::<String>();
4248 if delimiter_line.starts_with(delimiter_trim) {
4249 num_of_whitespaces + delimiter_trim.len() <= column as usize
4250 } else {
4251 false
4252 }
4253 };
4254
4255 let cursor_is_before_end_tag_if_exists = {
4256 let mut char_position = 0u32;
4257 let mut end_tag_offset = None;
4258
4259 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4260 if let Some(byte_pos) = chunk.find(&**end_tag) {
4261 let chars_before_match =
4262 chunk[..byte_pos].chars().count() as u32;
4263 end_tag_offset =
4264 Some(char_position + chars_before_match);
4265 break 'outer;
4266 }
4267 char_position += chunk.chars().count() as u32;
4268 }
4269
4270 if let Some(end_tag_offset) = end_tag_offset {
4271 let cursor_is_before_end_tag = column <= end_tag_offset;
4272 if cursor_is_after_start_tag {
4273 if cursor_is_before_end_tag {
4274 insert_extra_newline = true;
4275 }
4276 let cursor_is_at_start_of_end_tag =
4277 column == end_tag_offset;
4278 if cursor_is_at_start_of_end_tag {
4279 indent_on_extra_newline.len = (*len).into();
4280 }
4281 }
4282 cursor_is_before_end_tag
4283 } else {
4284 true
4285 }
4286 };
4287
4288 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4289 && cursor_is_before_end_tag_if_exists
4290 {
4291 if cursor_is_after_start_tag {
4292 indent_on_newline.len = (*len).into();
4293 }
4294 Some(delimiter.clone())
4295 } else {
4296 None
4297 }
4298 });
4299
4300 (
4301 comment_delimiter,
4302 doc_delimiter,
4303 insert_extra_newline,
4304 indent_on_newline,
4305 indent_on_extra_newline,
4306 )
4307 } else {
4308 (
4309 None,
4310 None,
4311 false,
4312 IndentSize::default(),
4313 IndentSize::default(),
4314 )
4315 };
4316
4317 let prevent_auto_indent = doc_delimiter.is_some();
4318 let delimiter = comment_delimiter.or(doc_delimiter);
4319
4320 let capacity_for_delimiter =
4321 delimiter.as_deref().map(str::len).unwrap_or_default();
4322 let mut new_text = String::with_capacity(
4323 1 + capacity_for_delimiter
4324 + existing_indent.len as usize
4325 + indent_on_newline.len as usize
4326 + indent_on_extra_newline.len as usize,
4327 );
4328 new_text.push('\n');
4329 new_text.extend(existing_indent.chars());
4330 new_text.extend(indent_on_newline.chars());
4331
4332 if let Some(delimiter) = &delimiter {
4333 new_text.push_str(delimiter);
4334 }
4335
4336 if insert_extra_newline {
4337 new_text.push('\n');
4338 new_text.extend(existing_indent.chars());
4339 new_text.extend(indent_on_extra_newline.chars());
4340 }
4341
4342 let anchor = buffer.anchor_after(end);
4343 let new_selection = selection.map(|_| anchor);
4344 (
4345 ((start..end, new_text), prevent_auto_indent),
4346 (insert_extra_newline, new_selection),
4347 )
4348 })
4349 .unzip()
4350 };
4351
4352 let mut auto_indent_edits = Vec::new();
4353 let mut edits = Vec::new();
4354 for (edit, prevent_auto_indent) in edits_with_flags {
4355 if prevent_auto_indent {
4356 edits.push(edit);
4357 } else {
4358 auto_indent_edits.push(edit);
4359 }
4360 }
4361 if !edits.is_empty() {
4362 this.edit(edits, cx);
4363 }
4364 if !auto_indent_edits.is_empty() {
4365 this.edit_with_autoindent(auto_indent_edits, cx);
4366 }
4367
4368 let buffer = this.buffer.read(cx).snapshot(cx);
4369 let new_selections = selection_info
4370 .into_iter()
4371 .map(|(extra_newline_inserted, new_selection)| {
4372 let mut cursor = new_selection.end.to_point(&buffer);
4373 if extra_newline_inserted {
4374 cursor.row -= 1;
4375 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4376 }
4377 new_selection.map(|_| cursor)
4378 })
4379 .collect();
4380
4381 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4382 s.select(new_selections)
4383 });
4384 this.refresh_inline_completion(true, false, window, cx);
4385 });
4386 }
4387
4388 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4389 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4390
4391 let buffer = self.buffer.read(cx);
4392 let snapshot = buffer.snapshot(cx);
4393
4394 let mut edits = Vec::new();
4395 let mut rows = Vec::new();
4396
4397 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4398 let cursor = selection.head();
4399 let row = cursor.row;
4400
4401 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4402
4403 let newline = "\n".to_string();
4404 edits.push((start_of_line..start_of_line, newline));
4405
4406 rows.push(row + rows_inserted as u32);
4407 }
4408
4409 self.transact(window, cx, |editor, window, cx| {
4410 editor.edit(edits, cx);
4411
4412 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4413 let mut index = 0;
4414 s.move_cursors_with(|map, _, _| {
4415 let row = rows[index];
4416 index += 1;
4417
4418 let point = Point::new(row, 0);
4419 let boundary = map.next_line_boundary(point).1;
4420 let clipped = map.clip_point(boundary, Bias::Left);
4421
4422 (clipped, SelectionGoal::None)
4423 });
4424 });
4425
4426 let mut indent_edits = Vec::new();
4427 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4428 for row in rows {
4429 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4430 for (row, indent) in indents {
4431 if indent.len == 0 {
4432 continue;
4433 }
4434
4435 let text = match indent.kind {
4436 IndentKind::Space => " ".repeat(indent.len as usize),
4437 IndentKind::Tab => "\t".repeat(indent.len as usize),
4438 };
4439 let point = Point::new(row.0, 0);
4440 indent_edits.push((point..point, text));
4441 }
4442 }
4443 editor.edit(indent_edits, cx);
4444 });
4445 }
4446
4447 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4448 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4449
4450 let buffer = self.buffer.read(cx);
4451 let snapshot = buffer.snapshot(cx);
4452
4453 let mut edits = Vec::new();
4454 let mut rows = Vec::new();
4455 let mut rows_inserted = 0;
4456
4457 for selection in self.selections.all_adjusted(cx) {
4458 let cursor = selection.head();
4459 let row = cursor.row;
4460
4461 let point = Point::new(row + 1, 0);
4462 let start_of_line = snapshot.clip_point(point, Bias::Left);
4463
4464 let newline = "\n".to_string();
4465 edits.push((start_of_line..start_of_line, newline));
4466
4467 rows_inserted += 1;
4468 rows.push(row + rows_inserted);
4469 }
4470
4471 self.transact(window, cx, |editor, window, cx| {
4472 editor.edit(edits, cx);
4473
4474 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4475 let mut index = 0;
4476 s.move_cursors_with(|map, _, _| {
4477 let row = rows[index];
4478 index += 1;
4479
4480 let point = Point::new(row, 0);
4481 let boundary = map.next_line_boundary(point).1;
4482 let clipped = map.clip_point(boundary, Bias::Left);
4483
4484 (clipped, SelectionGoal::None)
4485 });
4486 });
4487
4488 let mut indent_edits = Vec::new();
4489 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4490 for row in rows {
4491 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4492 for (row, indent) in indents {
4493 if indent.len == 0 {
4494 continue;
4495 }
4496
4497 let text = match indent.kind {
4498 IndentKind::Space => " ".repeat(indent.len as usize),
4499 IndentKind::Tab => "\t".repeat(indent.len as usize),
4500 };
4501 let point = Point::new(row.0, 0);
4502 indent_edits.push((point..point, text));
4503 }
4504 }
4505 editor.edit(indent_edits, cx);
4506 });
4507 }
4508
4509 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4510 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4511 original_indent_columns: Vec::new(),
4512 });
4513 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4514 }
4515
4516 fn insert_with_autoindent_mode(
4517 &mut self,
4518 text: &str,
4519 autoindent_mode: Option<AutoindentMode>,
4520 window: &mut Window,
4521 cx: &mut Context<Self>,
4522 ) {
4523 if self.read_only(cx) {
4524 return;
4525 }
4526
4527 let text: Arc<str> = text.into();
4528 self.transact(window, cx, |this, window, cx| {
4529 let old_selections = this.selections.all_adjusted(cx);
4530 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4531 let anchors = {
4532 let snapshot = buffer.read(cx);
4533 old_selections
4534 .iter()
4535 .map(|s| {
4536 let anchor = snapshot.anchor_after(s.head());
4537 s.map(|_| anchor)
4538 })
4539 .collect::<Vec<_>>()
4540 };
4541 buffer.edit(
4542 old_selections
4543 .iter()
4544 .map(|s| (s.start..s.end, text.clone())),
4545 autoindent_mode,
4546 cx,
4547 );
4548 anchors
4549 });
4550
4551 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4552 s.select_anchors(selection_anchors);
4553 });
4554
4555 cx.notify();
4556 });
4557 }
4558
4559 fn trigger_completion_on_input(
4560 &mut self,
4561 text: &str,
4562 trigger_in_words: bool,
4563 window: &mut Window,
4564 cx: &mut Context<Self>,
4565 ) {
4566 let completions_source = self
4567 .context_menu
4568 .borrow()
4569 .as_ref()
4570 .and_then(|menu| match menu {
4571 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4572 CodeContextMenu::CodeActions(_) => None,
4573 });
4574
4575 match completions_source {
4576 Some(CompletionsMenuSource::Words) => {
4577 self.show_word_completions(&ShowWordCompletions, window, cx)
4578 }
4579 Some(CompletionsMenuSource::Normal)
4580 | Some(CompletionsMenuSource::SnippetChoices)
4581 | None
4582 if self.is_completion_trigger(
4583 text,
4584 trigger_in_words,
4585 completions_source.is_some(),
4586 cx,
4587 ) =>
4588 {
4589 self.show_completions(
4590 &ShowCompletions {
4591 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4592 },
4593 window,
4594 cx,
4595 )
4596 }
4597 _ => {
4598 self.hide_context_menu(window, cx);
4599 }
4600 }
4601 }
4602
4603 fn is_completion_trigger(
4604 &self,
4605 text: &str,
4606 trigger_in_words: bool,
4607 menu_is_open: bool,
4608 cx: &mut Context<Self>,
4609 ) -> bool {
4610 let position = self.selections.newest_anchor().head();
4611 let multibuffer = self.buffer.read(cx);
4612 let Some(buffer) = position
4613 .buffer_id
4614 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4615 else {
4616 return false;
4617 };
4618
4619 if let Some(completion_provider) = &self.completion_provider {
4620 completion_provider.is_completion_trigger(
4621 &buffer,
4622 position.text_anchor,
4623 text,
4624 trigger_in_words,
4625 menu_is_open,
4626 cx,
4627 )
4628 } else {
4629 false
4630 }
4631 }
4632
4633 /// If any empty selections is touching the start of its innermost containing autoclose
4634 /// region, expand it to select the brackets.
4635 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4636 let selections = self.selections.all::<usize>(cx);
4637 let buffer = self.buffer.read(cx).read(cx);
4638 let new_selections = self
4639 .selections_with_autoclose_regions(selections, &buffer)
4640 .map(|(mut selection, region)| {
4641 if !selection.is_empty() {
4642 return selection;
4643 }
4644
4645 if let Some(region) = region {
4646 let mut range = region.range.to_offset(&buffer);
4647 if selection.start == range.start && range.start >= region.pair.start.len() {
4648 range.start -= region.pair.start.len();
4649 if buffer.contains_str_at(range.start, ®ion.pair.start)
4650 && buffer.contains_str_at(range.end, ®ion.pair.end)
4651 {
4652 range.end += region.pair.end.len();
4653 selection.start = range.start;
4654 selection.end = range.end;
4655
4656 return selection;
4657 }
4658 }
4659 }
4660
4661 let always_treat_brackets_as_autoclosed = buffer
4662 .language_settings_at(selection.start, cx)
4663 .always_treat_brackets_as_autoclosed;
4664
4665 if !always_treat_brackets_as_autoclosed {
4666 return selection;
4667 }
4668
4669 if let Some(scope) = buffer.language_scope_at(selection.start) {
4670 for (pair, enabled) in scope.brackets() {
4671 if !enabled || !pair.close {
4672 continue;
4673 }
4674
4675 if buffer.contains_str_at(selection.start, &pair.end) {
4676 let pair_start_len = pair.start.len();
4677 if buffer.contains_str_at(
4678 selection.start.saturating_sub(pair_start_len),
4679 &pair.start,
4680 ) {
4681 selection.start -= pair_start_len;
4682 selection.end += pair.end.len();
4683
4684 return selection;
4685 }
4686 }
4687 }
4688 }
4689
4690 selection
4691 })
4692 .collect();
4693
4694 drop(buffer);
4695 self.change_selections(None, window, cx, |selections| {
4696 selections.select(new_selections)
4697 });
4698 }
4699
4700 /// Iterate the given selections, and for each one, find the smallest surrounding
4701 /// autoclose region. This uses the ordering of the selections and the autoclose
4702 /// regions to avoid repeated comparisons.
4703 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4704 &'a self,
4705 selections: impl IntoIterator<Item = Selection<D>>,
4706 buffer: &'a MultiBufferSnapshot,
4707 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4708 let mut i = 0;
4709 let mut regions = self.autoclose_regions.as_slice();
4710 selections.into_iter().map(move |selection| {
4711 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4712
4713 let mut enclosing = None;
4714 while let Some(pair_state) = regions.get(i) {
4715 if pair_state.range.end.to_offset(buffer) < range.start {
4716 regions = ®ions[i + 1..];
4717 i = 0;
4718 } else if pair_state.range.start.to_offset(buffer) > range.end {
4719 break;
4720 } else {
4721 if pair_state.selection_id == selection.id {
4722 enclosing = Some(pair_state);
4723 }
4724 i += 1;
4725 }
4726 }
4727
4728 (selection, enclosing)
4729 })
4730 }
4731
4732 /// Remove any autoclose regions that no longer contain their selection.
4733 fn invalidate_autoclose_regions(
4734 &mut self,
4735 mut selections: &[Selection<Anchor>],
4736 buffer: &MultiBufferSnapshot,
4737 ) {
4738 self.autoclose_regions.retain(|state| {
4739 let mut i = 0;
4740 while let Some(selection) = selections.get(i) {
4741 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4742 selections = &selections[1..];
4743 continue;
4744 }
4745 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4746 break;
4747 }
4748 if selection.id == state.selection_id {
4749 return true;
4750 } else {
4751 i += 1;
4752 }
4753 }
4754 false
4755 });
4756 }
4757
4758 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4759 let offset = position.to_offset(buffer);
4760 let (word_range, kind) = buffer.surrounding_word(offset, true);
4761 if offset > word_range.start && kind == Some(CharKind::Word) {
4762 Some(
4763 buffer
4764 .text_for_range(word_range.start..offset)
4765 .collect::<String>(),
4766 )
4767 } else {
4768 None
4769 }
4770 }
4771
4772 pub fn toggle_inline_values(
4773 &mut self,
4774 _: &ToggleInlineValues,
4775 _: &mut Window,
4776 cx: &mut Context<Self>,
4777 ) {
4778 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4779
4780 self.refresh_inline_values(cx);
4781 }
4782
4783 pub fn toggle_inlay_hints(
4784 &mut self,
4785 _: &ToggleInlayHints,
4786 _: &mut Window,
4787 cx: &mut Context<Self>,
4788 ) {
4789 self.refresh_inlay_hints(
4790 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4791 cx,
4792 );
4793 }
4794
4795 pub fn inlay_hints_enabled(&self) -> bool {
4796 self.inlay_hint_cache.enabled
4797 }
4798
4799 pub fn inline_values_enabled(&self) -> bool {
4800 self.inline_value_cache.enabled
4801 }
4802
4803 #[cfg(any(test, feature = "test-support"))]
4804 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4805 self.display_map
4806 .read(cx)
4807 .current_inlays()
4808 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4809 .cloned()
4810 .collect()
4811 }
4812
4813 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4814 if self.semantics_provider.is_none() || !self.mode.is_full() {
4815 return;
4816 }
4817
4818 let reason_description = reason.description();
4819 let ignore_debounce = matches!(
4820 reason,
4821 InlayHintRefreshReason::SettingsChange(_)
4822 | InlayHintRefreshReason::Toggle(_)
4823 | InlayHintRefreshReason::ExcerptsRemoved(_)
4824 | InlayHintRefreshReason::ModifiersChanged(_)
4825 );
4826 let (invalidate_cache, required_languages) = match reason {
4827 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4828 match self.inlay_hint_cache.modifiers_override(enabled) {
4829 Some(enabled) => {
4830 if enabled {
4831 (InvalidationStrategy::RefreshRequested, None)
4832 } else {
4833 self.splice_inlays(
4834 &self
4835 .visible_inlay_hints(cx)
4836 .iter()
4837 .map(|inlay| inlay.id)
4838 .collect::<Vec<InlayId>>(),
4839 Vec::new(),
4840 cx,
4841 );
4842 return;
4843 }
4844 }
4845 None => return,
4846 }
4847 }
4848 InlayHintRefreshReason::Toggle(enabled) => {
4849 if self.inlay_hint_cache.toggle(enabled) {
4850 if enabled {
4851 (InvalidationStrategy::RefreshRequested, None)
4852 } else {
4853 self.splice_inlays(
4854 &self
4855 .visible_inlay_hints(cx)
4856 .iter()
4857 .map(|inlay| inlay.id)
4858 .collect::<Vec<InlayId>>(),
4859 Vec::new(),
4860 cx,
4861 );
4862 return;
4863 }
4864 } else {
4865 return;
4866 }
4867 }
4868 InlayHintRefreshReason::SettingsChange(new_settings) => {
4869 match self.inlay_hint_cache.update_settings(
4870 &self.buffer,
4871 new_settings,
4872 self.visible_inlay_hints(cx),
4873 cx,
4874 ) {
4875 ControlFlow::Break(Some(InlaySplice {
4876 to_remove,
4877 to_insert,
4878 })) => {
4879 self.splice_inlays(&to_remove, to_insert, cx);
4880 return;
4881 }
4882 ControlFlow::Break(None) => return,
4883 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4884 }
4885 }
4886 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4887 if let Some(InlaySplice {
4888 to_remove,
4889 to_insert,
4890 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4891 {
4892 self.splice_inlays(&to_remove, to_insert, cx);
4893 }
4894 self.display_map.update(cx, |display_map, _| {
4895 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4896 });
4897 return;
4898 }
4899 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4900 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4901 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4902 }
4903 InlayHintRefreshReason::RefreshRequested => {
4904 (InvalidationStrategy::RefreshRequested, None)
4905 }
4906 };
4907
4908 if let Some(InlaySplice {
4909 to_remove,
4910 to_insert,
4911 }) = self.inlay_hint_cache.spawn_hint_refresh(
4912 reason_description,
4913 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4914 invalidate_cache,
4915 ignore_debounce,
4916 cx,
4917 ) {
4918 self.splice_inlays(&to_remove, to_insert, cx);
4919 }
4920 }
4921
4922 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4923 self.display_map
4924 .read(cx)
4925 .current_inlays()
4926 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4927 .cloned()
4928 .collect()
4929 }
4930
4931 pub fn excerpts_for_inlay_hints_query(
4932 &self,
4933 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4934 cx: &mut Context<Editor>,
4935 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4936 let Some(project) = self.project.as_ref() else {
4937 return HashMap::default();
4938 };
4939 let project = project.read(cx);
4940 let multi_buffer = self.buffer().read(cx);
4941 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4942 let multi_buffer_visible_start = self
4943 .scroll_manager
4944 .anchor()
4945 .anchor
4946 .to_point(&multi_buffer_snapshot);
4947 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4948 multi_buffer_visible_start
4949 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4950 Bias::Left,
4951 );
4952 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4953 multi_buffer_snapshot
4954 .range_to_buffer_ranges(multi_buffer_visible_range)
4955 .into_iter()
4956 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4957 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4958 let buffer_file = project::File::from_dyn(buffer.file())?;
4959 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4960 let worktree_entry = buffer_worktree
4961 .read(cx)
4962 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4963 if worktree_entry.is_ignored {
4964 return None;
4965 }
4966
4967 let language = buffer.language()?;
4968 if let Some(restrict_to_languages) = restrict_to_languages {
4969 if !restrict_to_languages.contains(language) {
4970 return None;
4971 }
4972 }
4973 Some((
4974 excerpt_id,
4975 (
4976 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4977 buffer.version().clone(),
4978 excerpt_visible_range,
4979 ),
4980 ))
4981 })
4982 .collect()
4983 }
4984
4985 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4986 TextLayoutDetails {
4987 text_system: window.text_system().clone(),
4988 editor_style: self.style.clone().unwrap(),
4989 rem_size: window.rem_size(),
4990 scroll_anchor: self.scroll_manager.anchor(),
4991 visible_rows: self.visible_line_count(),
4992 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4993 }
4994 }
4995
4996 pub fn splice_inlays(
4997 &self,
4998 to_remove: &[InlayId],
4999 to_insert: Vec<Inlay>,
5000 cx: &mut Context<Self>,
5001 ) {
5002 self.display_map.update(cx, |display_map, cx| {
5003 display_map.splice_inlays(to_remove, to_insert, cx)
5004 });
5005 cx.notify();
5006 }
5007
5008 fn trigger_on_type_formatting(
5009 &self,
5010 input: String,
5011 window: &mut Window,
5012 cx: &mut Context<Self>,
5013 ) -> Option<Task<Result<()>>> {
5014 if input.len() != 1 {
5015 return None;
5016 }
5017
5018 let project = self.project.as_ref()?;
5019 let position = self.selections.newest_anchor().head();
5020 let (buffer, buffer_position) = self
5021 .buffer
5022 .read(cx)
5023 .text_anchor_for_position(position, cx)?;
5024
5025 let settings = language_settings::language_settings(
5026 buffer
5027 .read(cx)
5028 .language_at(buffer_position)
5029 .map(|l| l.name()),
5030 buffer.read(cx).file(),
5031 cx,
5032 );
5033 if !settings.use_on_type_format {
5034 return None;
5035 }
5036
5037 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5038 // hence we do LSP request & edit on host side only — add formats to host's history.
5039 let push_to_lsp_host_history = true;
5040 // If this is not the host, append its history with new edits.
5041 let push_to_client_history = project.read(cx).is_via_collab();
5042
5043 let on_type_formatting = project.update(cx, |project, cx| {
5044 project.on_type_format(
5045 buffer.clone(),
5046 buffer_position,
5047 input,
5048 push_to_lsp_host_history,
5049 cx,
5050 )
5051 });
5052 Some(cx.spawn_in(window, async move |editor, cx| {
5053 if let Some(transaction) = on_type_formatting.await? {
5054 if push_to_client_history {
5055 buffer
5056 .update(cx, |buffer, _| {
5057 buffer.push_transaction(transaction, Instant::now());
5058 buffer.finalize_last_transaction();
5059 })
5060 .ok();
5061 }
5062 editor.update(cx, |editor, cx| {
5063 editor.refresh_document_highlights(cx);
5064 })?;
5065 }
5066 Ok(())
5067 }))
5068 }
5069
5070 pub fn show_word_completions(
5071 &mut self,
5072 _: &ShowWordCompletions,
5073 window: &mut Window,
5074 cx: &mut Context<Self>,
5075 ) {
5076 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5077 }
5078
5079 pub fn show_completions(
5080 &mut self,
5081 options: &ShowCompletions,
5082 window: &mut Window,
5083 cx: &mut Context<Self>,
5084 ) {
5085 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5086 }
5087
5088 fn open_or_update_completions_menu(
5089 &mut self,
5090 requested_source: Option<CompletionsMenuSource>,
5091 trigger: Option<&str>,
5092 window: &mut Window,
5093 cx: &mut Context<Self>,
5094 ) {
5095 if self.pending_rename.is_some() {
5096 return;
5097 }
5098
5099 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5100
5101 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5102 // inserted and selected. To handle that case, the start of the selection is used so that
5103 // the menu starts with all choices.
5104 let position = self
5105 .selections
5106 .newest_anchor()
5107 .start
5108 .bias_right(&multibuffer_snapshot);
5109 if position.diff_base_anchor.is_some() {
5110 return;
5111 }
5112 let (buffer, buffer_position) =
5113 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5114 output
5115 } else {
5116 return;
5117 };
5118 let buffer_snapshot = buffer.read(cx).snapshot();
5119
5120 let query: Option<Arc<String>> =
5121 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5122
5123 drop(multibuffer_snapshot);
5124
5125 let provider = match requested_source {
5126 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5127 Some(CompletionsMenuSource::Words) => None,
5128 Some(CompletionsMenuSource::SnippetChoices) => {
5129 log::error!("bug: SnippetChoices requested_source is not handled");
5130 None
5131 }
5132 };
5133
5134 let sort_completions = provider
5135 .as_ref()
5136 .map_or(false, |provider| provider.sort_completions());
5137
5138 let filter_completions = provider
5139 .as_ref()
5140 .map_or(true, |provider| provider.filter_completions());
5141
5142 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5143 if filter_completions {
5144 menu.filter(query.clone(), provider.clone(), window, cx);
5145 }
5146 // When `is_incomplete` is false, no need to re-query completions when the current query
5147 // is a suffix of the initial query.
5148 if !menu.is_incomplete {
5149 // If the new query is a suffix of the old query (typing more characters) and
5150 // the previous result was complete, the existing completions can be filtered.
5151 //
5152 // Note that this is always true for snippet completions.
5153 let query_matches = match (&menu.initial_query, &query) {
5154 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5155 (None, _) => true,
5156 _ => false,
5157 };
5158 if query_matches {
5159 let position_matches = if menu.initial_position == position {
5160 true
5161 } else {
5162 let snapshot = self.buffer.read(cx).read(cx);
5163 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5164 };
5165 if position_matches {
5166 return;
5167 }
5168 }
5169 }
5170 };
5171
5172 let trigger_kind = match trigger {
5173 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5174 CompletionTriggerKind::TRIGGER_CHARACTER
5175 }
5176 _ => CompletionTriggerKind::INVOKED,
5177 };
5178 let completion_context = CompletionContext {
5179 trigger_character: trigger.and_then(|trigger| {
5180 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5181 Some(String::from(trigger))
5182 } else {
5183 None
5184 }
5185 }),
5186 trigger_kind,
5187 };
5188
5189 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5190 buffer_snapshot.surrounding_word(buffer_position)
5191 {
5192 let word_to_exclude = buffer_snapshot
5193 .text_for_range(word_range.clone())
5194 .collect::<String>();
5195 (
5196 buffer_snapshot.anchor_before(word_range.start)
5197 ..buffer_snapshot.anchor_after(buffer_position),
5198 Some(word_to_exclude),
5199 )
5200 } else {
5201 (buffer_position..buffer_position, None)
5202 };
5203
5204 let language = buffer_snapshot
5205 .language_at(buffer_position)
5206 .map(|language| language.name());
5207
5208 let completion_settings =
5209 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5210
5211 let show_completion_documentation = buffer_snapshot
5212 .settings_at(buffer_position, cx)
5213 .show_completion_documentation;
5214
5215 // The document can be large, so stay in reasonable bounds when searching for words,
5216 // otherwise completion pop-up might be slow to appear.
5217 const WORD_LOOKUP_ROWS: u32 = 5_000;
5218 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5219 let min_word_search = buffer_snapshot.clip_point(
5220 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5221 Bias::Left,
5222 );
5223 let max_word_search = buffer_snapshot.clip_point(
5224 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5225 Bias::Right,
5226 );
5227 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5228 ..buffer_snapshot.point_to_offset(max_word_search);
5229
5230 let skip_digits = query
5231 .as_ref()
5232 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5233
5234 let (mut words, provider_responses) = match &provider {
5235 Some(provider) => {
5236 let provider_responses = provider.completions(
5237 position.excerpt_id,
5238 &buffer,
5239 buffer_position,
5240 completion_context,
5241 window,
5242 cx,
5243 );
5244
5245 let words = match completion_settings.words {
5246 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5247 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5248 .background_spawn(async move {
5249 buffer_snapshot.words_in_range(WordsQuery {
5250 fuzzy_contents: None,
5251 range: word_search_range,
5252 skip_digits,
5253 })
5254 }),
5255 };
5256
5257 (words, provider_responses)
5258 }
5259 None => (
5260 cx.background_spawn(async move {
5261 buffer_snapshot.words_in_range(WordsQuery {
5262 fuzzy_contents: None,
5263 range: word_search_range,
5264 skip_digits,
5265 })
5266 }),
5267 Task::ready(Ok(Vec::new())),
5268 ),
5269 };
5270
5271 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5272
5273 let id = post_inc(&mut self.next_completion_id);
5274 let task = cx.spawn_in(window, async move |editor, cx| {
5275 let Ok(()) = editor.update(cx, |this, _| {
5276 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5277 }) else {
5278 return;
5279 };
5280
5281 // TODO: Ideally completions from different sources would be selectively re-queried, so
5282 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5283 let mut completions = Vec::new();
5284 let mut is_incomplete = false;
5285 if let Some(provider_responses) = provider_responses.await.log_err() {
5286 if !provider_responses.is_empty() {
5287 for response in provider_responses {
5288 completions.extend(response.completions);
5289 is_incomplete = is_incomplete || response.is_incomplete;
5290 }
5291 if completion_settings.words == WordsCompletionMode::Fallback {
5292 words = Task::ready(BTreeMap::default());
5293 }
5294 }
5295 }
5296
5297 let mut words = words.await;
5298 if let Some(word_to_exclude) = &word_to_exclude {
5299 words.remove(word_to_exclude);
5300 }
5301 for lsp_completion in &completions {
5302 words.remove(&lsp_completion.new_text);
5303 }
5304 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5305 replace_range: word_replace_range.clone(),
5306 new_text: word.clone(),
5307 label: CodeLabel::plain(word, None),
5308 icon_path: None,
5309 documentation: None,
5310 source: CompletionSource::BufferWord {
5311 word_range,
5312 resolved: false,
5313 },
5314 insert_text_mode: Some(InsertTextMode::AS_IS),
5315 confirm: None,
5316 }));
5317
5318 let menu = if completions.is_empty() {
5319 None
5320 } else {
5321 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5322 let languages = editor
5323 .workspace
5324 .as_ref()
5325 .and_then(|(workspace, _)| workspace.upgrade())
5326 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5327 let menu = CompletionsMenu::new(
5328 id,
5329 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5330 sort_completions,
5331 show_completion_documentation,
5332 position,
5333 query.clone(),
5334 is_incomplete,
5335 buffer.clone(),
5336 completions.into(),
5337 snippet_sort_order,
5338 languages,
5339 language,
5340 cx,
5341 );
5342
5343 let query = if filter_completions { query } else { None };
5344 let matches_task = if let Some(query) = query {
5345 menu.do_async_filtering(query, cx)
5346 } else {
5347 Task::ready(menu.unfiltered_matches())
5348 };
5349 (menu, matches_task)
5350 }) else {
5351 return;
5352 };
5353
5354 let matches = matches_task.await;
5355
5356 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5357 // Newer menu already set, so exit.
5358 match editor.context_menu.borrow().as_ref() {
5359 Some(CodeContextMenu::Completions(prev_menu)) => {
5360 if prev_menu.id > id {
5361 return;
5362 }
5363 }
5364 _ => {}
5365 };
5366
5367 // Only valid to take prev_menu because it the new menu is immediately set
5368 // below, or the menu is hidden.
5369 match editor.context_menu.borrow_mut().take() {
5370 Some(CodeContextMenu::Completions(prev_menu)) => {
5371 let position_matches =
5372 if prev_menu.initial_position == menu.initial_position {
5373 true
5374 } else {
5375 let snapshot = editor.buffer.read(cx).read(cx);
5376 prev_menu.initial_position.to_offset(&snapshot)
5377 == menu.initial_position.to_offset(&snapshot)
5378 };
5379 if position_matches {
5380 // Preserve markdown cache before `set_filter_results` because it will
5381 // try to populate the documentation cache.
5382 menu.preserve_markdown_cache(prev_menu);
5383 }
5384 }
5385 _ => {}
5386 };
5387
5388 menu.set_filter_results(matches, provider, window, cx);
5389 }) else {
5390 return;
5391 };
5392
5393 menu.visible().then_some(menu)
5394 };
5395
5396 editor
5397 .update_in(cx, |editor, window, cx| {
5398 if editor.focus_handle.is_focused(window) {
5399 if let Some(menu) = menu {
5400 *editor.context_menu.borrow_mut() =
5401 Some(CodeContextMenu::Completions(menu));
5402
5403 crate::hover_popover::hide_hover(editor, cx);
5404 if editor.show_edit_predictions_in_menu() {
5405 editor.update_visible_inline_completion(window, cx);
5406 } else {
5407 editor.discard_inline_completion(false, cx);
5408 }
5409
5410 cx.notify();
5411 return;
5412 }
5413 }
5414
5415 if editor.completion_tasks.len() <= 1 {
5416 // If there are no more completion tasks and the last menu was empty, we should hide it.
5417 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5418 // If it was already hidden and we don't show inline completions in the menu, we should
5419 // also show the inline-completion when available.
5420 if was_hidden && editor.show_edit_predictions_in_menu() {
5421 editor.update_visible_inline_completion(window, cx);
5422 }
5423 }
5424 })
5425 .ok();
5426 });
5427
5428 self.completion_tasks.push((id, task));
5429 }
5430
5431 #[cfg(feature = "test-support")]
5432 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5433 let menu = self.context_menu.borrow();
5434 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5435 let completions = menu.completions.borrow();
5436 Some(completions.to_vec())
5437 } else {
5438 None
5439 }
5440 }
5441
5442 pub fn with_completions_menu_matching_id<R>(
5443 &self,
5444 id: CompletionId,
5445 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5446 ) -> R {
5447 let mut context_menu = self.context_menu.borrow_mut();
5448 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5449 return f(None);
5450 };
5451 if completions_menu.id != id {
5452 return f(None);
5453 }
5454 f(Some(completions_menu))
5455 }
5456
5457 pub fn confirm_completion(
5458 &mut self,
5459 action: &ConfirmCompletion,
5460 window: &mut Window,
5461 cx: &mut Context<Self>,
5462 ) -> Option<Task<Result<()>>> {
5463 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5464 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5465 }
5466
5467 pub fn confirm_completion_insert(
5468 &mut self,
5469 _: &ConfirmCompletionInsert,
5470 window: &mut Window,
5471 cx: &mut Context<Self>,
5472 ) -> Option<Task<Result<()>>> {
5473 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5474 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5475 }
5476
5477 pub fn confirm_completion_replace(
5478 &mut self,
5479 _: &ConfirmCompletionReplace,
5480 window: &mut Window,
5481 cx: &mut Context<Self>,
5482 ) -> Option<Task<Result<()>>> {
5483 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5484 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5485 }
5486
5487 pub fn compose_completion(
5488 &mut self,
5489 action: &ComposeCompletion,
5490 window: &mut Window,
5491 cx: &mut Context<Self>,
5492 ) -> Option<Task<Result<()>>> {
5493 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5494 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5495 }
5496
5497 fn do_completion(
5498 &mut self,
5499 item_ix: Option<usize>,
5500 intent: CompletionIntent,
5501 window: &mut Window,
5502 cx: &mut Context<Editor>,
5503 ) -> Option<Task<Result<()>>> {
5504 use language::ToOffset as _;
5505
5506 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5507 else {
5508 return None;
5509 };
5510
5511 let candidate_id = {
5512 let entries = completions_menu.entries.borrow();
5513 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5514 if self.show_edit_predictions_in_menu() {
5515 self.discard_inline_completion(true, cx);
5516 }
5517 mat.candidate_id
5518 };
5519
5520 let completion = completions_menu
5521 .completions
5522 .borrow()
5523 .get(candidate_id)?
5524 .clone();
5525 cx.stop_propagation();
5526
5527 let buffer_handle = completions_menu.buffer.clone();
5528
5529 let CompletionEdit {
5530 new_text,
5531 snippet,
5532 replace_range,
5533 } = process_completion_for_edit(
5534 &completion,
5535 intent,
5536 &buffer_handle,
5537 &completions_menu.initial_position.text_anchor,
5538 cx,
5539 );
5540
5541 let buffer = buffer_handle.read(cx);
5542 let snapshot = self.buffer.read(cx).snapshot(cx);
5543 let newest_anchor = self.selections.newest_anchor();
5544 let replace_range_multibuffer = {
5545 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5546 let multibuffer_anchor = snapshot
5547 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5548 .unwrap()
5549 ..snapshot
5550 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5551 .unwrap();
5552 multibuffer_anchor.start.to_offset(&snapshot)
5553 ..multibuffer_anchor.end.to_offset(&snapshot)
5554 };
5555 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5556 return None;
5557 }
5558
5559 let old_text = buffer
5560 .text_for_range(replace_range.clone())
5561 .collect::<String>();
5562 let lookbehind = newest_anchor
5563 .start
5564 .text_anchor
5565 .to_offset(buffer)
5566 .saturating_sub(replace_range.start);
5567 let lookahead = replace_range
5568 .end
5569 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5570 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5571 let suffix = &old_text[lookbehind.min(old_text.len())..];
5572
5573 let selections = self.selections.all::<usize>(cx);
5574 let mut ranges = Vec::new();
5575 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5576
5577 for selection in &selections {
5578 let range = if selection.id == newest_anchor.id {
5579 replace_range_multibuffer.clone()
5580 } else {
5581 let mut range = selection.range();
5582
5583 // if prefix is present, don't duplicate it
5584 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5585 range.start = range.start.saturating_sub(lookbehind);
5586
5587 // if suffix is also present, mimic the newest cursor and replace it
5588 if selection.id != newest_anchor.id
5589 && snapshot.contains_str_at(range.end, suffix)
5590 {
5591 range.end += lookahead;
5592 }
5593 }
5594 range
5595 };
5596
5597 ranges.push(range.clone());
5598
5599 if !self.linked_edit_ranges.is_empty() {
5600 let start_anchor = snapshot.anchor_before(range.start);
5601 let end_anchor = snapshot.anchor_after(range.end);
5602 if let Some(ranges) = self
5603 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5604 {
5605 for (buffer, edits) in ranges {
5606 linked_edits
5607 .entry(buffer.clone())
5608 .or_default()
5609 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5610 }
5611 }
5612 }
5613 }
5614
5615 let common_prefix_len = old_text
5616 .chars()
5617 .zip(new_text.chars())
5618 .take_while(|(a, b)| a == b)
5619 .map(|(a, _)| a.len_utf8())
5620 .sum::<usize>();
5621
5622 cx.emit(EditorEvent::InputHandled {
5623 utf16_range_to_replace: None,
5624 text: new_text[common_prefix_len..].into(),
5625 });
5626
5627 self.transact(window, cx, |this, window, cx| {
5628 if let Some(mut snippet) = snippet {
5629 snippet.text = new_text.to_string();
5630 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5631 } else {
5632 this.buffer.update(cx, |buffer, cx| {
5633 let auto_indent = match completion.insert_text_mode {
5634 Some(InsertTextMode::AS_IS) => None,
5635 _ => this.autoindent_mode.clone(),
5636 };
5637 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5638 buffer.edit(edits, auto_indent, cx);
5639 });
5640 }
5641 for (buffer, edits) in linked_edits {
5642 buffer.update(cx, |buffer, cx| {
5643 let snapshot = buffer.snapshot();
5644 let edits = edits
5645 .into_iter()
5646 .map(|(range, text)| {
5647 use text::ToPoint as TP;
5648 let end_point = TP::to_point(&range.end, &snapshot);
5649 let start_point = TP::to_point(&range.start, &snapshot);
5650 (start_point..end_point, text)
5651 })
5652 .sorted_by_key(|(range, _)| range.start);
5653 buffer.edit(edits, None, cx);
5654 })
5655 }
5656
5657 this.refresh_inline_completion(true, false, window, cx);
5658 });
5659
5660 let show_new_completions_on_confirm = completion
5661 .confirm
5662 .as_ref()
5663 .map_or(false, |confirm| confirm(intent, window, cx));
5664 if show_new_completions_on_confirm {
5665 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5666 }
5667
5668 let provider = self.completion_provider.as_ref()?;
5669 drop(completion);
5670 let apply_edits = provider.apply_additional_edits_for_completion(
5671 buffer_handle,
5672 completions_menu.completions.clone(),
5673 candidate_id,
5674 true,
5675 cx,
5676 );
5677
5678 let editor_settings = EditorSettings::get_global(cx);
5679 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5680 // After the code completion is finished, users often want to know what signatures are needed.
5681 // so we should automatically call signature_help
5682 self.show_signature_help(&ShowSignatureHelp, window, cx);
5683 }
5684
5685 Some(cx.foreground_executor().spawn(async move {
5686 apply_edits.await?;
5687 Ok(())
5688 }))
5689 }
5690
5691 pub fn toggle_code_actions(
5692 &mut self,
5693 action: &ToggleCodeActions,
5694 window: &mut Window,
5695 cx: &mut Context<Self>,
5696 ) {
5697 let quick_launch = action.quick_launch;
5698 let mut context_menu = self.context_menu.borrow_mut();
5699 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5700 if code_actions.deployed_from == action.deployed_from {
5701 // Toggle if we're selecting the same one
5702 *context_menu = None;
5703 cx.notify();
5704 return;
5705 } else {
5706 // Otherwise, clear it and start a new one
5707 *context_menu = None;
5708 cx.notify();
5709 }
5710 }
5711 drop(context_menu);
5712 let snapshot = self.snapshot(window, cx);
5713 let deployed_from = action.deployed_from.clone();
5714 let action = action.clone();
5715 self.completion_tasks.clear();
5716 self.discard_inline_completion(false, cx);
5717
5718 let multibuffer_point = match &action.deployed_from {
5719 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5720 DisplayPoint::new(*row, 0).to_point(&snapshot)
5721 }
5722 _ => self.selections.newest::<Point>(cx).head(),
5723 };
5724 let Some((buffer, buffer_row)) = snapshot
5725 .buffer_snapshot
5726 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5727 .and_then(|(buffer_snapshot, range)| {
5728 self.buffer()
5729 .read(cx)
5730 .buffer(buffer_snapshot.remote_id())
5731 .map(|buffer| (buffer, range.start.row))
5732 })
5733 else {
5734 return;
5735 };
5736 let buffer_id = buffer.read(cx).remote_id();
5737 let tasks = self
5738 .tasks
5739 .get(&(buffer_id, buffer_row))
5740 .map(|t| Arc::new(t.to_owned()));
5741
5742 if !self.focus_handle.is_focused(window) {
5743 return;
5744 }
5745 let project = self.project.clone();
5746
5747 let code_actions_task = match deployed_from {
5748 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5749 _ => self.code_actions(buffer_row, window, cx),
5750 };
5751
5752 let runnable_task = match deployed_from {
5753 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5754 _ => {
5755 let mut task_context_task = Task::ready(None);
5756 if let Some(tasks) = &tasks {
5757 if let Some(project) = project {
5758 task_context_task =
5759 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5760 }
5761 }
5762
5763 cx.spawn_in(window, {
5764 let buffer = buffer.clone();
5765 async move |editor, cx| {
5766 let task_context = task_context_task.await;
5767
5768 let resolved_tasks =
5769 tasks
5770 .zip(task_context.clone())
5771 .map(|(tasks, task_context)| ResolvedTasks {
5772 templates: tasks.resolve(&task_context).collect(),
5773 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5774 multibuffer_point.row,
5775 tasks.column,
5776 )),
5777 });
5778 let debug_scenarios = editor
5779 .update(cx, |editor, cx| {
5780 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5781 })?
5782 .await;
5783 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5784 }
5785 })
5786 }
5787 };
5788
5789 cx.spawn_in(window, async move |editor, cx| {
5790 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5791 let code_actions = code_actions_task.await;
5792 let spawn_straight_away = quick_launch
5793 && resolved_tasks
5794 .as_ref()
5795 .map_or(false, |tasks| tasks.templates.len() == 1)
5796 && code_actions
5797 .as_ref()
5798 .map_or(true, |actions| actions.is_empty())
5799 && debug_scenarios.is_empty();
5800
5801 editor.update_in(cx, |editor, window, cx| {
5802 crate::hover_popover::hide_hover(editor, cx);
5803 *editor.context_menu.borrow_mut() =
5804 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5805 buffer,
5806 actions: CodeActionContents::new(
5807 resolved_tasks,
5808 code_actions,
5809 debug_scenarios,
5810 task_context.unwrap_or_default(),
5811 ),
5812 selected_item: Default::default(),
5813 scroll_handle: UniformListScrollHandle::default(),
5814 deployed_from,
5815 }));
5816 if spawn_straight_away {
5817 if let Some(task) = editor.confirm_code_action(
5818 &ConfirmCodeAction { item_ix: Some(0) },
5819 window,
5820 cx,
5821 ) {
5822 cx.notify();
5823 return task;
5824 }
5825 }
5826
5827 Task::ready(Ok(()))
5828 })
5829 })
5830 .detach_and_log_err(cx);
5831 }
5832
5833 fn debug_scenarios(
5834 &mut self,
5835 resolved_tasks: &Option<ResolvedTasks>,
5836 buffer: &Entity<Buffer>,
5837 cx: &mut App,
5838 ) -> Task<Vec<task::DebugScenario>> {
5839 if cx.has_flag::<DebuggerFeatureFlag>() {
5840 maybe!({
5841 let project = self.project.as_ref()?;
5842 let dap_store = project.read(cx).dap_store();
5843 let mut scenarios = vec![];
5844 let resolved_tasks = resolved_tasks.as_ref()?;
5845 let buffer = buffer.read(cx);
5846 let language = buffer.language()?;
5847 let file = buffer.file();
5848 let debug_adapter = language_settings(language.name().into(), file, cx)
5849 .debuggers
5850 .first()
5851 .map(SharedString::from)
5852 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5853
5854 dap_store.update(cx, |dap_store, cx| {
5855 for (_, task) in &resolved_tasks.templates {
5856 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5857 task.original_task().clone(),
5858 debug_adapter.clone().into(),
5859 task.display_label().to_owned().into(),
5860 cx,
5861 );
5862 scenarios.push(maybe_scenario);
5863 }
5864 });
5865 Some(cx.background_spawn(async move {
5866 let scenarios = futures::future::join_all(scenarios)
5867 .await
5868 .into_iter()
5869 .flatten()
5870 .collect::<Vec<_>>();
5871 scenarios
5872 }))
5873 })
5874 .unwrap_or_else(|| Task::ready(vec![]))
5875 } else {
5876 Task::ready(vec![])
5877 }
5878 }
5879
5880 fn code_actions(
5881 &mut self,
5882 buffer_row: u32,
5883 window: &mut Window,
5884 cx: &mut Context<Self>,
5885 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5886 let mut task = self.code_actions_task.take();
5887 cx.spawn_in(window, async move |editor, cx| {
5888 while let Some(prev_task) = task {
5889 prev_task.await.log_err();
5890 task = editor
5891 .update(cx, |this, _| this.code_actions_task.take())
5892 .ok()?;
5893 }
5894
5895 editor
5896 .update(cx, |editor, cx| {
5897 editor
5898 .available_code_actions
5899 .clone()
5900 .and_then(|(location, code_actions)| {
5901 let snapshot = location.buffer.read(cx).snapshot();
5902 let point_range = location.range.to_point(&snapshot);
5903 let point_range = point_range.start.row..=point_range.end.row;
5904 if point_range.contains(&buffer_row) {
5905 Some(code_actions)
5906 } else {
5907 None
5908 }
5909 })
5910 })
5911 .ok()
5912 .flatten()
5913 })
5914 }
5915
5916 pub fn confirm_code_action(
5917 &mut self,
5918 action: &ConfirmCodeAction,
5919 window: &mut Window,
5920 cx: &mut Context<Self>,
5921 ) -> Option<Task<Result<()>>> {
5922 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5923
5924 let actions_menu =
5925 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5926 menu
5927 } else {
5928 return None;
5929 };
5930
5931 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5932 let action = actions_menu.actions.get(action_ix)?;
5933 let title = action.label();
5934 let buffer = actions_menu.buffer;
5935 let workspace = self.workspace()?;
5936
5937 match action {
5938 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5939 workspace.update(cx, |workspace, cx| {
5940 workspace.schedule_resolved_task(
5941 task_source_kind,
5942 resolved_task,
5943 false,
5944 window,
5945 cx,
5946 );
5947
5948 Some(Task::ready(Ok(())))
5949 })
5950 }
5951 CodeActionsItem::CodeAction {
5952 excerpt_id,
5953 action,
5954 provider,
5955 } => {
5956 let apply_code_action =
5957 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5958 let workspace = workspace.downgrade();
5959 Some(cx.spawn_in(window, async move |editor, cx| {
5960 let project_transaction = apply_code_action.await?;
5961 Self::open_project_transaction(
5962 &editor,
5963 workspace,
5964 project_transaction,
5965 title,
5966 cx,
5967 )
5968 .await
5969 }))
5970 }
5971 CodeActionsItem::DebugScenario(scenario) => {
5972 let context = actions_menu.actions.context.clone();
5973
5974 workspace.update(cx, |workspace, cx| {
5975 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5976 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5977 });
5978 Some(Task::ready(Ok(())))
5979 }
5980 }
5981 }
5982
5983 pub async fn open_project_transaction(
5984 this: &WeakEntity<Editor>,
5985 workspace: WeakEntity<Workspace>,
5986 transaction: ProjectTransaction,
5987 title: String,
5988 cx: &mut AsyncWindowContext,
5989 ) -> Result<()> {
5990 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5991 cx.update(|_, cx| {
5992 entries.sort_unstable_by_key(|(buffer, _)| {
5993 buffer.read(cx).file().map(|f| f.path().clone())
5994 });
5995 })?;
5996
5997 // If the project transaction's edits are all contained within this editor, then
5998 // avoid opening a new editor to display them.
5999
6000 if let Some((buffer, transaction)) = entries.first() {
6001 if entries.len() == 1 {
6002 let excerpt = this.update(cx, |editor, cx| {
6003 editor
6004 .buffer()
6005 .read(cx)
6006 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6007 })?;
6008 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6009 if excerpted_buffer == *buffer {
6010 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6011 let excerpt_range = excerpt_range.to_offset(buffer);
6012 buffer
6013 .edited_ranges_for_transaction::<usize>(transaction)
6014 .all(|range| {
6015 excerpt_range.start <= range.start
6016 && excerpt_range.end >= range.end
6017 })
6018 })?;
6019
6020 if all_edits_within_excerpt {
6021 return Ok(());
6022 }
6023 }
6024 }
6025 }
6026 } else {
6027 return Ok(());
6028 }
6029
6030 let mut ranges_to_highlight = Vec::new();
6031 let excerpt_buffer = cx.new(|cx| {
6032 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6033 for (buffer_handle, transaction) in &entries {
6034 let edited_ranges = buffer_handle
6035 .read(cx)
6036 .edited_ranges_for_transaction::<Point>(transaction)
6037 .collect::<Vec<_>>();
6038 let (ranges, _) = multibuffer.set_excerpts_for_path(
6039 PathKey::for_buffer(buffer_handle, cx),
6040 buffer_handle.clone(),
6041 edited_ranges,
6042 DEFAULT_MULTIBUFFER_CONTEXT,
6043 cx,
6044 );
6045
6046 ranges_to_highlight.extend(ranges);
6047 }
6048 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6049 multibuffer
6050 })?;
6051
6052 workspace.update_in(cx, |workspace, window, cx| {
6053 let project = workspace.project().clone();
6054 let editor =
6055 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6056 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6057 editor.update(cx, |editor, cx| {
6058 editor.highlight_background::<Self>(
6059 &ranges_to_highlight,
6060 |theme| theme.editor_highlighted_line_background,
6061 cx,
6062 );
6063 });
6064 })?;
6065
6066 Ok(())
6067 }
6068
6069 pub fn clear_code_action_providers(&mut self) {
6070 self.code_action_providers.clear();
6071 self.available_code_actions.take();
6072 }
6073
6074 pub fn add_code_action_provider(
6075 &mut self,
6076 provider: Rc<dyn CodeActionProvider>,
6077 window: &mut Window,
6078 cx: &mut Context<Self>,
6079 ) {
6080 if self
6081 .code_action_providers
6082 .iter()
6083 .any(|existing_provider| existing_provider.id() == provider.id())
6084 {
6085 return;
6086 }
6087
6088 self.code_action_providers.push(provider);
6089 self.refresh_code_actions(window, cx);
6090 }
6091
6092 pub fn remove_code_action_provider(
6093 &mut self,
6094 id: Arc<str>,
6095 window: &mut Window,
6096 cx: &mut Context<Self>,
6097 ) {
6098 self.code_action_providers
6099 .retain(|provider| provider.id() != id);
6100 self.refresh_code_actions(window, cx);
6101 }
6102
6103 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6104 !self.code_action_providers.is_empty()
6105 && EditorSettings::get_global(cx).toolbar.code_actions
6106 }
6107
6108 pub fn has_available_code_actions(&self) -> bool {
6109 self.available_code_actions
6110 .as_ref()
6111 .is_some_and(|(_, actions)| !actions.is_empty())
6112 }
6113
6114 fn render_inline_code_actions(
6115 &self,
6116 icon_size: ui::IconSize,
6117 display_row: DisplayRow,
6118 is_active: bool,
6119 cx: &mut Context<Self>,
6120 ) -> AnyElement {
6121 let show_tooltip = !self.context_menu_visible();
6122 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6123 .icon_size(icon_size)
6124 .shape(ui::IconButtonShape::Square)
6125 .style(ButtonStyle::Transparent)
6126 .icon_color(ui::Color::Hidden)
6127 .toggle_state(is_active)
6128 .when(show_tooltip, |this| {
6129 this.tooltip({
6130 let focus_handle = self.focus_handle.clone();
6131 move |window, cx| {
6132 Tooltip::for_action_in(
6133 "Toggle Code Actions",
6134 &ToggleCodeActions {
6135 deployed_from: None,
6136 quick_launch: false,
6137 },
6138 &focus_handle,
6139 window,
6140 cx,
6141 )
6142 }
6143 })
6144 })
6145 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6146 window.focus(&editor.focus_handle(cx));
6147 editor.toggle_code_actions(
6148 &crate::actions::ToggleCodeActions {
6149 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6150 display_row,
6151 )),
6152 quick_launch: false,
6153 },
6154 window,
6155 cx,
6156 );
6157 }))
6158 .into_any_element()
6159 }
6160
6161 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6162 &self.context_menu
6163 }
6164
6165 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6166 let newest_selection = self.selections.newest_anchor().clone();
6167 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6168 let buffer = self.buffer.read(cx);
6169 if newest_selection.head().diff_base_anchor.is_some() {
6170 return None;
6171 }
6172 let (start_buffer, start) =
6173 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6174 let (end_buffer, end) =
6175 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6176 if start_buffer != end_buffer {
6177 return None;
6178 }
6179
6180 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6181 cx.background_executor()
6182 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6183 .await;
6184
6185 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6186 let providers = this.code_action_providers.clone();
6187 let tasks = this
6188 .code_action_providers
6189 .iter()
6190 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6191 .collect::<Vec<_>>();
6192 (providers, tasks)
6193 })?;
6194
6195 let mut actions = Vec::new();
6196 for (provider, provider_actions) in
6197 providers.into_iter().zip(future::join_all(tasks).await)
6198 {
6199 if let Some(provider_actions) = provider_actions.log_err() {
6200 actions.extend(provider_actions.into_iter().map(|action| {
6201 AvailableCodeAction {
6202 excerpt_id: newest_selection.start.excerpt_id,
6203 action,
6204 provider: provider.clone(),
6205 }
6206 }));
6207 }
6208 }
6209
6210 this.update(cx, |this, cx| {
6211 this.available_code_actions = if actions.is_empty() {
6212 None
6213 } else {
6214 Some((
6215 Location {
6216 buffer: start_buffer,
6217 range: start..end,
6218 },
6219 actions.into(),
6220 ))
6221 };
6222 cx.notify();
6223 })
6224 }));
6225 None
6226 }
6227
6228 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6229 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6230 self.show_git_blame_inline = false;
6231
6232 self.show_git_blame_inline_delay_task =
6233 Some(cx.spawn_in(window, async move |this, cx| {
6234 cx.background_executor().timer(delay).await;
6235
6236 this.update(cx, |this, cx| {
6237 this.show_git_blame_inline = true;
6238 cx.notify();
6239 })
6240 .log_err();
6241 }));
6242 }
6243 }
6244
6245 fn show_blame_popover(
6246 &mut self,
6247 blame_entry: &BlameEntry,
6248 position: gpui::Point<Pixels>,
6249 cx: &mut Context<Self>,
6250 ) {
6251 if let Some(state) = &mut self.inline_blame_popover {
6252 state.hide_task.take();
6253 cx.notify();
6254 } else {
6255 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6256 let show_task = cx.spawn(async move |editor, cx| {
6257 cx.background_executor()
6258 .timer(std::time::Duration::from_millis(delay))
6259 .await;
6260 editor
6261 .update(cx, |editor, cx| {
6262 if let Some(state) = &mut editor.inline_blame_popover {
6263 state.show_task = None;
6264 cx.notify();
6265 }
6266 })
6267 .ok();
6268 });
6269 let Some(blame) = self.blame.as_ref() else {
6270 return;
6271 };
6272 let blame = blame.read(cx);
6273 let details = blame.details_for_entry(&blame_entry);
6274 let markdown = cx.new(|cx| {
6275 Markdown::new(
6276 details
6277 .as_ref()
6278 .map(|message| message.message.clone())
6279 .unwrap_or_default(),
6280 None,
6281 None,
6282 cx,
6283 )
6284 });
6285 self.inline_blame_popover = Some(InlineBlamePopover {
6286 position,
6287 show_task: Some(show_task),
6288 hide_task: None,
6289 popover_bounds: None,
6290 popover_state: InlineBlamePopoverState {
6291 scroll_handle: ScrollHandle::new(),
6292 commit_message: details,
6293 markdown,
6294 },
6295 });
6296 }
6297 }
6298
6299 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6300 if let Some(state) = &mut self.inline_blame_popover {
6301 if state.show_task.is_some() {
6302 self.inline_blame_popover.take();
6303 cx.notify();
6304 } else {
6305 let hide_task = cx.spawn(async move |editor, cx| {
6306 cx.background_executor()
6307 .timer(std::time::Duration::from_millis(100))
6308 .await;
6309 editor
6310 .update(cx, |editor, cx| {
6311 editor.inline_blame_popover.take();
6312 cx.notify();
6313 })
6314 .ok();
6315 });
6316 state.hide_task = Some(hide_task);
6317 }
6318 }
6319 }
6320
6321 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6322 if self.pending_rename.is_some() {
6323 return None;
6324 }
6325
6326 let provider = self.semantics_provider.clone()?;
6327 let buffer = self.buffer.read(cx);
6328 let newest_selection = self.selections.newest_anchor().clone();
6329 let cursor_position = newest_selection.head();
6330 let (cursor_buffer, cursor_buffer_position) =
6331 buffer.text_anchor_for_position(cursor_position, cx)?;
6332 let (tail_buffer, tail_buffer_position) =
6333 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6334 if cursor_buffer != tail_buffer {
6335 return None;
6336 }
6337
6338 let snapshot = cursor_buffer.read(cx).snapshot();
6339 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6340 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6341 if start_word_range != end_word_range {
6342 self.document_highlights_task.take();
6343 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6344 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6345 return None;
6346 }
6347
6348 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6349 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6350 cx.background_executor()
6351 .timer(Duration::from_millis(debounce))
6352 .await;
6353
6354 let highlights = if let Some(highlights) = cx
6355 .update(|cx| {
6356 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6357 })
6358 .ok()
6359 .flatten()
6360 {
6361 highlights.await.log_err()
6362 } else {
6363 None
6364 };
6365
6366 if let Some(highlights) = highlights {
6367 this.update(cx, |this, cx| {
6368 if this.pending_rename.is_some() {
6369 return;
6370 }
6371
6372 let buffer_id = cursor_position.buffer_id;
6373 let buffer = this.buffer.read(cx);
6374 if !buffer
6375 .text_anchor_for_position(cursor_position, cx)
6376 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6377 {
6378 return;
6379 }
6380
6381 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6382 let mut write_ranges = Vec::new();
6383 let mut read_ranges = Vec::new();
6384 for highlight in highlights {
6385 for (excerpt_id, excerpt_range) in
6386 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6387 {
6388 let start = highlight
6389 .range
6390 .start
6391 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6392 let end = highlight
6393 .range
6394 .end
6395 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6396 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6397 continue;
6398 }
6399
6400 let range = Anchor {
6401 buffer_id,
6402 excerpt_id,
6403 text_anchor: start,
6404 diff_base_anchor: None,
6405 }..Anchor {
6406 buffer_id,
6407 excerpt_id,
6408 text_anchor: end,
6409 diff_base_anchor: None,
6410 };
6411 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6412 write_ranges.push(range);
6413 } else {
6414 read_ranges.push(range);
6415 }
6416 }
6417 }
6418
6419 this.highlight_background::<DocumentHighlightRead>(
6420 &read_ranges,
6421 |theme| theme.editor_document_highlight_read_background,
6422 cx,
6423 );
6424 this.highlight_background::<DocumentHighlightWrite>(
6425 &write_ranges,
6426 |theme| theme.editor_document_highlight_write_background,
6427 cx,
6428 );
6429 cx.notify();
6430 })
6431 .log_err();
6432 }
6433 }));
6434 None
6435 }
6436
6437 fn prepare_highlight_query_from_selection(
6438 &mut self,
6439 cx: &mut Context<Editor>,
6440 ) -> Option<(String, Range<Anchor>)> {
6441 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6442 return None;
6443 }
6444 if !EditorSettings::get_global(cx).selection_highlight {
6445 return None;
6446 }
6447 if self.selections.count() != 1 || self.selections.line_mode {
6448 return None;
6449 }
6450 let selection = self.selections.newest::<Point>(cx);
6451 if selection.is_empty() || selection.start.row != selection.end.row {
6452 return None;
6453 }
6454 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6455 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6456 let query = multi_buffer_snapshot
6457 .text_for_range(selection_anchor_range.clone())
6458 .collect::<String>();
6459 if query.trim().is_empty() {
6460 return None;
6461 }
6462 Some((query, selection_anchor_range))
6463 }
6464
6465 fn update_selection_occurrence_highlights(
6466 &mut self,
6467 query_text: String,
6468 query_range: Range<Anchor>,
6469 multi_buffer_range_to_query: Range<Point>,
6470 use_debounce: bool,
6471 window: &mut Window,
6472 cx: &mut Context<Editor>,
6473 ) -> Task<()> {
6474 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6475 cx.spawn_in(window, async move |editor, cx| {
6476 if use_debounce {
6477 cx.background_executor()
6478 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6479 .await;
6480 }
6481 let match_task = cx.background_spawn(async move {
6482 let buffer_ranges = multi_buffer_snapshot
6483 .range_to_buffer_ranges(multi_buffer_range_to_query)
6484 .into_iter()
6485 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6486 let mut match_ranges = Vec::new();
6487 let Ok(regex) = project::search::SearchQuery::text(
6488 query_text.clone(),
6489 false,
6490 false,
6491 false,
6492 Default::default(),
6493 Default::default(),
6494 false,
6495 None,
6496 ) else {
6497 return Vec::default();
6498 };
6499 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6500 match_ranges.extend(
6501 regex
6502 .search(&buffer_snapshot, Some(search_range.clone()))
6503 .await
6504 .into_iter()
6505 .filter_map(|match_range| {
6506 let match_start = buffer_snapshot
6507 .anchor_after(search_range.start + match_range.start);
6508 let match_end = buffer_snapshot
6509 .anchor_before(search_range.start + match_range.end);
6510 let match_anchor_range = Anchor::range_in_buffer(
6511 excerpt_id,
6512 buffer_snapshot.remote_id(),
6513 match_start..match_end,
6514 );
6515 (match_anchor_range != query_range).then_some(match_anchor_range)
6516 }),
6517 );
6518 }
6519 match_ranges
6520 });
6521 let match_ranges = match_task.await;
6522 editor
6523 .update_in(cx, |editor, _, cx| {
6524 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6525 if !match_ranges.is_empty() {
6526 editor.highlight_background::<SelectedTextHighlight>(
6527 &match_ranges,
6528 |theme| theme.editor_document_highlight_bracket_background,
6529 cx,
6530 )
6531 }
6532 })
6533 .log_err();
6534 })
6535 }
6536
6537 fn refresh_selected_text_highlights(
6538 &mut self,
6539 on_buffer_edit: bool,
6540 window: &mut Window,
6541 cx: &mut Context<Editor>,
6542 ) {
6543 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6544 else {
6545 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6546 self.quick_selection_highlight_task.take();
6547 self.debounced_selection_highlight_task.take();
6548 return;
6549 };
6550 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6551 if on_buffer_edit
6552 || self
6553 .quick_selection_highlight_task
6554 .as_ref()
6555 .map_or(true, |(prev_anchor_range, _)| {
6556 prev_anchor_range != &query_range
6557 })
6558 {
6559 let multi_buffer_visible_start = self
6560 .scroll_manager
6561 .anchor()
6562 .anchor
6563 .to_point(&multi_buffer_snapshot);
6564 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6565 multi_buffer_visible_start
6566 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6567 Bias::Left,
6568 );
6569 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6570 self.quick_selection_highlight_task = Some((
6571 query_range.clone(),
6572 self.update_selection_occurrence_highlights(
6573 query_text.clone(),
6574 query_range.clone(),
6575 multi_buffer_visible_range,
6576 false,
6577 window,
6578 cx,
6579 ),
6580 ));
6581 }
6582 if on_buffer_edit
6583 || self
6584 .debounced_selection_highlight_task
6585 .as_ref()
6586 .map_or(true, |(prev_anchor_range, _)| {
6587 prev_anchor_range != &query_range
6588 })
6589 {
6590 let multi_buffer_start = multi_buffer_snapshot
6591 .anchor_before(0)
6592 .to_point(&multi_buffer_snapshot);
6593 let multi_buffer_end = multi_buffer_snapshot
6594 .anchor_after(multi_buffer_snapshot.len())
6595 .to_point(&multi_buffer_snapshot);
6596 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6597 self.debounced_selection_highlight_task = Some((
6598 query_range.clone(),
6599 self.update_selection_occurrence_highlights(
6600 query_text,
6601 query_range,
6602 multi_buffer_full_range,
6603 true,
6604 window,
6605 cx,
6606 ),
6607 ));
6608 }
6609 }
6610
6611 pub fn refresh_inline_completion(
6612 &mut self,
6613 debounce: bool,
6614 user_requested: bool,
6615 window: &mut Window,
6616 cx: &mut Context<Self>,
6617 ) -> Option<()> {
6618 let provider = self.edit_prediction_provider()?;
6619 let cursor = self.selections.newest_anchor().head();
6620 let (buffer, cursor_buffer_position) =
6621 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6622
6623 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6624 self.discard_inline_completion(false, cx);
6625 return None;
6626 }
6627
6628 if !user_requested
6629 && (!self.should_show_edit_predictions()
6630 || !self.is_focused(window)
6631 || buffer.read(cx).is_empty())
6632 {
6633 self.discard_inline_completion(false, cx);
6634 return None;
6635 }
6636
6637 self.update_visible_inline_completion(window, cx);
6638 provider.refresh(
6639 self.project.clone(),
6640 buffer,
6641 cursor_buffer_position,
6642 debounce,
6643 cx,
6644 );
6645 Some(())
6646 }
6647
6648 fn show_edit_predictions_in_menu(&self) -> bool {
6649 match self.edit_prediction_settings {
6650 EditPredictionSettings::Disabled => false,
6651 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6652 }
6653 }
6654
6655 pub fn edit_predictions_enabled(&self) -> bool {
6656 match self.edit_prediction_settings {
6657 EditPredictionSettings::Disabled => false,
6658 EditPredictionSettings::Enabled { .. } => true,
6659 }
6660 }
6661
6662 fn edit_prediction_requires_modifier(&self) -> bool {
6663 match self.edit_prediction_settings {
6664 EditPredictionSettings::Disabled => false,
6665 EditPredictionSettings::Enabled {
6666 preview_requires_modifier,
6667 ..
6668 } => preview_requires_modifier,
6669 }
6670 }
6671
6672 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6673 if self.edit_prediction_provider.is_none() {
6674 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6675 } else {
6676 let selection = self.selections.newest_anchor();
6677 let cursor = selection.head();
6678
6679 if let Some((buffer, cursor_buffer_position)) =
6680 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6681 {
6682 self.edit_prediction_settings =
6683 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6684 }
6685 }
6686 }
6687
6688 fn edit_prediction_settings_at_position(
6689 &self,
6690 buffer: &Entity<Buffer>,
6691 buffer_position: language::Anchor,
6692 cx: &App,
6693 ) -> EditPredictionSettings {
6694 if !self.mode.is_full()
6695 || !self.show_inline_completions_override.unwrap_or(true)
6696 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6697 {
6698 return EditPredictionSettings::Disabled;
6699 }
6700
6701 let buffer = buffer.read(cx);
6702
6703 let file = buffer.file();
6704
6705 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6706 return EditPredictionSettings::Disabled;
6707 };
6708
6709 let by_provider = matches!(
6710 self.menu_inline_completions_policy,
6711 MenuInlineCompletionsPolicy::ByProvider
6712 );
6713
6714 let show_in_menu = by_provider
6715 && self
6716 .edit_prediction_provider
6717 .as_ref()
6718 .map_or(false, |provider| {
6719 provider.provider.show_completions_in_menu()
6720 });
6721
6722 let preview_requires_modifier =
6723 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6724
6725 EditPredictionSettings::Enabled {
6726 show_in_menu,
6727 preview_requires_modifier,
6728 }
6729 }
6730
6731 fn should_show_edit_predictions(&self) -> bool {
6732 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6733 }
6734
6735 pub fn edit_prediction_preview_is_active(&self) -> bool {
6736 matches!(
6737 self.edit_prediction_preview,
6738 EditPredictionPreview::Active { .. }
6739 )
6740 }
6741
6742 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6743 let cursor = self.selections.newest_anchor().head();
6744 if let Some((buffer, cursor_position)) =
6745 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6746 {
6747 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6748 } else {
6749 false
6750 }
6751 }
6752
6753 pub fn supports_minimap(&self, cx: &App) -> bool {
6754 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6755 }
6756
6757 fn edit_predictions_enabled_in_buffer(
6758 &self,
6759 buffer: &Entity<Buffer>,
6760 buffer_position: language::Anchor,
6761 cx: &App,
6762 ) -> bool {
6763 maybe!({
6764 if self.read_only(cx) {
6765 return Some(false);
6766 }
6767 let provider = self.edit_prediction_provider()?;
6768 if !provider.is_enabled(&buffer, buffer_position, cx) {
6769 return Some(false);
6770 }
6771 let buffer = buffer.read(cx);
6772 let Some(file) = buffer.file() else {
6773 return Some(true);
6774 };
6775 let settings = all_language_settings(Some(file), cx);
6776 Some(settings.edit_predictions_enabled_for_file(file, cx))
6777 })
6778 .unwrap_or(false)
6779 }
6780
6781 fn cycle_inline_completion(
6782 &mut self,
6783 direction: Direction,
6784 window: &mut Window,
6785 cx: &mut Context<Self>,
6786 ) -> Option<()> {
6787 let provider = self.edit_prediction_provider()?;
6788 let cursor = self.selections.newest_anchor().head();
6789 let (buffer, cursor_buffer_position) =
6790 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6791 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6792 return None;
6793 }
6794
6795 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6796 self.update_visible_inline_completion(window, cx);
6797
6798 Some(())
6799 }
6800
6801 pub fn show_inline_completion(
6802 &mut self,
6803 _: &ShowEditPrediction,
6804 window: &mut Window,
6805 cx: &mut Context<Self>,
6806 ) {
6807 if !self.has_active_inline_completion() {
6808 self.refresh_inline_completion(false, true, window, cx);
6809 return;
6810 }
6811
6812 self.update_visible_inline_completion(window, cx);
6813 }
6814
6815 pub fn display_cursor_names(
6816 &mut self,
6817 _: &DisplayCursorNames,
6818 window: &mut Window,
6819 cx: &mut Context<Self>,
6820 ) {
6821 self.show_cursor_names(window, cx);
6822 }
6823
6824 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6825 self.show_cursor_names = true;
6826 cx.notify();
6827 cx.spawn_in(window, async move |this, cx| {
6828 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6829 this.update(cx, |this, cx| {
6830 this.show_cursor_names = false;
6831 cx.notify()
6832 })
6833 .ok()
6834 })
6835 .detach();
6836 }
6837
6838 pub fn next_edit_prediction(
6839 &mut self,
6840 _: &NextEditPrediction,
6841 window: &mut Window,
6842 cx: &mut Context<Self>,
6843 ) {
6844 if self.has_active_inline_completion() {
6845 self.cycle_inline_completion(Direction::Next, window, cx);
6846 } else {
6847 let is_copilot_disabled = self
6848 .refresh_inline_completion(false, true, window, cx)
6849 .is_none();
6850 if is_copilot_disabled {
6851 cx.propagate();
6852 }
6853 }
6854 }
6855
6856 pub fn previous_edit_prediction(
6857 &mut self,
6858 _: &PreviousEditPrediction,
6859 window: &mut Window,
6860 cx: &mut Context<Self>,
6861 ) {
6862 if self.has_active_inline_completion() {
6863 self.cycle_inline_completion(Direction::Prev, window, cx);
6864 } else {
6865 let is_copilot_disabled = self
6866 .refresh_inline_completion(false, true, window, cx)
6867 .is_none();
6868 if is_copilot_disabled {
6869 cx.propagate();
6870 }
6871 }
6872 }
6873
6874 pub fn accept_edit_prediction(
6875 &mut self,
6876 _: &AcceptEditPrediction,
6877 window: &mut Window,
6878 cx: &mut Context<Self>,
6879 ) {
6880 if self.show_edit_predictions_in_menu() {
6881 self.hide_context_menu(window, cx);
6882 }
6883
6884 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6885 return;
6886 };
6887
6888 self.report_inline_completion_event(
6889 active_inline_completion.completion_id.clone(),
6890 true,
6891 cx,
6892 );
6893
6894 match &active_inline_completion.completion {
6895 InlineCompletion::Move { target, .. } => {
6896 let target = *target;
6897
6898 if let Some(position_map) = &self.last_position_map {
6899 if position_map
6900 .visible_row_range
6901 .contains(&target.to_display_point(&position_map.snapshot).row())
6902 || !self.edit_prediction_requires_modifier()
6903 {
6904 self.unfold_ranges(&[target..target], true, false, cx);
6905 // Note that this is also done in vim's handler of the Tab action.
6906 self.change_selections(
6907 Some(Autoscroll::newest()),
6908 window,
6909 cx,
6910 |selections| {
6911 selections.select_anchor_ranges([target..target]);
6912 },
6913 );
6914 self.clear_row_highlights::<EditPredictionPreview>();
6915
6916 self.edit_prediction_preview
6917 .set_previous_scroll_position(None);
6918 } else {
6919 self.edit_prediction_preview
6920 .set_previous_scroll_position(Some(
6921 position_map.snapshot.scroll_anchor,
6922 ));
6923
6924 self.highlight_rows::<EditPredictionPreview>(
6925 target..target,
6926 cx.theme().colors().editor_highlighted_line_background,
6927 RowHighlightOptions {
6928 autoscroll: true,
6929 ..Default::default()
6930 },
6931 cx,
6932 );
6933 self.request_autoscroll(Autoscroll::fit(), cx);
6934 }
6935 }
6936 }
6937 InlineCompletion::Edit { edits, .. } => {
6938 if let Some(provider) = self.edit_prediction_provider() {
6939 provider.accept(cx);
6940 }
6941
6942 // Store the transaction ID and selections before applying the edit
6943 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6944
6945 let snapshot = self.buffer.read(cx).snapshot(cx);
6946 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6947
6948 self.buffer.update(cx, |buffer, cx| {
6949 buffer.edit(edits.iter().cloned(), None, cx)
6950 });
6951
6952 self.change_selections(None, window, cx, |s| {
6953 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6954 });
6955
6956 let selections = self.selections.disjoint_anchors();
6957 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6958 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6959 if has_new_transaction {
6960 self.selection_history
6961 .insert_transaction(transaction_id_now, selections);
6962 }
6963 }
6964
6965 self.update_visible_inline_completion(window, cx);
6966 if self.active_inline_completion.is_none() {
6967 self.refresh_inline_completion(true, true, window, cx);
6968 }
6969
6970 cx.notify();
6971 }
6972 }
6973
6974 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6975 }
6976
6977 pub fn accept_partial_inline_completion(
6978 &mut self,
6979 _: &AcceptPartialEditPrediction,
6980 window: &mut Window,
6981 cx: &mut Context<Self>,
6982 ) {
6983 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6984 return;
6985 };
6986 if self.selections.count() != 1 {
6987 return;
6988 }
6989
6990 self.report_inline_completion_event(
6991 active_inline_completion.completion_id.clone(),
6992 true,
6993 cx,
6994 );
6995
6996 match &active_inline_completion.completion {
6997 InlineCompletion::Move { target, .. } => {
6998 let target = *target;
6999 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7000 selections.select_anchor_ranges([target..target]);
7001 });
7002 }
7003 InlineCompletion::Edit { edits, .. } => {
7004 // Find an insertion that starts at the cursor position.
7005 let snapshot = self.buffer.read(cx).snapshot(cx);
7006 let cursor_offset = self.selections.newest::<usize>(cx).head();
7007 let insertion = edits.iter().find_map(|(range, text)| {
7008 let range = range.to_offset(&snapshot);
7009 if range.is_empty() && range.start == cursor_offset {
7010 Some(text)
7011 } else {
7012 None
7013 }
7014 });
7015
7016 if let Some(text) = insertion {
7017 let mut partial_completion = text
7018 .chars()
7019 .by_ref()
7020 .take_while(|c| c.is_alphabetic())
7021 .collect::<String>();
7022 if partial_completion.is_empty() {
7023 partial_completion = text
7024 .chars()
7025 .by_ref()
7026 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7027 .collect::<String>();
7028 }
7029
7030 cx.emit(EditorEvent::InputHandled {
7031 utf16_range_to_replace: None,
7032 text: partial_completion.clone().into(),
7033 });
7034
7035 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7036
7037 self.refresh_inline_completion(true, true, window, cx);
7038 cx.notify();
7039 } else {
7040 self.accept_edit_prediction(&Default::default(), window, cx);
7041 }
7042 }
7043 }
7044 }
7045
7046 fn discard_inline_completion(
7047 &mut self,
7048 should_report_inline_completion_event: bool,
7049 cx: &mut Context<Self>,
7050 ) -> bool {
7051 if should_report_inline_completion_event {
7052 let completion_id = self
7053 .active_inline_completion
7054 .as_ref()
7055 .and_then(|active_completion| active_completion.completion_id.clone());
7056
7057 self.report_inline_completion_event(completion_id, false, cx);
7058 }
7059
7060 if let Some(provider) = self.edit_prediction_provider() {
7061 provider.discard(cx);
7062 }
7063
7064 self.take_active_inline_completion(cx)
7065 }
7066
7067 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7068 let Some(provider) = self.edit_prediction_provider() else {
7069 return;
7070 };
7071
7072 let Some((_, buffer, _)) = self
7073 .buffer
7074 .read(cx)
7075 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7076 else {
7077 return;
7078 };
7079
7080 let extension = buffer
7081 .read(cx)
7082 .file()
7083 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7084
7085 let event_type = match accepted {
7086 true => "Edit Prediction Accepted",
7087 false => "Edit Prediction Discarded",
7088 };
7089 telemetry::event!(
7090 event_type,
7091 provider = provider.name(),
7092 prediction_id = id,
7093 suggestion_accepted = accepted,
7094 file_extension = extension,
7095 );
7096 }
7097
7098 pub fn has_active_inline_completion(&self) -> bool {
7099 self.active_inline_completion.is_some()
7100 }
7101
7102 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7103 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7104 return false;
7105 };
7106
7107 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7108 self.clear_highlights::<InlineCompletionHighlight>(cx);
7109 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7110 true
7111 }
7112
7113 /// Returns true when we're displaying the edit prediction popover below the cursor
7114 /// like we are not previewing and the LSP autocomplete menu is visible
7115 /// or we are in `when_holding_modifier` mode.
7116 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7117 if self.edit_prediction_preview_is_active()
7118 || !self.show_edit_predictions_in_menu()
7119 || !self.edit_predictions_enabled()
7120 {
7121 return false;
7122 }
7123
7124 if self.has_visible_completions_menu() {
7125 return true;
7126 }
7127
7128 has_completion && self.edit_prediction_requires_modifier()
7129 }
7130
7131 fn handle_modifiers_changed(
7132 &mut self,
7133 modifiers: Modifiers,
7134 position_map: &PositionMap,
7135 window: &mut Window,
7136 cx: &mut Context<Self>,
7137 ) {
7138 if self.show_edit_predictions_in_menu() {
7139 self.update_edit_prediction_preview(&modifiers, window, cx);
7140 }
7141
7142 self.update_selection_mode(&modifiers, position_map, window, cx);
7143
7144 let mouse_position = window.mouse_position();
7145 if !position_map.text_hitbox.is_hovered(window) {
7146 return;
7147 }
7148
7149 self.update_hovered_link(
7150 position_map.point_for_position(mouse_position),
7151 &position_map.snapshot,
7152 modifiers,
7153 window,
7154 cx,
7155 )
7156 }
7157
7158 fn multi_cursor_modifier(
7159 cursor_event: bool,
7160 modifiers: &Modifiers,
7161 cx: &mut Context<Self>,
7162 ) -> bool {
7163 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7164 if cursor_event {
7165 match multi_cursor_setting {
7166 MultiCursorModifier::Alt => modifiers.alt,
7167 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7168 }
7169 } else {
7170 match multi_cursor_setting {
7171 MultiCursorModifier::Alt => modifiers.secondary(),
7172 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7173 }
7174 }
7175 }
7176
7177 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7178 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7179 }
7180
7181 fn update_selection_mode(
7182 &mut self,
7183 modifiers: &Modifiers,
7184 position_map: &PositionMap,
7185 window: &mut Window,
7186 cx: &mut Context<Self>,
7187 ) {
7188 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7189 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7190 || self.selections.pending.is_none()
7191 {
7192 return;
7193 }
7194
7195 let mouse_position = window.mouse_position();
7196 let point_for_position = position_map.point_for_position(mouse_position);
7197 let position = point_for_position.previous_valid;
7198
7199 self.select(
7200 SelectPhase::BeginColumnar {
7201 position,
7202 reset: false,
7203 goal_column: point_for_position.exact_unclipped.column(),
7204 },
7205 window,
7206 cx,
7207 );
7208 }
7209
7210 fn update_edit_prediction_preview(
7211 &mut self,
7212 modifiers: &Modifiers,
7213 window: &mut Window,
7214 cx: &mut Context<Self>,
7215 ) {
7216 let mut modifiers_held = false;
7217 if let Some(accept_keystroke) = self
7218 .accept_edit_prediction_keybind(false, window, cx)
7219 .keystroke()
7220 {
7221 modifiers_held = modifiers_held
7222 || (&accept_keystroke.modifiers == modifiers
7223 && accept_keystroke.modifiers.modified());
7224 };
7225 if let Some(accept_partial_keystroke) = self
7226 .accept_edit_prediction_keybind(true, window, cx)
7227 .keystroke()
7228 {
7229 modifiers_held = modifiers_held
7230 || (&accept_partial_keystroke.modifiers == modifiers
7231 && accept_partial_keystroke.modifiers.modified());
7232 }
7233
7234 if modifiers_held {
7235 if matches!(
7236 self.edit_prediction_preview,
7237 EditPredictionPreview::Inactive { .. }
7238 ) {
7239 self.edit_prediction_preview = EditPredictionPreview::Active {
7240 previous_scroll_position: None,
7241 since: Instant::now(),
7242 };
7243
7244 self.update_visible_inline_completion(window, cx);
7245 cx.notify();
7246 }
7247 } else if let EditPredictionPreview::Active {
7248 previous_scroll_position,
7249 since,
7250 } = self.edit_prediction_preview
7251 {
7252 if let (Some(previous_scroll_position), Some(position_map)) =
7253 (previous_scroll_position, self.last_position_map.as_ref())
7254 {
7255 self.set_scroll_position(
7256 previous_scroll_position
7257 .scroll_position(&position_map.snapshot.display_snapshot),
7258 window,
7259 cx,
7260 );
7261 }
7262
7263 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7264 released_too_fast: since.elapsed() < Duration::from_millis(200),
7265 };
7266 self.clear_row_highlights::<EditPredictionPreview>();
7267 self.update_visible_inline_completion(window, cx);
7268 cx.notify();
7269 }
7270 }
7271
7272 fn update_visible_inline_completion(
7273 &mut self,
7274 _window: &mut Window,
7275 cx: &mut Context<Self>,
7276 ) -> Option<()> {
7277 let selection = self.selections.newest_anchor();
7278 let cursor = selection.head();
7279 let multibuffer = self.buffer.read(cx).snapshot(cx);
7280 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7281 let excerpt_id = cursor.excerpt_id;
7282
7283 let show_in_menu = self.show_edit_predictions_in_menu();
7284 let completions_menu_has_precedence = !show_in_menu
7285 && (self.context_menu.borrow().is_some()
7286 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7287
7288 if completions_menu_has_precedence
7289 || !offset_selection.is_empty()
7290 || self
7291 .active_inline_completion
7292 .as_ref()
7293 .map_or(false, |completion| {
7294 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7295 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7296 !invalidation_range.contains(&offset_selection.head())
7297 })
7298 {
7299 self.discard_inline_completion(false, cx);
7300 return None;
7301 }
7302
7303 self.take_active_inline_completion(cx);
7304 let Some(provider) = self.edit_prediction_provider() else {
7305 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7306 return None;
7307 };
7308
7309 let (buffer, cursor_buffer_position) =
7310 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7311
7312 self.edit_prediction_settings =
7313 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7314
7315 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7316
7317 if self.edit_prediction_indent_conflict {
7318 let cursor_point = cursor.to_point(&multibuffer);
7319
7320 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7321
7322 if let Some((_, indent)) = indents.iter().next() {
7323 if indent.len == cursor_point.column {
7324 self.edit_prediction_indent_conflict = false;
7325 }
7326 }
7327 }
7328
7329 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7330 let edits = inline_completion
7331 .edits
7332 .into_iter()
7333 .flat_map(|(range, new_text)| {
7334 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7335 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7336 Some((start..end, new_text))
7337 })
7338 .collect::<Vec<_>>();
7339 if edits.is_empty() {
7340 return None;
7341 }
7342
7343 let first_edit_start = edits.first().unwrap().0.start;
7344 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7345 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7346
7347 let last_edit_end = edits.last().unwrap().0.end;
7348 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7349 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7350
7351 let cursor_row = cursor.to_point(&multibuffer).row;
7352
7353 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7354
7355 let mut inlay_ids = Vec::new();
7356 let invalidation_row_range;
7357 let move_invalidation_row_range = if cursor_row < edit_start_row {
7358 Some(cursor_row..edit_end_row)
7359 } else if cursor_row > edit_end_row {
7360 Some(edit_start_row..cursor_row)
7361 } else {
7362 None
7363 };
7364 let is_move =
7365 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7366 let completion = if is_move {
7367 invalidation_row_range =
7368 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7369 let target = first_edit_start;
7370 InlineCompletion::Move { target, snapshot }
7371 } else {
7372 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7373 && !self.inline_completions_hidden_for_vim_mode;
7374
7375 if show_completions_in_buffer {
7376 if edits
7377 .iter()
7378 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7379 {
7380 let mut inlays = Vec::new();
7381 for (range, new_text) in &edits {
7382 let inlay = Inlay::inline_completion(
7383 post_inc(&mut self.next_inlay_id),
7384 range.start,
7385 new_text.as_str(),
7386 );
7387 inlay_ids.push(inlay.id);
7388 inlays.push(inlay);
7389 }
7390
7391 self.splice_inlays(&[], inlays, cx);
7392 } else {
7393 let background_color = cx.theme().status().deleted_background;
7394 self.highlight_text::<InlineCompletionHighlight>(
7395 edits.iter().map(|(range, _)| range.clone()).collect(),
7396 HighlightStyle {
7397 background_color: Some(background_color),
7398 ..Default::default()
7399 },
7400 cx,
7401 );
7402 }
7403 }
7404
7405 invalidation_row_range = edit_start_row..edit_end_row;
7406
7407 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7408 if provider.show_tab_accept_marker() {
7409 EditDisplayMode::TabAccept
7410 } else {
7411 EditDisplayMode::Inline
7412 }
7413 } else {
7414 EditDisplayMode::DiffPopover
7415 };
7416
7417 InlineCompletion::Edit {
7418 edits,
7419 edit_preview: inline_completion.edit_preview,
7420 display_mode,
7421 snapshot,
7422 }
7423 };
7424
7425 let invalidation_range = multibuffer
7426 .anchor_before(Point::new(invalidation_row_range.start, 0))
7427 ..multibuffer.anchor_after(Point::new(
7428 invalidation_row_range.end,
7429 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7430 ));
7431
7432 self.stale_inline_completion_in_menu = None;
7433 self.active_inline_completion = Some(InlineCompletionState {
7434 inlay_ids,
7435 completion,
7436 completion_id: inline_completion.id,
7437 invalidation_range,
7438 });
7439
7440 cx.notify();
7441
7442 Some(())
7443 }
7444
7445 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7446 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7447 }
7448
7449 fn clear_tasks(&mut self) {
7450 self.tasks.clear()
7451 }
7452
7453 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7454 if self.tasks.insert(key, value).is_some() {
7455 // This case should hopefully be rare, but just in case...
7456 log::error!(
7457 "multiple different run targets found on a single line, only the last target will be rendered"
7458 )
7459 }
7460 }
7461
7462 /// Get all display points of breakpoints that will be rendered within editor
7463 ///
7464 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7465 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7466 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7467 fn active_breakpoints(
7468 &self,
7469 range: Range<DisplayRow>,
7470 window: &mut Window,
7471 cx: &mut Context<Self>,
7472 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7473 let mut breakpoint_display_points = HashMap::default();
7474
7475 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7476 return breakpoint_display_points;
7477 };
7478
7479 let snapshot = self.snapshot(window, cx);
7480
7481 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7482 let Some(project) = self.project.as_ref() else {
7483 return breakpoint_display_points;
7484 };
7485
7486 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7487 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7488
7489 for (buffer_snapshot, range, excerpt_id) in
7490 multi_buffer_snapshot.range_to_buffer_ranges(range)
7491 {
7492 let Some(buffer) = project
7493 .read(cx)
7494 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7495 else {
7496 continue;
7497 };
7498 let breakpoints = breakpoint_store.read(cx).breakpoints(
7499 &buffer,
7500 Some(
7501 buffer_snapshot.anchor_before(range.start)
7502 ..buffer_snapshot.anchor_after(range.end),
7503 ),
7504 buffer_snapshot,
7505 cx,
7506 );
7507 for (breakpoint, state) in breakpoints {
7508 let multi_buffer_anchor =
7509 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7510 let position = multi_buffer_anchor
7511 .to_point(&multi_buffer_snapshot)
7512 .to_display_point(&snapshot);
7513
7514 breakpoint_display_points.insert(
7515 position.row(),
7516 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7517 );
7518 }
7519 }
7520
7521 breakpoint_display_points
7522 }
7523
7524 fn breakpoint_context_menu(
7525 &self,
7526 anchor: Anchor,
7527 window: &mut Window,
7528 cx: &mut Context<Self>,
7529 ) -> Entity<ui::ContextMenu> {
7530 let weak_editor = cx.weak_entity();
7531 let focus_handle = self.focus_handle(cx);
7532
7533 let row = self
7534 .buffer
7535 .read(cx)
7536 .snapshot(cx)
7537 .summary_for_anchor::<Point>(&anchor)
7538 .row;
7539
7540 let breakpoint = self
7541 .breakpoint_at_row(row, window, cx)
7542 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7543
7544 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7545 "Edit Log Breakpoint"
7546 } else {
7547 "Set Log Breakpoint"
7548 };
7549
7550 let condition_breakpoint_msg = if breakpoint
7551 .as_ref()
7552 .is_some_and(|bp| bp.1.condition.is_some())
7553 {
7554 "Edit Condition Breakpoint"
7555 } else {
7556 "Set Condition Breakpoint"
7557 };
7558
7559 let hit_condition_breakpoint_msg = if breakpoint
7560 .as_ref()
7561 .is_some_and(|bp| bp.1.hit_condition.is_some())
7562 {
7563 "Edit Hit Condition Breakpoint"
7564 } else {
7565 "Set Hit Condition Breakpoint"
7566 };
7567
7568 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7569 "Unset Breakpoint"
7570 } else {
7571 "Set Breakpoint"
7572 };
7573
7574 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7575
7576 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7577 BreakpointState::Enabled => Some("Disable"),
7578 BreakpointState::Disabled => Some("Enable"),
7579 });
7580
7581 let (anchor, breakpoint) =
7582 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7583
7584 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7585 menu.on_blur_subscription(Subscription::new(|| {}))
7586 .context(focus_handle)
7587 .when(run_to_cursor, |this| {
7588 let weak_editor = weak_editor.clone();
7589 this.entry("Run to cursor", None, move |window, cx| {
7590 weak_editor
7591 .update(cx, |editor, cx| {
7592 editor.change_selections(None, window, cx, |s| {
7593 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7594 });
7595 })
7596 .ok();
7597
7598 window.dispatch_action(Box::new(RunToCursor), cx);
7599 })
7600 .separator()
7601 })
7602 .when_some(toggle_state_msg, |this, msg| {
7603 this.entry(msg, None, {
7604 let weak_editor = weak_editor.clone();
7605 let breakpoint = breakpoint.clone();
7606 move |_window, cx| {
7607 weak_editor
7608 .update(cx, |this, cx| {
7609 this.edit_breakpoint_at_anchor(
7610 anchor,
7611 breakpoint.as_ref().clone(),
7612 BreakpointEditAction::InvertState,
7613 cx,
7614 );
7615 })
7616 .log_err();
7617 }
7618 })
7619 })
7620 .entry(set_breakpoint_msg, None, {
7621 let weak_editor = weak_editor.clone();
7622 let breakpoint = breakpoint.clone();
7623 move |_window, cx| {
7624 weak_editor
7625 .update(cx, |this, cx| {
7626 this.edit_breakpoint_at_anchor(
7627 anchor,
7628 breakpoint.as_ref().clone(),
7629 BreakpointEditAction::Toggle,
7630 cx,
7631 );
7632 })
7633 .log_err();
7634 }
7635 })
7636 .entry(log_breakpoint_msg, None, {
7637 let breakpoint = breakpoint.clone();
7638 let weak_editor = weak_editor.clone();
7639 move |window, cx| {
7640 weak_editor
7641 .update(cx, |this, cx| {
7642 this.add_edit_breakpoint_block(
7643 anchor,
7644 breakpoint.as_ref(),
7645 BreakpointPromptEditAction::Log,
7646 window,
7647 cx,
7648 );
7649 })
7650 .log_err();
7651 }
7652 })
7653 .entry(condition_breakpoint_msg, None, {
7654 let breakpoint = breakpoint.clone();
7655 let weak_editor = weak_editor.clone();
7656 move |window, cx| {
7657 weak_editor
7658 .update(cx, |this, cx| {
7659 this.add_edit_breakpoint_block(
7660 anchor,
7661 breakpoint.as_ref(),
7662 BreakpointPromptEditAction::Condition,
7663 window,
7664 cx,
7665 );
7666 })
7667 .log_err();
7668 }
7669 })
7670 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7671 weak_editor
7672 .update(cx, |this, cx| {
7673 this.add_edit_breakpoint_block(
7674 anchor,
7675 breakpoint.as_ref(),
7676 BreakpointPromptEditAction::HitCondition,
7677 window,
7678 cx,
7679 );
7680 })
7681 .log_err();
7682 })
7683 })
7684 }
7685
7686 fn render_breakpoint(
7687 &self,
7688 position: Anchor,
7689 row: DisplayRow,
7690 breakpoint: &Breakpoint,
7691 state: Option<BreakpointSessionState>,
7692 cx: &mut Context<Self>,
7693 ) -> IconButton {
7694 let is_rejected = state.is_some_and(|s| !s.verified);
7695 // Is it a breakpoint that shows up when hovering over gutter?
7696 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7697 (false, false),
7698 |PhantomBreakpointIndicator {
7699 is_active,
7700 display_row,
7701 collides_with_existing_breakpoint,
7702 }| {
7703 (
7704 is_active && display_row == row,
7705 collides_with_existing_breakpoint,
7706 )
7707 },
7708 );
7709
7710 let (color, icon) = {
7711 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7712 (false, false) => ui::IconName::DebugBreakpoint,
7713 (true, false) => ui::IconName::DebugLogBreakpoint,
7714 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7715 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7716 };
7717
7718 let color = if is_phantom {
7719 Color::Hint
7720 } else if is_rejected {
7721 Color::Disabled
7722 } else {
7723 Color::Debugger
7724 };
7725
7726 (color, icon)
7727 };
7728
7729 let breakpoint = Arc::from(breakpoint.clone());
7730
7731 let alt_as_text = gpui::Keystroke {
7732 modifiers: Modifiers::secondary_key(),
7733 ..Default::default()
7734 };
7735 let primary_action_text = if breakpoint.is_disabled() {
7736 "Enable breakpoint"
7737 } else if is_phantom && !collides_with_existing {
7738 "Set breakpoint"
7739 } else {
7740 "Unset breakpoint"
7741 };
7742 let focus_handle = self.focus_handle.clone();
7743
7744 let meta = if is_rejected {
7745 SharedString::from("No executable code is associated with this line.")
7746 } else if collides_with_existing && !breakpoint.is_disabled() {
7747 SharedString::from(format!(
7748 "{alt_as_text}-click to disable,\nright-click for more options."
7749 ))
7750 } else {
7751 SharedString::from("Right-click for more options.")
7752 };
7753 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7754 .icon_size(IconSize::XSmall)
7755 .size(ui::ButtonSize::None)
7756 .when(is_rejected, |this| {
7757 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7758 })
7759 .icon_color(color)
7760 .style(ButtonStyle::Transparent)
7761 .on_click(cx.listener({
7762 let breakpoint = breakpoint.clone();
7763
7764 move |editor, event: &ClickEvent, window, cx| {
7765 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7766 BreakpointEditAction::InvertState
7767 } else {
7768 BreakpointEditAction::Toggle
7769 };
7770
7771 window.focus(&editor.focus_handle(cx));
7772 editor.edit_breakpoint_at_anchor(
7773 position,
7774 breakpoint.as_ref().clone(),
7775 edit_action,
7776 cx,
7777 );
7778 }
7779 }))
7780 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7781 editor.set_breakpoint_context_menu(
7782 row,
7783 Some(position),
7784 event.down.position,
7785 window,
7786 cx,
7787 );
7788 }))
7789 .tooltip(move |window, cx| {
7790 Tooltip::with_meta_in(
7791 primary_action_text,
7792 Some(&ToggleBreakpoint),
7793 meta.clone(),
7794 &focus_handle,
7795 window,
7796 cx,
7797 )
7798 })
7799 }
7800
7801 fn build_tasks_context(
7802 project: &Entity<Project>,
7803 buffer: &Entity<Buffer>,
7804 buffer_row: u32,
7805 tasks: &Arc<RunnableTasks>,
7806 cx: &mut Context<Self>,
7807 ) -> Task<Option<task::TaskContext>> {
7808 let position = Point::new(buffer_row, tasks.column);
7809 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7810 let location = Location {
7811 buffer: buffer.clone(),
7812 range: range_start..range_start,
7813 };
7814 // Fill in the environmental variables from the tree-sitter captures
7815 let mut captured_task_variables = TaskVariables::default();
7816 for (capture_name, value) in tasks.extra_variables.clone() {
7817 captured_task_variables.insert(
7818 task::VariableName::Custom(capture_name.into()),
7819 value.clone(),
7820 );
7821 }
7822 project.update(cx, |project, cx| {
7823 project.task_store().update(cx, |task_store, cx| {
7824 task_store.task_context_for_location(captured_task_variables, location, cx)
7825 })
7826 })
7827 }
7828
7829 pub fn spawn_nearest_task(
7830 &mut self,
7831 action: &SpawnNearestTask,
7832 window: &mut Window,
7833 cx: &mut Context<Self>,
7834 ) {
7835 let Some((workspace, _)) = self.workspace.clone() else {
7836 return;
7837 };
7838 let Some(project) = self.project.clone() else {
7839 return;
7840 };
7841
7842 // Try to find a closest, enclosing node using tree-sitter that has a
7843 // task
7844 let Some((buffer, buffer_row, tasks)) = self
7845 .find_enclosing_node_task(cx)
7846 // Or find the task that's closest in row-distance.
7847 .or_else(|| self.find_closest_task(cx))
7848 else {
7849 return;
7850 };
7851
7852 let reveal_strategy = action.reveal;
7853 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7854 cx.spawn_in(window, async move |_, cx| {
7855 let context = task_context.await?;
7856 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7857
7858 let resolved = &mut resolved_task.resolved;
7859 resolved.reveal = reveal_strategy;
7860
7861 workspace
7862 .update_in(cx, |workspace, window, cx| {
7863 workspace.schedule_resolved_task(
7864 task_source_kind,
7865 resolved_task,
7866 false,
7867 window,
7868 cx,
7869 );
7870 })
7871 .ok()
7872 })
7873 .detach();
7874 }
7875
7876 fn find_closest_task(
7877 &mut self,
7878 cx: &mut Context<Self>,
7879 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7880 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7881
7882 let ((buffer_id, row), tasks) = self
7883 .tasks
7884 .iter()
7885 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7886
7887 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7888 let tasks = Arc::new(tasks.to_owned());
7889 Some((buffer, *row, tasks))
7890 }
7891
7892 fn find_enclosing_node_task(
7893 &mut self,
7894 cx: &mut Context<Self>,
7895 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7896 let snapshot = self.buffer.read(cx).snapshot(cx);
7897 let offset = self.selections.newest::<usize>(cx).head();
7898 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7899 let buffer_id = excerpt.buffer().remote_id();
7900
7901 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7902 let mut cursor = layer.node().walk();
7903
7904 while cursor.goto_first_child_for_byte(offset).is_some() {
7905 if cursor.node().end_byte() == offset {
7906 cursor.goto_next_sibling();
7907 }
7908 }
7909
7910 // Ascend to the smallest ancestor that contains the range and has a task.
7911 loop {
7912 let node = cursor.node();
7913 let node_range = node.byte_range();
7914 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7915
7916 // Check if this node contains our offset
7917 if node_range.start <= offset && node_range.end >= offset {
7918 // If it contains offset, check for task
7919 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7920 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7921 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7922 }
7923 }
7924
7925 if !cursor.goto_parent() {
7926 break;
7927 }
7928 }
7929 None
7930 }
7931
7932 fn render_run_indicator(
7933 &self,
7934 _style: &EditorStyle,
7935 is_active: bool,
7936 row: DisplayRow,
7937 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7938 cx: &mut Context<Self>,
7939 ) -> IconButton {
7940 let color = Color::Muted;
7941 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7942
7943 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7944 .shape(ui::IconButtonShape::Square)
7945 .icon_size(IconSize::XSmall)
7946 .icon_color(color)
7947 .toggle_state(is_active)
7948 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7949 let quick_launch = e.down.button == MouseButton::Left;
7950 window.focus(&editor.focus_handle(cx));
7951 editor.toggle_code_actions(
7952 &ToggleCodeActions {
7953 deployed_from: Some(CodeActionSource::RunMenu(row)),
7954 quick_launch,
7955 },
7956 window,
7957 cx,
7958 );
7959 }))
7960 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7961 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7962 }))
7963 }
7964
7965 pub fn context_menu_visible(&self) -> bool {
7966 !self.edit_prediction_preview_is_active()
7967 && self
7968 .context_menu
7969 .borrow()
7970 .as_ref()
7971 .map_or(false, |menu| menu.visible())
7972 }
7973
7974 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7975 self.context_menu
7976 .borrow()
7977 .as_ref()
7978 .map(|menu| menu.origin())
7979 }
7980
7981 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7982 self.context_menu_options = Some(options);
7983 }
7984
7985 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7986 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7987
7988 fn render_edit_prediction_popover(
7989 &mut self,
7990 text_bounds: &Bounds<Pixels>,
7991 content_origin: gpui::Point<Pixels>,
7992 right_margin: Pixels,
7993 editor_snapshot: &EditorSnapshot,
7994 visible_row_range: Range<DisplayRow>,
7995 scroll_top: f32,
7996 scroll_bottom: f32,
7997 line_layouts: &[LineWithInvisibles],
7998 line_height: Pixels,
7999 scroll_pixel_position: gpui::Point<Pixels>,
8000 newest_selection_head: Option<DisplayPoint>,
8001 editor_width: Pixels,
8002 style: &EditorStyle,
8003 window: &mut Window,
8004 cx: &mut App,
8005 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8006 if self.mode().is_minimap() {
8007 return None;
8008 }
8009 let active_inline_completion = self.active_inline_completion.as_ref()?;
8010
8011 if self.edit_prediction_visible_in_cursor_popover(true) {
8012 return None;
8013 }
8014
8015 match &active_inline_completion.completion {
8016 InlineCompletion::Move { target, .. } => {
8017 let target_display_point = target.to_display_point(editor_snapshot);
8018
8019 if self.edit_prediction_requires_modifier() {
8020 if !self.edit_prediction_preview_is_active() {
8021 return None;
8022 }
8023
8024 self.render_edit_prediction_modifier_jump_popover(
8025 text_bounds,
8026 content_origin,
8027 visible_row_range,
8028 line_layouts,
8029 line_height,
8030 scroll_pixel_position,
8031 newest_selection_head,
8032 target_display_point,
8033 window,
8034 cx,
8035 )
8036 } else {
8037 self.render_edit_prediction_eager_jump_popover(
8038 text_bounds,
8039 content_origin,
8040 editor_snapshot,
8041 visible_row_range,
8042 scroll_top,
8043 scroll_bottom,
8044 line_height,
8045 scroll_pixel_position,
8046 target_display_point,
8047 editor_width,
8048 window,
8049 cx,
8050 )
8051 }
8052 }
8053 InlineCompletion::Edit {
8054 display_mode: EditDisplayMode::Inline,
8055 ..
8056 } => None,
8057 InlineCompletion::Edit {
8058 display_mode: EditDisplayMode::TabAccept,
8059 edits,
8060 ..
8061 } => {
8062 let range = &edits.first()?.0;
8063 let target_display_point = range.end.to_display_point(editor_snapshot);
8064
8065 self.render_edit_prediction_end_of_line_popover(
8066 "Accept",
8067 editor_snapshot,
8068 visible_row_range,
8069 target_display_point,
8070 line_height,
8071 scroll_pixel_position,
8072 content_origin,
8073 editor_width,
8074 window,
8075 cx,
8076 )
8077 }
8078 InlineCompletion::Edit {
8079 edits,
8080 edit_preview,
8081 display_mode: EditDisplayMode::DiffPopover,
8082 snapshot,
8083 } => self.render_edit_prediction_diff_popover(
8084 text_bounds,
8085 content_origin,
8086 right_margin,
8087 editor_snapshot,
8088 visible_row_range,
8089 line_layouts,
8090 line_height,
8091 scroll_pixel_position,
8092 newest_selection_head,
8093 editor_width,
8094 style,
8095 edits,
8096 edit_preview,
8097 snapshot,
8098 window,
8099 cx,
8100 ),
8101 }
8102 }
8103
8104 fn render_edit_prediction_modifier_jump_popover(
8105 &mut self,
8106 text_bounds: &Bounds<Pixels>,
8107 content_origin: gpui::Point<Pixels>,
8108 visible_row_range: Range<DisplayRow>,
8109 line_layouts: &[LineWithInvisibles],
8110 line_height: Pixels,
8111 scroll_pixel_position: gpui::Point<Pixels>,
8112 newest_selection_head: Option<DisplayPoint>,
8113 target_display_point: DisplayPoint,
8114 window: &mut Window,
8115 cx: &mut App,
8116 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8117 let scrolled_content_origin =
8118 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8119
8120 const SCROLL_PADDING_Y: Pixels = px(12.);
8121
8122 if target_display_point.row() < visible_row_range.start {
8123 return self.render_edit_prediction_scroll_popover(
8124 |_| SCROLL_PADDING_Y,
8125 IconName::ArrowUp,
8126 visible_row_range,
8127 line_layouts,
8128 newest_selection_head,
8129 scrolled_content_origin,
8130 window,
8131 cx,
8132 );
8133 } else if target_display_point.row() >= visible_row_range.end {
8134 return self.render_edit_prediction_scroll_popover(
8135 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8136 IconName::ArrowDown,
8137 visible_row_range,
8138 line_layouts,
8139 newest_selection_head,
8140 scrolled_content_origin,
8141 window,
8142 cx,
8143 );
8144 }
8145
8146 const POLE_WIDTH: Pixels = px(2.);
8147
8148 let line_layout =
8149 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8150 let target_column = target_display_point.column() as usize;
8151
8152 let target_x = line_layout.x_for_index(target_column);
8153 let target_y =
8154 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8155
8156 let flag_on_right = target_x < text_bounds.size.width / 2.;
8157
8158 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8159 border_color.l += 0.001;
8160
8161 let mut element = v_flex()
8162 .items_end()
8163 .when(flag_on_right, |el| el.items_start())
8164 .child(if flag_on_right {
8165 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8166 .rounded_bl(px(0.))
8167 .rounded_tl(px(0.))
8168 .border_l_2()
8169 .border_color(border_color)
8170 } else {
8171 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8172 .rounded_br(px(0.))
8173 .rounded_tr(px(0.))
8174 .border_r_2()
8175 .border_color(border_color)
8176 })
8177 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8178 .into_any();
8179
8180 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8181
8182 let mut origin = scrolled_content_origin + point(target_x, target_y)
8183 - point(
8184 if flag_on_right {
8185 POLE_WIDTH
8186 } else {
8187 size.width - POLE_WIDTH
8188 },
8189 size.height - line_height,
8190 );
8191
8192 origin.x = origin.x.max(content_origin.x);
8193
8194 element.prepaint_at(origin, window, cx);
8195
8196 Some((element, origin))
8197 }
8198
8199 fn render_edit_prediction_scroll_popover(
8200 &mut self,
8201 to_y: impl Fn(Size<Pixels>) -> Pixels,
8202 scroll_icon: IconName,
8203 visible_row_range: Range<DisplayRow>,
8204 line_layouts: &[LineWithInvisibles],
8205 newest_selection_head: Option<DisplayPoint>,
8206 scrolled_content_origin: gpui::Point<Pixels>,
8207 window: &mut Window,
8208 cx: &mut App,
8209 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8210 let mut element = self
8211 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8212 .into_any();
8213
8214 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8215
8216 let cursor = newest_selection_head?;
8217 let cursor_row_layout =
8218 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8219 let cursor_column = cursor.column() as usize;
8220
8221 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8222
8223 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8224
8225 element.prepaint_at(origin, window, cx);
8226 Some((element, origin))
8227 }
8228
8229 fn render_edit_prediction_eager_jump_popover(
8230 &mut self,
8231 text_bounds: &Bounds<Pixels>,
8232 content_origin: gpui::Point<Pixels>,
8233 editor_snapshot: &EditorSnapshot,
8234 visible_row_range: Range<DisplayRow>,
8235 scroll_top: f32,
8236 scroll_bottom: f32,
8237 line_height: Pixels,
8238 scroll_pixel_position: gpui::Point<Pixels>,
8239 target_display_point: DisplayPoint,
8240 editor_width: Pixels,
8241 window: &mut Window,
8242 cx: &mut App,
8243 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8244 if target_display_point.row().as_f32() < scroll_top {
8245 let mut element = self
8246 .render_edit_prediction_line_popover(
8247 "Jump to Edit",
8248 Some(IconName::ArrowUp),
8249 window,
8250 cx,
8251 )?
8252 .into_any();
8253
8254 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8255 let offset = point(
8256 (text_bounds.size.width - size.width) / 2.,
8257 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8258 );
8259
8260 let origin = text_bounds.origin + offset;
8261 element.prepaint_at(origin, window, cx);
8262 Some((element, origin))
8263 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8264 let mut element = self
8265 .render_edit_prediction_line_popover(
8266 "Jump to Edit",
8267 Some(IconName::ArrowDown),
8268 window,
8269 cx,
8270 )?
8271 .into_any();
8272
8273 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8274 let offset = point(
8275 (text_bounds.size.width - size.width) / 2.,
8276 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8277 );
8278
8279 let origin = text_bounds.origin + offset;
8280 element.prepaint_at(origin, window, cx);
8281 Some((element, origin))
8282 } else {
8283 self.render_edit_prediction_end_of_line_popover(
8284 "Jump to Edit",
8285 editor_snapshot,
8286 visible_row_range,
8287 target_display_point,
8288 line_height,
8289 scroll_pixel_position,
8290 content_origin,
8291 editor_width,
8292 window,
8293 cx,
8294 )
8295 }
8296 }
8297
8298 fn render_edit_prediction_end_of_line_popover(
8299 self: &mut Editor,
8300 label: &'static str,
8301 editor_snapshot: &EditorSnapshot,
8302 visible_row_range: Range<DisplayRow>,
8303 target_display_point: DisplayPoint,
8304 line_height: Pixels,
8305 scroll_pixel_position: gpui::Point<Pixels>,
8306 content_origin: gpui::Point<Pixels>,
8307 editor_width: Pixels,
8308 window: &mut Window,
8309 cx: &mut App,
8310 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8311 let target_line_end = DisplayPoint::new(
8312 target_display_point.row(),
8313 editor_snapshot.line_len(target_display_point.row()),
8314 );
8315
8316 let mut element = self
8317 .render_edit_prediction_line_popover(label, None, window, cx)?
8318 .into_any();
8319
8320 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8321
8322 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8323
8324 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8325 let mut origin = start_point
8326 + line_origin
8327 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8328 origin.x = origin.x.max(content_origin.x);
8329
8330 let max_x = content_origin.x + editor_width - size.width;
8331
8332 if origin.x > max_x {
8333 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8334
8335 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8336 origin.y += offset;
8337 IconName::ArrowUp
8338 } else {
8339 origin.y -= offset;
8340 IconName::ArrowDown
8341 };
8342
8343 element = self
8344 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8345 .into_any();
8346
8347 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8348
8349 origin.x = content_origin.x + editor_width - size.width - px(2.);
8350 }
8351
8352 element.prepaint_at(origin, window, cx);
8353 Some((element, origin))
8354 }
8355
8356 fn render_edit_prediction_diff_popover(
8357 self: &Editor,
8358 text_bounds: &Bounds<Pixels>,
8359 content_origin: gpui::Point<Pixels>,
8360 right_margin: Pixels,
8361 editor_snapshot: &EditorSnapshot,
8362 visible_row_range: Range<DisplayRow>,
8363 line_layouts: &[LineWithInvisibles],
8364 line_height: Pixels,
8365 scroll_pixel_position: gpui::Point<Pixels>,
8366 newest_selection_head: Option<DisplayPoint>,
8367 editor_width: Pixels,
8368 style: &EditorStyle,
8369 edits: &Vec<(Range<Anchor>, String)>,
8370 edit_preview: &Option<language::EditPreview>,
8371 snapshot: &language::BufferSnapshot,
8372 window: &mut Window,
8373 cx: &mut App,
8374 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8375 let edit_start = edits
8376 .first()
8377 .unwrap()
8378 .0
8379 .start
8380 .to_display_point(editor_snapshot);
8381 let edit_end = edits
8382 .last()
8383 .unwrap()
8384 .0
8385 .end
8386 .to_display_point(editor_snapshot);
8387
8388 let is_visible = visible_row_range.contains(&edit_start.row())
8389 || visible_row_range.contains(&edit_end.row());
8390 if !is_visible {
8391 return None;
8392 }
8393
8394 let highlighted_edits =
8395 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8396
8397 let styled_text = highlighted_edits.to_styled_text(&style.text);
8398 let line_count = highlighted_edits.text.lines().count();
8399
8400 const BORDER_WIDTH: Pixels = px(1.);
8401
8402 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8403 let has_keybind = keybind.is_some();
8404
8405 let mut element = h_flex()
8406 .items_start()
8407 .child(
8408 h_flex()
8409 .bg(cx.theme().colors().editor_background)
8410 .border(BORDER_WIDTH)
8411 .shadow_sm()
8412 .border_color(cx.theme().colors().border)
8413 .rounded_l_lg()
8414 .when(line_count > 1, |el| el.rounded_br_lg())
8415 .pr_1()
8416 .child(styled_text),
8417 )
8418 .child(
8419 h_flex()
8420 .h(line_height + BORDER_WIDTH * 2.)
8421 .px_1p5()
8422 .gap_1()
8423 // Workaround: For some reason, there's a gap if we don't do this
8424 .ml(-BORDER_WIDTH)
8425 .shadow(vec![gpui::BoxShadow {
8426 color: gpui::black().opacity(0.05),
8427 offset: point(px(1.), px(1.)),
8428 blur_radius: px(2.),
8429 spread_radius: px(0.),
8430 }])
8431 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8432 .border(BORDER_WIDTH)
8433 .border_color(cx.theme().colors().border)
8434 .rounded_r_lg()
8435 .id("edit_prediction_diff_popover_keybind")
8436 .when(!has_keybind, |el| {
8437 let status_colors = cx.theme().status();
8438
8439 el.bg(status_colors.error_background)
8440 .border_color(status_colors.error.opacity(0.6))
8441 .child(Icon::new(IconName::Info).color(Color::Error))
8442 .cursor_default()
8443 .hoverable_tooltip(move |_window, cx| {
8444 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8445 })
8446 })
8447 .children(keybind),
8448 )
8449 .into_any();
8450
8451 let longest_row =
8452 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8453 let longest_line_width = if visible_row_range.contains(&longest_row) {
8454 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8455 } else {
8456 layout_line(
8457 longest_row,
8458 editor_snapshot,
8459 style,
8460 editor_width,
8461 |_| false,
8462 window,
8463 cx,
8464 )
8465 .width
8466 };
8467
8468 let viewport_bounds =
8469 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8470 right: -right_margin,
8471 ..Default::default()
8472 });
8473
8474 let x_after_longest =
8475 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8476 - scroll_pixel_position.x;
8477
8478 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8479
8480 // Fully visible if it can be displayed within the window (allow overlapping other
8481 // panes). However, this is only allowed if the popover starts within text_bounds.
8482 let can_position_to_the_right = x_after_longest < text_bounds.right()
8483 && x_after_longest + element_bounds.width < viewport_bounds.right();
8484
8485 let mut origin = if can_position_to_the_right {
8486 point(
8487 x_after_longest,
8488 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8489 - scroll_pixel_position.y,
8490 )
8491 } else {
8492 let cursor_row = newest_selection_head.map(|head| head.row());
8493 let above_edit = edit_start
8494 .row()
8495 .0
8496 .checked_sub(line_count as u32)
8497 .map(DisplayRow);
8498 let below_edit = Some(edit_end.row() + 1);
8499 let above_cursor =
8500 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8501 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8502
8503 // Place the edit popover adjacent to the edit if there is a location
8504 // available that is onscreen and does not obscure the cursor. Otherwise,
8505 // place it adjacent to the cursor.
8506 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8507 .into_iter()
8508 .flatten()
8509 .find(|&start_row| {
8510 let end_row = start_row + line_count as u32;
8511 visible_row_range.contains(&start_row)
8512 && visible_row_range.contains(&end_row)
8513 && cursor_row.map_or(true, |cursor_row| {
8514 !((start_row..end_row).contains(&cursor_row))
8515 })
8516 })?;
8517
8518 content_origin
8519 + point(
8520 -scroll_pixel_position.x,
8521 row_target.as_f32() * line_height - scroll_pixel_position.y,
8522 )
8523 };
8524
8525 origin.x -= BORDER_WIDTH;
8526
8527 window.defer_draw(element, origin, 1);
8528
8529 // Do not return an element, since it will already be drawn due to defer_draw.
8530 None
8531 }
8532
8533 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8534 px(30.)
8535 }
8536
8537 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8538 if self.read_only(cx) {
8539 cx.theme().players().read_only()
8540 } else {
8541 self.style.as_ref().unwrap().local_player
8542 }
8543 }
8544
8545 fn render_edit_prediction_accept_keybind(
8546 &self,
8547 window: &mut Window,
8548 cx: &App,
8549 ) -> Option<AnyElement> {
8550 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8551 let accept_keystroke = accept_binding.keystroke()?;
8552
8553 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8554
8555 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8556 Color::Accent
8557 } else {
8558 Color::Muted
8559 };
8560
8561 h_flex()
8562 .px_0p5()
8563 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8564 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8565 .text_size(TextSize::XSmall.rems(cx))
8566 .child(h_flex().children(ui::render_modifiers(
8567 &accept_keystroke.modifiers,
8568 PlatformStyle::platform(),
8569 Some(modifiers_color),
8570 Some(IconSize::XSmall.rems().into()),
8571 true,
8572 )))
8573 .when(is_platform_style_mac, |parent| {
8574 parent.child(accept_keystroke.key.clone())
8575 })
8576 .when(!is_platform_style_mac, |parent| {
8577 parent.child(
8578 Key::new(
8579 util::capitalize(&accept_keystroke.key),
8580 Some(Color::Default),
8581 )
8582 .size(Some(IconSize::XSmall.rems().into())),
8583 )
8584 })
8585 .into_any()
8586 .into()
8587 }
8588
8589 fn render_edit_prediction_line_popover(
8590 &self,
8591 label: impl Into<SharedString>,
8592 icon: Option<IconName>,
8593 window: &mut Window,
8594 cx: &App,
8595 ) -> Option<Stateful<Div>> {
8596 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8597
8598 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8599 let has_keybind = keybind.is_some();
8600
8601 let result = h_flex()
8602 .id("ep-line-popover")
8603 .py_0p5()
8604 .pl_1()
8605 .pr(padding_right)
8606 .gap_1()
8607 .rounded_md()
8608 .border_1()
8609 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8610 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8611 .shadow_sm()
8612 .when(!has_keybind, |el| {
8613 let status_colors = cx.theme().status();
8614
8615 el.bg(status_colors.error_background)
8616 .border_color(status_colors.error.opacity(0.6))
8617 .pl_2()
8618 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8619 .cursor_default()
8620 .hoverable_tooltip(move |_window, cx| {
8621 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8622 })
8623 })
8624 .children(keybind)
8625 .child(
8626 Label::new(label)
8627 .size(LabelSize::Small)
8628 .when(!has_keybind, |el| {
8629 el.color(cx.theme().status().error.into()).strikethrough()
8630 }),
8631 )
8632 .when(!has_keybind, |el| {
8633 el.child(
8634 h_flex().ml_1().child(
8635 Icon::new(IconName::Info)
8636 .size(IconSize::Small)
8637 .color(cx.theme().status().error.into()),
8638 ),
8639 )
8640 })
8641 .when_some(icon, |element, icon| {
8642 element.child(
8643 div()
8644 .mt(px(1.5))
8645 .child(Icon::new(icon).size(IconSize::Small)),
8646 )
8647 });
8648
8649 Some(result)
8650 }
8651
8652 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8653 let accent_color = cx.theme().colors().text_accent;
8654 let editor_bg_color = cx.theme().colors().editor_background;
8655 editor_bg_color.blend(accent_color.opacity(0.1))
8656 }
8657
8658 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8659 let accent_color = cx.theme().colors().text_accent;
8660 let editor_bg_color = cx.theme().colors().editor_background;
8661 editor_bg_color.blend(accent_color.opacity(0.6))
8662 }
8663
8664 fn render_edit_prediction_cursor_popover(
8665 &self,
8666 min_width: Pixels,
8667 max_width: Pixels,
8668 cursor_point: Point,
8669 style: &EditorStyle,
8670 accept_keystroke: Option<&gpui::Keystroke>,
8671 _window: &Window,
8672 cx: &mut Context<Editor>,
8673 ) -> Option<AnyElement> {
8674 let provider = self.edit_prediction_provider.as_ref()?;
8675
8676 if provider.provider.needs_terms_acceptance(cx) {
8677 return Some(
8678 h_flex()
8679 .min_w(min_width)
8680 .flex_1()
8681 .px_2()
8682 .py_1()
8683 .gap_3()
8684 .elevation_2(cx)
8685 .hover(|style| style.bg(cx.theme().colors().element_hover))
8686 .id("accept-terms")
8687 .cursor_pointer()
8688 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8689 .on_click(cx.listener(|this, _event, window, cx| {
8690 cx.stop_propagation();
8691 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8692 window.dispatch_action(
8693 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8694 cx,
8695 );
8696 }))
8697 .child(
8698 h_flex()
8699 .flex_1()
8700 .gap_2()
8701 .child(Icon::new(IconName::ZedPredict))
8702 .child(Label::new("Accept Terms of Service"))
8703 .child(div().w_full())
8704 .child(
8705 Icon::new(IconName::ArrowUpRight)
8706 .color(Color::Muted)
8707 .size(IconSize::Small),
8708 )
8709 .into_any_element(),
8710 )
8711 .into_any(),
8712 );
8713 }
8714
8715 let is_refreshing = provider.provider.is_refreshing(cx);
8716
8717 fn pending_completion_container() -> Div {
8718 h_flex()
8719 .h_full()
8720 .flex_1()
8721 .gap_2()
8722 .child(Icon::new(IconName::ZedPredict))
8723 }
8724
8725 let completion = match &self.active_inline_completion {
8726 Some(prediction) => {
8727 if !self.has_visible_completions_menu() {
8728 const RADIUS: Pixels = px(6.);
8729 const BORDER_WIDTH: Pixels = px(1.);
8730
8731 return Some(
8732 h_flex()
8733 .elevation_2(cx)
8734 .border(BORDER_WIDTH)
8735 .border_color(cx.theme().colors().border)
8736 .when(accept_keystroke.is_none(), |el| {
8737 el.border_color(cx.theme().status().error)
8738 })
8739 .rounded(RADIUS)
8740 .rounded_tl(px(0.))
8741 .overflow_hidden()
8742 .child(div().px_1p5().child(match &prediction.completion {
8743 InlineCompletion::Move { target, snapshot } => {
8744 use text::ToPoint as _;
8745 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8746 {
8747 Icon::new(IconName::ZedPredictDown)
8748 } else {
8749 Icon::new(IconName::ZedPredictUp)
8750 }
8751 }
8752 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8753 }))
8754 .child(
8755 h_flex()
8756 .gap_1()
8757 .py_1()
8758 .px_2()
8759 .rounded_r(RADIUS - BORDER_WIDTH)
8760 .border_l_1()
8761 .border_color(cx.theme().colors().border)
8762 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8763 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8764 el.child(
8765 Label::new("Hold")
8766 .size(LabelSize::Small)
8767 .when(accept_keystroke.is_none(), |el| {
8768 el.strikethrough()
8769 })
8770 .line_height_style(LineHeightStyle::UiLabel),
8771 )
8772 })
8773 .id("edit_prediction_cursor_popover_keybind")
8774 .when(accept_keystroke.is_none(), |el| {
8775 let status_colors = cx.theme().status();
8776
8777 el.bg(status_colors.error_background)
8778 .border_color(status_colors.error.opacity(0.6))
8779 .child(Icon::new(IconName::Info).color(Color::Error))
8780 .cursor_default()
8781 .hoverable_tooltip(move |_window, cx| {
8782 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8783 .into()
8784 })
8785 })
8786 .when_some(
8787 accept_keystroke.as_ref(),
8788 |el, accept_keystroke| {
8789 el.child(h_flex().children(ui::render_modifiers(
8790 &accept_keystroke.modifiers,
8791 PlatformStyle::platform(),
8792 Some(Color::Default),
8793 Some(IconSize::XSmall.rems().into()),
8794 false,
8795 )))
8796 },
8797 ),
8798 )
8799 .into_any(),
8800 );
8801 }
8802
8803 self.render_edit_prediction_cursor_popover_preview(
8804 prediction,
8805 cursor_point,
8806 style,
8807 cx,
8808 )?
8809 }
8810
8811 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8812 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8813 stale_completion,
8814 cursor_point,
8815 style,
8816 cx,
8817 )?,
8818
8819 None => {
8820 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8821 }
8822 },
8823
8824 None => pending_completion_container().child(Label::new("No Prediction")),
8825 };
8826
8827 let completion = if is_refreshing {
8828 completion
8829 .with_animation(
8830 "loading-completion",
8831 Animation::new(Duration::from_secs(2))
8832 .repeat()
8833 .with_easing(pulsating_between(0.4, 0.8)),
8834 |label, delta| label.opacity(delta),
8835 )
8836 .into_any_element()
8837 } else {
8838 completion.into_any_element()
8839 };
8840
8841 let has_completion = self.active_inline_completion.is_some();
8842
8843 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8844 Some(
8845 h_flex()
8846 .min_w(min_width)
8847 .max_w(max_width)
8848 .flex_1()
8849 .elevation_2(cx)
8850 .border_color(cx.theme().colors().border)
8851 .child(
8852 div()
8853 .flex_1()
8854 .py_1()
8855 .px_2()
8856 .overflow_hidden()
8857 .child(completion),
8858 )
8859 .when_some(accept_keystroke, |el, accept_keystroke| {
8860 if !accept_keystroke.modifiers.modified() {
8861 return el;
8862 }
8863
8864 el.child(
8865 h_flex()
8866 .h_full()
8867 .border_l_1()
8868 .rounded_r_lg()
8869 .border_color(cx.theme().colors().border)
8870 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8871 .gap_1()
8872 .py_1()
8873 .px_2()
8874 .child(
8875 h_flex()
8876 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8877 .when(is_platform_style_mac, |parent| parent.gap_1())
8878 .child(h_flex().children(ui::render_modifiers(
8879 &accept_keystroke.modifiers,
8880 PlatformStyle::platform(),
8881 Some(if !has_completion {
8882 Color::Muted
8883 } else {
8884 Color::Default
8885 }),
8886 None,
8887 false,
8888 ))),
8889 )
8890 .child(Label::new("Preview").into_any_element())
8891 .opacity(if has_completion { 1.0 } else { 0.4 }),
8892 )
8893 })
8894 .into_any(),
8895 )
8896 }
8897
8898 fn render_edit_prediction_cursor_popover_preview(
8899 &self,
8900 completion: &InlineCompletionState,
8901 cursor_point: Point,
8902 style: &EditorStyle,
8903 cx: &mut Context<Editor>,
8904 ) -> Option<Div> {
8905 use text::ToPoint as _;
8906
8907 fn render_relative_row_jump(
8908 prefix: impl Into<String>,
8909 current_row: u32,
8910 target_row: u32,
8911 ) -> Div {
8912 let (row_diff, arrow) = if target_row < current_row {
8913 (current_row - target_row, IconName::ArrowUp)
8914 } else {
8915 (target_row - current_row, IconName::ArrowDown)
8916 };
8917
8918 h_flex()
8919 .child(
8920 Label::new(format!("{}{}", prefix.into(), row_diff))
8921 .color(Color::Muted)
8922 .size(LabelSize::Small),
8923 )
8924 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8925 }
8926
8927 match &completion.completion {
8928 InlineCompletion::Move {
8929 target, snapshot, ..
8930 } => Some(
8931 h_flex()
8932 .px_2()
8933 .gap_2()
8934 .flex_1()
8935 .child(
8936 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8937 Icon::new(IconName::ZedPredictDown)
8938 } else {
8939 Icon::new(IconName::ZedPredictUp)
8940 },
8941 )
8942 .child(Label::new("Jump to Edit")),
8943 ),
8944
8945 InlineCompletion::Edit {
8946 edits,
8947 edit_preview,
8948 snapshot,
8949 display_mode: _,
8950 } => {
8951 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8952
8953 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8954 &snapshot,
8955 &edits,
8956 edit_preview.as_ref()?,
8957 true,
8958 cx,
8959 )
8960 .first_line_preview();
8961
8962 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8963 .with_default_highlights(&style.text, highlighted_edits.highlights);
8964
8965 let preview = h_flex()
8966 .gap_1()
8967 .min_w_16()
8968 .child(styled_text)
8969 .when(has_more_lines, |parent| parent.child("…"));
8970
8971 let left = if first_edit_row != cursor_point.row {
8972 render_relative_row_jump("", cursor_point.row, first_edit_row)
8973 .into_any_element()
8974 } else {
8975 Icon::new(IconName::ZedPredict).into_any_element()
8976 };
8977
8978 Some(
8979 h_flex()
8980 .h_full()
8981 .flex_1()
8982 .gap_2()
8983 .pr_1()
8984 .overflow_x_hidden()
8985 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8986 .child(left)
8987 .child(preview),
8988 )
8989 }
8990 }
8991 }
8992
8993 pub fn render_context_menu(
8994 &self,
8995 style: &EditorStyle,
8996 max_height_in_lines: u32,
8997 window: &mut Window,
8998 cx: &mut Context<Editor>,
8999 ) -> Option<AnyElement> {
9000 let menu = self.context_menu.borrow();
9001 let menu = menu.as_ref()?;
9002 if !menu.visible() {
9003 return None;
9004 };
9005 Some(menu.render(style, max_height_in_lines, window, cx))
9006 }
9007
9008 fn render_context_menu_aside(
9009 &mut self,
9010 max_size: Size<Pixels>,
9011 window: &mut Window,
9012 cx: &mut Context<Editor>,
9013 ) -> Option<AnyElement> {
9014 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9015 if menu.visible() {
9016 menu.render_aside(max_size, window, cx)
9017 } else {
9018 None
9019 }
9020 })
9021 }
9022
9023 fn hide_context_menu(
9024 &mut self,
9025 window: &mut Window,
9026 cx: &mut Context<Self>,
9027 ) -> Option<CodeContextMenu> {
9028 cx.notify();
9029 self.completion_tasks.clear();
9030 let context_menu = self.context_menu.borrow_mut().take();
9031 self.stale_inline_completion_in_menu.take();
9032 self.update_visible_inline_completion(window, cx);
9033 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9034 if let Some(completion_provider) = &self.completion_provider {
9035 completion_provider.selection_changed(None, window, cx);
9036 }
9037 }
9038 context_menu
9039 }
9040
9041 fn show_snippet_choices(
9042 &mut self,
9043 choices: &Vec<String>,
9044 selection: Range<Anchor>,
9045 cx: &mut Context<Self>,
9046 ) {
9047 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9048 (Some(a), Some(b)) if a == b => a,
9049 _ => {
9050 log::error!("expected anchor range to have matching buffer IDs");
9051 return;
9052 }
9053 };
9054 let multi_buffer = self.buffer().read(cx);
9055 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9056 return;
9057 };
9058
9059 let id = post_inc(&mut self.next_completion_id);
9060 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9061 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9062 CompletionsMenu::new_snippet_choices(
9063 id,
9064 true,
9065 choices,
9066 selection,
9067 buffer,
9068 snippet_sort_order,
9069 ),
9070 ));
9071 }
9072
9073 pub fn insert_snippet(
9074 &mut self,
9075 insertion_ranges: &[Range<usize>],
9076 snippet: Snippet,
9077 window: &mut Window,
9078 cx: &mut Context<Self>,
9079 ) -> Result<()> {
9080 struct Tabstop<T> {
9081 is_end_tabstop: bool,
9082 ranges: Vec<Range<T>>,
9083 choices: Option<Vec<String>>,
9084 }
9085
9086 let tabstops = self.buffer.update(cx, |buffer, cx| {
9087 let snippet_text: Arc<str> = snippet.text.clone().into();
9088 let edits = insertion_ranges
9089 .iter()
9090 .cloned()
9091 .map(|range| (range, snippet_text.clone()));
9092 let autoindent_mode = AutoindentMode::Block {
9093 original_indent_columns: Vec::new(),
9094 };
9095 buffer.edit(edits, Some(autoindent_mode), cx);
9096
9097 let snapshot = &*buffer.read(cx);
9098 let snippet = &snippet;
9099 snippet
9100 .tabstops
9101 .iter()
9102 .map(|tabstop| {
9103 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9104 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9105 });
9106 let mut tabstop_ranges = tabstop
9107 .ranges
9108 .iter()
9109 .flat_map(|tabstop_range| {
9110 let mut delta = 0_isize;
9111 insertion_ranges.iter().map(move |insertion_range| {
9112 let insertion_start = insertion_range.start as isize + delta;
9113 delta +=
9114 snippet.text.len() as isize - insertion_range.len() as isize;
9115
9116 let start = ((insertion_start + tabstop_range.start) as usize)
9117 .min(snapshot.len());
9118 let end = ((insertion_start + tabstop_range.end) as usize)
9119 .min(snapshot.len());
9120 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9121 })
9122 })
9123 .collect::<Vec<_>>();
9124 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9125
9126 Tabstop {
9127 is_end_tabstop,
9128 ranges: tabstop_ranges,
9129 choices: tabstop.choices.clone(),
9130 }
9131 })
9132 .collect::<Vec<_>>()
9133 });
9134 if let Some(tabstop) = tabstops.first() {
9135 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9136 // Reverse order so that the first range is the newest created selection.
9137 // Completions will use it and autoscroll will prioritize it.
9138 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9139 });
9140
9141 if let Some(choices) = &tabstop.choices {
9142 if let Some(selection) = tabstop.ranges.first() {
9143 self.show_snippet_choices(choices, selection.clone(), cx)
9144 }
9145 }
9146
9147 // If we're already at the last tabstop and it's at the end of the snippet,
9148 // we're done, we don't need to keep the state around.
9149 if !tabstop.is_end_tabstop {
9150 let choices = tabstops
9151 .iter()
9152 .map(|tabstop| tabstop.choices.clone())
9153 .collect();
9154
9155 let ranges = tabstops
9156 .into_iter()
9157 .map(|tabstop| tabstop.ranges)
9158 .collect::<Vec<_>>();
9159
9160 self.snippet_stack.push(SnippetState {
9161 active_index: 0,
9162 ranges,
9163 choices,
9164 });
9165 }
9166
9167 // Check whether the just-entered snippet ends with an auto-closable bracket.
9168 if self.autoclose_regions.is_empty() {
9169 let snapshot = self.buffer.read(cx).snapshot(cx);
9170 for selection in &mut self.selections.all::<Point>(cx) {
9171 let selection_head = selection.head();
9172 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9173 continue;
9174 };
9175
9176 let mut bracket_pair = None;
9177 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9178 let prev_chars = snapshot
9179 .reversed_chars_at(selection_head)
9180 .collect::<String>();
9181 for (pair, enabled) in scope.brackets() {
9182 if enabled
9183 && pair.close
9184 && prev_chars.starts_with(pair.start.as_str())
9185 && next_chars.starts_with(pair.end.as_str())
9186 {
9187 bracket_pair = Some(pair.clone());
9188 break;
9189 }
9190 }
9191 if let Some(pair) = bracket_pair {
9192 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9193 let autoclose_enabled =
9194 self.use_autoclose && snapshot_settings.use_autoclose;
9195 if autoclose_enabled {
9196 let start = snapshot.anchor_after(selection_head);
9197 let end = snapshot.anchor_after(selection_head);
9198 self.autoclose_regions.push(AutocloseRegion {
9199 selection_id: selection.id,
9200 range: start..end,
9201 pair,
9202 });
9203 }
9204 }
9205 }
9206 }
9207 }
9208 Ok(())
9209 }
9210
9211 pub fn move_to_next_snippet_tabstop(
9212 &mut self,
9213 window: &mut Window,
9214 cx: &mut Context<Self>,
9215 ) -> bool {
9216 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9217 }
9218
9219 pub fn move_to_prev_snippet_tabstop(
9220 &mut self,
9221 window: &mut Window,
9222 cx: &mut Context<Self>,
9223 ) -> bool {
9224 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9225 }
9226
9227 pub fn move_to_snippet_tabstop(
9228 &mut self,
9229 bias: Bias,
9230 window: &mut Window,
9231 cx: &mut Context<Self>,
9232 ) -> bool {
9233 if let Some(mut snippet) = self.snippet_stack.pop() {
9234 match bias {
9235 Bias::Left => {
9236 if snippet.active_index > 0 {
9237 snippet.active_index -= 1;
9238 } else {
9239 self.snippet_stack.push(snippet);
9240 return false;
9241 }
9242 }
9243 Bias::Right => {
9244 if snippet.active_index + 1 < snippet.ranges.len() {
9245 snippet.active_index += 1;
9246 } else {
9247 self.snippet_stack.push(snippet);
9248 return false;
9249 }
9250 }
9251 }
9252 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9253 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9254 // Reverse order so that the first range is the newest created selection.
9255 // Completions will use it and autoscroll will prioritize it.
9256 s.select_ranges(current_ranges.iter().rev().cloned())
9257 });
9258
9259 if let Some(choices) = &snippet.choices[snippet.active_index] {
9260 if let Some(selection) = current_ranges.first() {
9261 self.show_snippet_choices(&choices, selection.clone(), cx);
9262 }
9263 }
9264
9265 // If snippet state is not at the last tabstop, push it back on the stack
9266 if snippet.active_index + 1 < snippet.ranges.len() {
9267 self.snippet_stack.push(snippet);
9268 }
9269 return true;
9270 }
9271 }
9272
9273 false
9274 }
9275
9276 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9277 self.transact(window, cx, |this, window, cx| {
9278 this.select_all(&SelectAll, window, cx);
9279 this.insert("", window, cx);
9280 });
9281 }
9282
9283 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9284 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9285 self.transact(window, cx, |this, window, cx| {
9286 this.select_autoclose_pair(window, cx);
9287 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9288 if !this.linked_edit_ranges.is_empty() {
9289 let selections = this.selections.all::<MultiBufferPoint>(cx);
9290 let snapshot = this.buffer.read(cx).snapshot(cx);
9291
9292 for selection in selections.iter() {
9293 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9294 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9295 if selection_start.buffer_id != selection_end.buffer_id {
9296 continue;
9297 }
9298 if let Some(ranges) =
9299 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9300 {
9301 for (buffer, entries) in ranges {
9302 linked_ranges.entry(buffer).or_default().extend(entries);
9303 }
9304 }
9305 }
9306 }
9307
9308 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9309 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9310 for selection in &mut selections {
9311 if selection.is_empty() {
9312 let old_head = selection.head();
9313 let mut new_head =
9314 movement::left(&display_map, old_head.to_display_point(&display_map))
9315 .to_point(&display_map);
9316 if let Some((buffer, line_buffer_range)) = display_map
9317 .buffer_snapshot
9318 .buffer_line_for_row(MultiBufferRow(old_head.row))
9319 {
9320 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9321 let indent_len = match indent_size.kind {
9322 IndentKind::Space => {
9323 buffer.settings_at(line_buffer_range.start, cx).tab_size
9324 }
9325 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9326 };
9327 if old_head.column <= indent_size.len && old_head.column > 0 {
9328 let indent_len = indent_len.get();
9329 new_head = cmp::min(
9330 new_head,
9331 MultiBufferPoint::new(
9332 old_head.row,
9333 ((old_head.column - 1) / indent_len) * indent_len,
9334 ),
9335 );
9336 }
9337 }
9338
9339 selection.set_head(new_head, SelectionGoal::None);
9340 }
9341 }
9342
9343 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9344 s.select(selections)
9345 });
9346 this.insert("", window, cx);
9347 let empty_str: Arc<str> = Arc::from("");
9348 for (buffer, edits) in linked_ranges {
9349 let snapshot = buffer.read(cx).snapshot();
9350 use text::ToPoint as TP;
9351
9352 let edits = edits
9353 .into_iter()
9354 .map(|range| {
9355 let end_point = TP::to_point(&range.end, &snapshot);
9356 let mut start_point = TP::to_point(&range.start, &snapshot);
9357
9358 if end_point == start_point {
9359 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9360 .saturating_sub(1);
9361 start_point =
9362 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9363 };
9364
9365 (start_point..end_point, empty_str.clone())
9366 })
9367 .sorted_by_key(|(range, _)| range.start)
9368 .collect::<Vec<_>>();
9369 buffer.update(cx, |this, cx| {
9370 this.edit(edits, None, cx);
9371 })
9372 }
9373 this.refresh_inline_completion(true, false, window, cx);
9374 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9375 });
9376 }
9377
9378 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9379 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9380 self.transact(window, cx, |this, window, cx| {
9381 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9382 s.move_with(|map, selection| {
9383 if selection.is_empty() {
9384 let cursor = movement::right(map, selection.head());
9385 selection.end = cursor;
9386 selection.reversed = true;
9387 selection.goal = SelectionGoal::None;
9388 }
9389 })
9390 });
9391 this.insert("", window, cx);
9392 this.refresh_inline_completion(true, false, window, cx);
9393 });
9394 }
9395
9396 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9397 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9398 if self.move_to_prev_snippet_tabstop(window, cx) {
9399 return;
9400 }
9401 self.outdent(&Outdent, window, cx);
9402 }
9403
9404 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9405 if self.move_to_next_snippet_tabstop(window, cx) {
9406 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9407 return;
9408 }
9409 if self.read_only(cx) {
9410 return;
9411 }
9412 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9413 let mut selections = self.selections.all_adjusted(cx);
9414 let buffer = self.buffer.read(cx);
9415 let snapshot = buffer.snapshot(cx);
9416 let rows_iter = selections.iter().map(|s| s.head().row);
9417 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9418
9419 let has_some_cursor_in_whitespace = selections
9420 .iter()
9421 .filter(|selection| selection.is_empty())
9422 .any(|selection| {
9423 let cursor = selection.head();
9424 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9425 cursor.column < current_indent.len
9426 });
9427
9428 let mut edits = Vec::new();
9429 let mut prev_edited_row = 0;
9430 let mut row_delta = 0;
9431 for selection in &mut selections {
9432 if selection.start.row != prev_edited_row {
9433 row_delta = 0;
9434 }
9435 prev_edited_row = selection.end.row;
9436
9437 // If the selection is non-empty, then increase the indentation of the selected lines.
9438 if !selection.is_empty() {
9439 row_delta =
9440 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9441 continue;
9442 }
9443
9444 let cursor = selection.head();
9445 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9446 if let Some(suggested_indent) =
9447 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9448 {
9449 // Don't do anything if already at suggested indent
9450 // and there is any other cursor which is not
9451 if has_some_cursor_in_whitespace
9452 && cursor.column == current_indent.len
9453 && current_indent.len == suggested_indent.len
9454 {
9455 continue;
9456 }
9457
9458 // Adjust line and move cursor to suggested indent
9459 // if cursor is not at suggested indent
9460 if cursor.column < suggested_indent.len
9461 && cursor.column <= current_indent.len
9462 && current_indent.len <= suggested_indent.len
9463 {
9464 selection.start = Point::new(cursor.row, suggested_indent.len);
9465 selection.end = selection.start;
9466 if row_delta == 0 {
9467 edits.extend(Buffer::edit_for_indent_size_adjustment(
9468 cursor.row,
9469 current_indent,
9470 suggested_indent,
9471 ));
9472 row_delta = suggested_indent.len - current_indent.len;
9473 }
9474 continue;
9475 }
9476
9477 // If current indent is more than suggested indent
9478 // only move cursor to current indent and skip indent
9479 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9480 selection.start = Point::new(cursor.row, current_indent.len);
9481 selection.end = selection.start;
9482 continue;
9483 }
9484 }
9485
9486 // Otherwise, insert a hard or soft tab.
9487 let settings = buffer.language_settings_at(cursor, cx);
9488 let tab_size = if settings.hard_tabs {
9489 IndentSize::tab()
9490 } else {
9491 let tab_size = settings.tab_size.get();
9492 let indent_remainder = snapshot
9493 .text_for_range(Point::new(cursor.row, 0)..cursor)
9494 .flat_map(str::chars)
9495 .fold(row_delta % tab_size, |counter: u32, c| {
9496 if c == '\t' {
9497 0
9498 } else {
9499 (counter + 1) % tab_size
9500 }
9501 });
9502
9503 let chars_to_next_tab_stop = tab_size - indent_remainder;
9504 IndentSize::spaces(chars_to_next_tab_stop)
9505 };
9506 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9507 selection.end = selection.start;
9508 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9509 row_delta += tab_size.len;
9510 }
9511
9512 self.transact(window, cx, |this, window, cx| {
9513 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9514 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9515 s.select(selections)
9516 });
9517 this.refresh_inline_completion(true, false, window, cx);
9518 });
9519 }
9520
9521 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9522 if self.read_only(cx) {
9523 return;
9524 }
9525 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9526 let mut selections = self.selections.all::<Point>(cx);
9527 let mut prev_edited_row = 0;
9528 let mut row_delta = 0;
9529 let mut edits = Vec::new();
9530 let buffer = self.buffer.read(cx);
9531 let snapshot = buffer.snapshot(cx);
9532 for selection in &mut selections {
9533 if selection.start.row != prev_edited_row {
9534 row_delta = 0;
9535 }
9536 prev_edited_row = selection.end.row;
9537
9538 row_delta =
9539 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9540 }
9541
9542 self.transact(window, cx, |this, window, cx| {
9543 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9544 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9545 s.select(selections)
9546 });
9547 });
9548 }
9549
9550 fn indent_selection(
9551 buffer: &MultiBuffer,
9552 snapshot: &MultiBufferSnapshot,
9553 selection: &mut Selection<Point>,
9554 edits: &mut Vec<(Range<Point>, String)>,
9555 delta_for_start_row: u32,
9556 cx: &App,
9557 ) -> u32 {
9558 let settings = buffer.language_settings_at(selection.start, cx);
9559 let tab_size = settings.tab_size.get();
9560 let indent_kind = if settings.hard_tabs {
9561 IndentKind::Tab
9562 } else {
9563 IndentKind::Space
9564 };
9565 let mut start_row = selection.start.row;
9566 let mut end_row = selection.end.row + 1;
9567
9568 // If a selection ends at the beginning of a line, don't indent
9569 // that last line.
9570 if selection.end.column == 0 && selection.end.row > selection.start.row {
9571 end_row -= 1;
9572 }
9573
9574 // Avoid re-indenting a row that has already been indented by a
9575 // previous selection, but still update this selection's column
9576 // to reflect that indentation.
9577 if delta_for_start_row > 0 {
9578 start_row += 1;
9579 selection.start.column += delta_for_start_row;
9580 if selection.end.row == selection.start.row {
9581 selection.end.column += delta_for_start_row;
9582 }
9583 }
9584
9585 let mut delta_for_end_row = 0;
9586 let has_multiple_rows = start_row + 1 != end_row;
9587 for row in start_row..end_row {
9588 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9589 let indent_delta = match (current_indent.kind, indent_kind) {
9590 (IndentKind::Space, IndentKind::Space) => {
9591 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9592 IndentSize::spaces(columns_to_next_tab_stop)
9593 }
9594 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9595 (_, IndentKind::Tab) => IndentSize::tab(),
9596 };
9597
9598 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9599 0
9600 } else {
9601 selection.start.column
9602 };
9603 let row_start = Point::new(row, start);
9604 edits.push((
9605 row_start..row_start,
9606 indent_delta.chars().collect::<String>(),
9607 ));
9608
9609 // Update this selection's endpoints to reflect the indentation.
9610 if row == selection.start.row {
9611 selection.start.column += indent_delta.len;
9612 }
9613 if row == selection.end.row {
9614 selection.end.column += indent_delta.len;
9615 delta_for_end_row = indent_delta.len;
9616 }
9617 }
9618
9619 if selection.start.row == selection.end.row {
9620 delta_for_start_row + delta_for_end_row
9621 } else {
9622 delta_for_end_row
9623 }
9624 }
9625
9626 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9627 if self.read_only(cx) {
9628 return;
9629 }
9630 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9632 let selections = self.selections.all::<Point>(cx);
9633 let mut deletion_ranges = Vec::new();
9634 let mut last_outdent = None;
9635 {
9636 let buffer = self.buffer.read(cx);
9637 let snapshot = buffer.snapshot(cx);
9638 for selection in &selections {
9639 let settings = buffer.language_settings_at(selection.start, cx);
9640 let tab_size = settings.tab_size.get();
9641 let mut rows = selection.spanned_rows(false, &display_map);
9642
9643 // Avoid re-outdenting a row that has already been outdented by a
9644 // previous selection.
9645 if let Some(last_row) = last_outdent {
9646 if last_row == rows.start {
9647 rows.start = rows.start.next_row();
9648 }
9649 }
9650 let has_multiple_rows = rows.len() > 1;
9651 for row in rows.iter_rows() {
9652 let indent_size = snapshot.indent_size_for_line(row);
9653 if indent_size.len > 0 {
9654 let deletion_len = match indent_size.kind {
9655 IndentKind::Space => {
9656 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9657 if columns_to_prev_tab_stop == 0 {
9658 tab_size
9659 } else {
9660 columns_to_prev_tab_stop
9661 }
9662 }
9663 IndentKind::Tab => 1,
9664 };
9665 let start = if has_multiple_rows
9666 || deletion_len > selection.start.column
9667 || indent_size.len < selection.start.column
9668 {
9669 0
9670 } else {
9671 selection.start.column - deletion_len
9672 };
9673 deletion_ranges.push(
9674 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9675 );
9676 last_outdent = Some(row);
9677 }
9678 }
9679 }
9680 }
9681
9682 self.transact(window, cx, |this, window, cx| {
9683 this.buffer.update(cx, |buffer, cx| {
9684 let empty_str: Arc<str> = Arc::default();
9685 buffer.edit(
9686 deletion_ranges
9687 .into_iter()
9688 .map(|range| (range, empty_str.clone())),
9689 None,
9690 cx,
9691 );
9692 });
9693 let selections = this.selections.all::<usize>(cx);
9694 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9695 s.select(selections)
9696 });
9697 });
9698 }
9699
9700 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9701 if self.read_only(cx) {
9702 return;
9703 }
9704 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9705 let selections = self
9706 .selections
9707 .all::<usize>(cx)
9708 .into_iter()
9709 .map(|s| s.range());
9710
9711 self.transact(window, cx, |this, window, cx| {
9712 this.buffer.update(cx, |buffer, cx| {
9713 buffer.autoindent_ranges(selections, cx);
9714 });
9715 let selections = this.selections.all::<usize>(cx);
9716 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9717 s.select(selections)
9718 });
9719 });
9720 }
9721
9722 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9723 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9725 let selections = self.selections.all::<Point>(cx);
9726
9727 let mut new_cursors = Vec::new();
9728 let mut edit_ranges = Vec::new();
9729 let mut selections = selections.iter().peekable();
9730 while let Some(selection) = selections.next() {
9731 let mut rows = selection.spanned_rows(false, &display_map);
9732 let goal_display_column = selection.head().to_display_point(&display_map).column();
9733
9734 // Accumulate contiguous regions of rows that we want to delete.
9735 while let Some(next_selection) = selections.peek() {
9736 let next_rows = next_selection.spanned_rows(false, &display_map);
9737 if next_rows.start <= rows.end {
9738 rows.end = next_rows.end;
9739 selections.next().unwrap();
9740 } else {
9741 break;
9742 }
9743 }
9744
9745 let buffer = &display_map.buffer_snapshot;
9746 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9747 let edit_end;
9748 let cursor_buffer_row;
9749 if buffer.max_point().row >= rows.end.0 {
9750 // If there's a line after the range, delete the \n from the end of the row range
9751 // and position the cursor on the next line.
9752 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9753 cursor_buffer_row = rows.end;
9754 } else {
9755 // If there isn't a line after the range, delete the \n from the line before the
9756 // start of the row range and position the cursor there.
9757 edit_start = edit_start.saturating_sub(1);
9758 edit_end = buffer.len();
9759 cursor_buffer_row = rows.start.previous_row();
9760 }
9761
9762 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9763 *cursor.column_mut() =
9764 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9765
9766 new_cursors.push((
9767 selection.id,
9768 buffer.anchor_after(cursor.to_point(&display_map)),
9769 ));
9770 edit_ranges.push(edit_start..edit_end);
9771 }
9772
9773 self.transact(window, cx, |this, window, cx| {
9774 let buffer = this.buffer.update(cx, |buffer, cx| {
9775 let empty_str: Arc<str> = Arc::default();
9776 buffer.edit(
9777 edit_ranges
9778 .into_iter()
9779 .map(|range| (range, empty_str.clone())),
9780 None,
9781 cx,
9782 );
9783 buffer.snapshot(cx)
9784 });
9785 let new_selections = new_cursors
9786 .into_iter()
9787 .map(|(id, cursor)| {
9788 let cursor = cursor.to_point(&buffer);
9789 Selection {
9790 id,
9791 start: cursor,
9792 end: cursor,
9793 reversed: false,
9794 goal: SelectionGoal::None,
9795 }
9796 })
9797 .collect();
9798
9799 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9800 s.select(new_selections);
9801 });
9802 });
9803 }
9804
9805 pub fn join_lines_impl(
9806 &mut self,
9807 insert_whitespace: bool,
9808 window: &mut Window,
9809 cx: &mut Context<Self>,
9810 ) {
9811 if self.read_only(cx) {
9812 return;
9813 }
9814 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9815 for selection in self.selections.all::<Point>(cx) {
9816 let start = MultiBufferRow(selection.start.row);
9817 // Treat single line selections as if they include the next line. Otherwise this action
9818 // would do nothing for single line selections individual cursors.
9819 let end = if selection.start.row == selection.end.row {
9820 MultiBufferRow(selection.start.row + 1)
9821 } else {
9822 MultiBufferRow(selection.end.row)
9823 };
9824
9825 if let Some(last_row_range) = row_ranges.last_mut() {
9826 if start <= last_row_range.end {
9827 last_row_range.end = end;
9828 continue;
9829 }
9830 }
9831 row_ranges.push(start..end);
9832 }
9833
9834 let snapshot = self.buffer.read(cx).snapshot(cx);
9835 let mut cursor_positions = Vec::new();
9836 for row_range in &row_ranges {
9837 let anchor = snapshot.anchor_before(Point::new(
9838 row_range.end.previous_row().0,
9839 snapshot.line_len(row_range.end.previous_row()),
9840 ));
9841 cursor_positions.push(anchor..anchor);
9842 }
9843
9844 self.transact(window, cx, |this, window, cx| {
9845 for row_range in row_ranges.into_iter().rev() {
9846 for row in row_range.iter_rows().rev() {
9847 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9848 let next_line_row = row.next_row();
9849 let indent = snapshot.indent_size_for_line(next_line_row);
9850 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9851
9852 let replace =
9853 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9854 " "
9855 } else {
9856 ""
9857 };
9858
9859 this.buffer.update(cx, |buffer, cx| {
9860 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9861 });
9862 }
9863 }
9864
9865 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9866 s.select_anchor_ranges(cursor_positions)
9867 });
9868 });
9869 }
9870
9871 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9872 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9873 self.join_lines_impl(true, window, cx);
9874 }
9875
9876 pub fn sort_lines_case_sensitive(
9877 &mut self,
9878 _: &SortLinesCaseSensitive,
9879 window: &mut Window,
9880 cx: &mut Context<Self>,
9881 ) {
9882 self.manipulate_lines(window, cx, |lines| lines.sort())
9883 }
9884
9885 pub fn sort_lines_case_insensitive(
9886 &mut self,
9887 _: &SortLinesCaseInsensitive,
9888 window: &mut Window,
9889 cx: &mut Context<Self>,
9890 ) {
9891 self.manipulate_lines(window, cx, |lines| {
9892 lines.sort_by_key(|line| line.to_lowercase())
9893 })
9894 }
9895
9896 pub fn unique_lines_case_insensitive(
9897 &mut self,
9898 _: &UniqueLinesCaseInsensitive,
9899 window: &mut Window,
9900 cx: &mut Context<Self>,
9901 ) {
9902 self.manipulate_lines(window, cx, |lines| {
9903 let mut seen = HashSet::default();
9904 lines.retain(|line| seen.insert(line.to_lowercase()));
9905 })
9906 }
9907
9908 pub fn unique_lines_case_sensitive(
9909 &mut self,
9910 _: &UniqueLinesCaseSensitive,
9911 window: &mut Window,
9912 cx: &mut Context<Self>,
9913 ) {
9914 self.manipulate_lines(window, cx, |lines| {
9915 let mut seen = HashSet::default();
9916 lines.retain(|line| seen.insert(*line));
9917 })
9918 }
9919
9920 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9921 let Some(project) = self.project.clone() else {
9922 return;
9923 };
9924 self.reload(project, window, cx)
9925 .detach_and_notify_err(window, cx);
9926 }
9927
9928 pub fn restore_file(
9929 &mut self,
9930 _: &::git::RestoreFile,
9931 window: &mut Window,
9932 cx: &mut Context<Self>,
9933 ) {
9934 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9935 let mut buffer_ids = HashSet::default();
9936 let snapshot = self.buffer().read(cx).snapshot(cx);
9937 for selection in self.selections.all::<usize>(cx) {
9938 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9939 }
9940
9941 let buffer = self.buffer().read(cx);
9942 let ranges = buffer_ids
9943 .into_iter()
9944 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9945 .collect::<Vec<_>>();
9946
9947 self.restore_hunks_in_ranges(ranges, window, cx);
9948 }
9949
9950 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9951 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9952 let selections = self
9953 .selections
9954 .all(cx)
9955 .into_iter()
9956 .map(|s| s.range())
9957 .collect();
9958 self.restore_hunks_in_ranges(selections, window, cx);
9959 }
9960
9961 pub fn restore_hunks_in_ranges(
9962 &mut self,
9963 ranges: Vec<Range<Point>>,
9964 window: &mut Window,
9965 cx: &mut Context<Editor>,
9966 ) {
9967 let mut revert_changes = HashMap::default();
9968 let chunk_by = self
9969 .snapshot(window, cx)
9970 .hunks_for_ranges(ranges)
9971 .into_iter()
9972 .chunk_by(|hunk| hunk.buffer_id);
9973 for (buffer_id, hunks) in &chunk_by {
9974 let hunks = hunks.collect::<Vec<_>>();
9975 for hunk in &hunks {
9976 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9977 }
9978 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9979 }
9980 drop(chunk_by);
9981 if !revert_changes.is_empty() {
9982 self.transact(window, cx, |editor, window, cx| {
9983 editor.restore(revert_changes, window, cx);
9984 });
9985 }
9986 }
9987
9988 pub fn open_active_item_in_terminal(
9989 &mut self,
9990 _: &OpenInTerminal,
9991 window: &mut Window,
9992 cx: &mut Context<Self>,
9993 ) {
9994 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9995 let project_path = buffer.read(cx).project_path(cx)?;
9996 let project = self.project.as_ref()?.read(cx);
9997 let entry = project.entry_for_path(&project_path, cx)?;
9998 let parent = match &entry.canonical_path {
9999 Some(canonical_path) => canonical_path.to_path_buf(),
10000 None => project.absolute_path(&project_path, cx)?,
10001 }
10002 .parent()?
10003 .to_path_buf();
10004 Some(parent)
10005 }) {
10006 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10007 }
10008 }
10009
10010 fn set_breakpoint_context_menu(
10011 &mut self,
10012 display_row: DisplayRow,
10013 position: Option<Anchor>,
10014 clicked_point: gpui::Point<Pixels>,
10015 window: &mut Window,
10016 cx: &mut Context<Self>,
10017 ) {
10018 if !cx.has_flag::<DebuggerFeatureFlag>() {
10019 return;
10020 }
10021 let source = self
10022 .buffer
10023 .read(cx)
10024 .snapshot(cx)
10025 .anchor_before(Point::new(display_row.0, 0u32));
10026
10027 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10028
10029 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10030 self,
10031 source,
10032 clicked_point,
10033 context_menu,
10034 window,
10035 cx,
10036 );
10037 }
10038
10039 fn add_edit_breakpoint_block(
10040 &mut self,
10041 anchor: Anchor,
10042 breakpoint: &Breakpoint,
10043 edit_action: BreakpointPromptEditAction,
10044 window: &mut Window,
10045 cx: &mut Context<Self>,
10046 ) {
10047 let weak_editor = cx.weak_entity();
10048 let bp_prompt = cx.new(|cx| {
10049 BreakpointPromptEditor::new(
10050 weak_editor,
10051 anchor,
10052 breakpoint.clone(),
10053 edit_action,
10054 window,
10055 cx,
10056 )
10057 });
10058
10059 let height = bp_prompt.update(cx, |this, cx| {
10060 this.prompt
10061 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10062 });
10063 let cloned_prompt = bp_prompt.clone();
10064 let blocks = vec![BlockProperties {
10065 style: BlockStyle::Sticky,
10066 placement: BlockPlacement::Above(anchor),
10067 height: Some(height),
10068 render: Arc::new(move |cx| {
10069 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10070 cloned_prompt.clone().into_any_element()
10071 }),
10072 priority: 0,
10073 render_in_minimap: true,
10074 }];
10075
10076 let focus_handle = bp_prompt.focus_handle(cx);
10077 window.focus(&focus_handle);
10078
10079 let block_ids = self.insert_blocks(blocks, None, cx);
10080 bp_prompt.update(cx, |prompt, _| {
10081 prompt.add_block_ids(block_ids);
10082 });
10083 }
10084
10085 pub(crate) fn breakpoint_at_row(
10086 &self,
10087 row: u32,
10088 window: &mut Window,
10089 cx: &mut Context<Self>,
10090 ) -> Option<(Anchor, Breakpoint)> {
10091 let snapshot = self.snapshot(window, cx);
10092 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10093
10094 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10095 }
10096
10097 pub(crate) fn breakpoint_at_anchor(
10098 &self,
10099 breakpoint_position: Anchor,
10100 snapshot: &EditorSnapshot,
10101 cx: &mut Context<Self>,
10102 ) -> Option<(Anchor, Breakpoint)> {
10103 let project = self.project.clone()?;
10104
10105 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10106 snapshot
10107 .buffer_snapshot
10108 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10109 })?;
10110
10111 let enclosing_excerpt = breakpoint_position.excerpt_id;
10112 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10113 let buffer_snapshot = buffer.read(cx).snapshot();
10114
10115 let row = buffer_snapshot
10116 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10117 .row;
10118
10119 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10120 let anchor_end = snapshot
10121 .buffer_snapshot
10122 .anchor_after(Point::new(row, line_len));
10123
10124 let bp = self
10125 .breakpoint_store
10126 .as_ref()?
10127 .read_with(cx, |breakpoint_store, cx| {
10128 breakpoint_store
10129 .breakpoints(
10130 &buffer,
10131 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10132 &buffer_snapshot,
10133 cx,
10134 )
10135 .next()
10136 .and_then(|(bp, _)| {
10137 let breakpoint_row = buffer_snapshot
10138 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10139 .row;
10140
10141 if breakpoint_row == row {
10142 snapshot
10143 .buffer_snapshot
10144 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10145 .map(|position| (position, bp.bp.clone()))
10146 } else {
10147 None
10148 }
10149 })
10150 });
10151 bp
10152 }
10153
10154 pub fn edit_log_breakpoint(
10155 &mut self,
10156 _: &EditLogBreakpoint,
10157 window: &mut Window,
10158 cx: &mut Context<Self>,
10159 ) {
10160 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10161 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10162 message: None,
10163 state: BreakpointState::Enabled,
10164 condition: None,
10165 hit_condition: None,
10166 });
10167
10168 self.add_edit_breakpoint_block(
10169 anchor,
10170 &breakpoint,
10171 BreakpointPromptEditAction::Log,
10172 window,
10173 cx,
10174 );
10175 }
10176 }
10177
10178 fn breakpoints_at_cursors(
10179 &self,
10180 window: &mut Window,
10181 cx: &mut Context<Self>,
10182 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10183 let snapshot = self.snapshot(window, cx);
10184 let cursors = self
10185 .selections
10186 .disjoint_anchors()
10187 .into_iter()
10188 .map(|selection| {
10189 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10190
10191 let breakpoint_position = self
10192 .breakpoint_at_row(cursor_position.row, window, cx)
10193 .map(|bp| bp.0)
10194 .unwrap_or_else(|| {
10195 snapshot
10196 .display_snapshot
10197 .buffer_snapshot
10198 .anchor_after(Point::new(cursor_position.row, 0))
10199 });
10200
10201 let breakpoint = self
10202 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10203 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10204
10205 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10206 })
10207 // 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.
10208 .collect::<HashMap<Anchor, _>>();
10209
10210 cursors.into_iter().collect()
10211 }
10212
10213 pub fn enable_breakpoint(
10214 &mut self,
10215 _: &crate::actions::EnableBreakpoint,
10216 window: &mut Window,
10217 cx: &mut Context<Self>,
10218 ) {
10219 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10220 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10221 continue;
10222 };
10223 self.edit_breakpoint_at_anchor(
10224 anchor,
10225 breakpoint,
10226 BreakpointEditAction::InvertState,
10227 cx,
10228 );
10229 }
10230 }
10231
10232 pub fn disable_breakpoint(
10233 &mut self,
10234 _: &crate::actions::DisableBreakpoint,
10235 window: &mut Window,
10236 cx: &mut Context<Self>,
10237 ) {
10238 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10239 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10240 continue;
10241 };
10242 self.edit_breakpoint_at_anchor(
10243 anchor,
10244 breakpoint,
10245 BreakpointEditAction::InvertState,
10246 cx,
10247 );
10248 }
10249 }
10250
10251 pub fn toggle_breakpoint(
10252 &mut self,
10253 _: &crate::actions::ToggleBreakpoint,
10254 window: &mut Window,
10255 cx: &mut Context<Self>,
10256 ) {
10257 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10258 if let Some(breakpoint) = breakpoint {
10259 self.edit_breakpoint_at_anchor(
10260 anchor,
10261 breakpoint,
10262 BreakpointEditAction::Toggle,
10263 cx,
10264 );
10265 } else {
10266 self.edit_breakpoint_at_anchor(
10267 anchor,
10268 Breakpoint::new_standard(),
10269 BreakpointEditAction::Toggle,
10270 cx,
10271 );
10272 }
10273 }
10274 }
10275
10276 pub fn edit_breakpoint_at_anchor(
10277 &mut self,
10278 breakpoint_position: Anchor,
10279 breakpoint: Breakpoint,
10280 edit_action: BreakpointEditAction,
10281 cx: &mut Context<Self>,
10282 ) {
10283 let Some(breakpoint_store) = &self.breakpoint_store else {
10284 return;
10285 };
10286
10287 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10288 if breakpoint_position == Anchor::min() {
10289 self.buffer()
10290 .read(cx)
10291 .excerpt_buffer_ids()
10292 .into_iter()
10293 .next()
10294 } else {
10295 None
10296 }
10297 }) else {
10298 return;
10299 };
10300
10301 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10302 return;
10303 };
10304
10305 breakpoint_store.update(cx, |breakpoint_store, cx| {
10306 breakpoint_store.toggle_breakpoint(
10307 buffer,
10308 BreakpointWithPosition {
10309 position: breakpoint_position.text_anchor,
10310 bp: breakpoint,
10311 },
10312 edit_action,
10313 cx,
10314 );
10315 });
10316
10317 cx.notify();
10318 }
10319
10320 #[cfg(any(test, feature = "test-support"))]
10321 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10322 self.breakpoint_store.clone()
10323 }
10324
10325 pub fn prepare_restore_change(
10326 &self,
10327 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10328 hunk: &MultiBufferDiffHunk,
10329 cx: &mut App,
10330 ) -> Option<()> {
10331 if hunk.is_created_file() {
10332 return None;
10333 }
10334 let buffer = self.buffer.read(cx);
10335 let diff = buffer.diff_for(hunk.buffer_id)?;
10336 let buffer = buffer.buffer(hunk.buffer_id)?;
10337 let buffer = buffer.read(cx);
10338 let original_text = diff
10339 .read(cx)
10340 .base_text()
10341 .as_rope()
10342 .slice(hunk.diff_base_byte_range.clone());
10343 let buffer_snapshot = buffer.snapshot();
10344 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10345 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10346 probe
10347 .0
10348 .start
10349 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10350 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10351 }) {
10352 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10353 Some(())
10354 } else {
10355 None
10356 }
10357 }
10358
10359 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10360 self.manipulate_lines(window, cx, |lines| lines.reverse())
10361 }
10362
10363 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10364 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10365 }
10366
10367 fn manipulate_lines<Fn>(
10368 &mut self,
10369 window: &mut Window,
10370 cx: &mut Context<Self>,
10371 mut callback: Fn,
10372 ) where
10373 Fn: FnMut(&mut Vec<&str>),
10374 {
10375 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10376
10377 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10378 let buffer = self.buffer.read(cx).snapshot(cx);
10379
10380 let mut edits = Vec::new();
10381
10382 let selections = self.selections.all::<Point>(cx);
10383 let mut selections = selections.iter().peekable();
10384 let mut contiguous_row_selections = Vec::new();
10385 let mut new_selections = Vec::new();
10386 let mut added_lines = 0;
10387 let mut removed_lines = 0;
10388
10389 while let Some(selection) = selections.next() {
10390 let (start_row, end_row) = consume_contiguous_rows(
10391 &mut contiguous_row_selections,
10392 selection,
10393 &display_map,
10394 &mut selections,
10395 );
10396
10397 let start_point = Point::new(start_row.0, 0);
10398 let end_point = Point::new(
10399 end_row.previous_row().0,
10400 buffer.line_len(end_row.previous_row()),
10401 );
10402 let text = buffer
10403 .text_for_range(start_point..end_point)
10404 .collect::<String>();
10405
10406 let mut lines = text.split('\n').collect_vec();
10407
10408 let lines_before = lines.len();
10409 callback(&mut lines);
10410 let lines_after = lines.len();
10411
10412 edits.push((start_point..end_point, lines.join("\n")));
10413
10414 // Selections must change based on added and removed line count
10415 let start_row =
10416 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10417 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10418 new_selections.push(Selection {
10419 id: selection.id,
10420 start: start_row,
10421 end: end_row,
10422 goal: SelectionGoal::None,
10423 reversed: selection.reversed,
10424 });
10425
10426 if lines_after > lines_before {
10427 added_lines += lines_after - lines_before;
10428 } else if lines_before > lines_after {
10429 removed_lines += lines_before - lines_after;
10430 }
10431 }
10432
10433 self.transact(window, cx, |this, window, cx| {
10434 let buffer = this.buffer.update(cx, |buffer, cx| {
10435 buffer.edit(edits, None, cx);
10436 buffer.snapshot(cx)
10437 });
10438
10439 // Recalculate offsets on newly edited buffer
10440 let new_selections = new_selections
10441 .iter()
10442 .map(|s| {
10443 let start_point = Point::new(s.start.0, 0);
10444 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10445 Selection {
10446 id: s.id,
10447 start: buffer.point_to_offset(start_point),
10448 end: buffer.point_to_offset(end_point),
10449 goal: s.goal,
10450 reversed: s.reversed,
10451 }
10452 })
10453 .collect();
10454
10455 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10456 s.select(new_selections);
10457 });
10458
10459 this.request_autoscroll(Autoscroll::fit(), cx);
10460 });
10461 }
10462
10463 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10464 self.manipulate_text(window, cx, |text| {
10465 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10466 if has_upper_case_characters {
10467 text.to_lowercase()
10468 } else {
10469 text.to_uppercase()
10470 }
10471 })
10472 }
10473
10474 pub fn convert_to_upper_case(
10475 &mut self,
10476 _: &ConvertToUpperCase,
10477 window: &mut Window,
10478 cx: &mut Context<Self>,
10479 ) {
10480 self.manipulate_text(window, cx, |text| text.to_uppercase())
10481 }
10482
10483 pub fn convert_to_lower_case(
10484 &mut self,
10485 _: &ConvertToLowerCase,
10486 window: &mut Window,
10487 cx: &mut Context<Self>,
10488 ) {
10489 self.manipulate_text(window, cx, |text| text.to_lowercase())
10490 }
10491
10492 pub fn convert_to_title_case(
10493 &mut self,
10494 _: &ConvertToTitleCase,
10495 window: &mut Window,
10496 cx: &mut Context<Self>,
10497 ) {
10498 self.manipulate_text(window, cx, |text| {
10499 text.split('\n')
10500 .map(|line| line.to_case(Case::Title))
10501 .join("\n")
10502 })
10503 }
10504
10505 pub fn convert_to_snake_case(
10506 &mut self,
10507 _: &ConvertToSnakeCase,
10508 window: &mut Window,
10509 cx: &mut Context<Self>,
10510 ) {
10511 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10512 }
10513
10514 pub fn convert_to_kebab_case(
10515 &mut self,
10516 _: &ConvertToKebabCase,
10517 window: &mut Window,
10518 cx: &mut Context<Self>,
10519 ) {
10520 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10521 }
10522
10523 pub fn convert_to_upper_camel_case(
10524 &mut self,
10525 _: &ConvertToUpperCamelCase,
10526 window: &mut Window,
10527 cx: &mut Context<Self>,
10528 ) {
10529 self.manipulate_text(window, cx, |text| {
10530 text.split('\n')
10531 .map(|line| line.to_case(Case::UpperCamel))
10532 .join("\n")
10533 })
10534 }
10535
10536 pub fn convert_to_lower_camel_case(
10537 &mut self,
10538 _: &ConvertToLowerCamelCase,
10539 window: &mut Window,
10540 cx: &mut Context<Self>,
10541 ) {
10542 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10543 }
10544
10545 pub fn convert_to_opposite_case(
10546 &mut self,
10547 _: &ConvertToOppositeCase,
10548 window: &mut Window,
10549 cx: &mut Context<Self>,
10550 ) {
10551 self.manipulate_text(window, cx, |text| {
10552 text.chars()
10553 .fold(String::with_capacity(text.len()), |mut t, c| {
10554 if c.is_uppercase() {
10555 t.extend(c.to_lowercase());
10556 } else {
10557 t.extend(c.to_uppercase());
10558 }
10559 t
10560 })
10561 })
10562 }
10563
10564 pub fn convert_to_rot13(
10565 &mut self,
10566 _: &ConvertToRot13,
10567 window: &mut Window,
10568 cx: &mut Context<Self>,
10569 ) {
10570 self.manipulate_text(window, cx, |text| {
10571 text.chars()
10572 .map(|c| match c {
10573 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10574 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10575 _ => c,
10576 })
10577 .collect()
10578 })
10579 }
10580
10581 pub fn convert_to_rot47(
10582 &mut self,
10583 _: &ConvertToRot47,
10584 window: &mut Window,
10585 cx: &mut Context<Self>,
10586 ) {
10587 self.manipulate_text(window, cx, |text| {
10588 text.chars()
10589 .map(|c| {
10590 let code_point = c as u32;
10591 if code_point >= 33 && code_point <= 126 {
10592 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10593 }
10594 c
10595 })
10596 .collect()
10597 })
10598 }
10599
10600 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10601 where
10602 Fn: FnMut(&str) -> String,
10603 {
10604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10605 let buffer = self.buffer.read(cx).snapshot(cx);
10606
10607 let mut new_selections = Vec::new();
10608 let mut edits = Vec::new();
10609 let mut selection_adjustment = 0i32;
10610
10611 for selection in self.selections.all::<usize>(cx) {
10612 let selection_is_empty = selection.is_empty();
10613
10614 let (start, end) = if selection_is_empty {
10615 let word_range = movement::surrounding_word(
10616 &display_map,
10617 selection.start.to_display_point(&display_map),
10618 );
10619 let start = word_range.start.to_offset(&display_map, Bias::Left);
10620 let end = word_range.end.to_offset(&display_map, Bias::Left);
10621 (start, end)
10622 } else {
10623 (selection.start, selection.end)
10624 };
10625
10626 let text = buffer.text_for_range(start..end).collect::<String>();
10627 let old_length = text.len() as i32;
10628 let text = callback(&text);
10629
10630 new_selections.push(Selection {
10631 start: (start as i32 - selection_adjustment) as usize,
10632 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10633 goal: SelectionGoal::None,
10634 ..selection
10635 });
10636
10637 selection_adjustment += old_length - text.len() as i32;
10638
10639 edits.push((start..end, text));
10640 }
10641
10642 self.transact(window, cx, |this, window, cx| {
10643 this.buffer.update(cx, |buffer, cx| {
10644 buffer.edit(edits, None, cx);
10645 });
10646
10647 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10648 s.select(new_selections);
10649 });
10650
10651 this.request_autoscroll(Autoscroll::fit(), cx);
10652 });
10653 }
10654
10655 pub fn move_selection_on_drop(
10656 &mut self,
10657 selection: &Selection<Anchor>,
10658 target: DisplayPoint,
10659 is_cut: bool,
10660 window: &mut Window,
10661 cx: &mut Context<Self>,
10662 ) {
10663 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10664 let buffer = &display_map.buffer_snapshot;
10665 let mut edits = Vec::new();
10666 let insert_point = display_map
10667 .clip_point(target, Bias::Left)
10668 .to_point(&display_map);
10669 let text = buffer
10670 .text_for_range(selection.start..selection.end)
10671 .collect::<String>();
10672 if is_cut {
10673 edits.push(((selection.start..selection.end), String::new()));
10674 }
10675 let insert_anchor = buffer.anchor_before(insert_point);
10676 edits.push(((insert_anchor..insert_anchor), text));
10677 let last_edit_start = insert_anchor.bias_left(buffer);
10678 let last_edit_end = insert_anchor.bias_right(buffer);
10679 self.transact(window, cx, |this, window, cx| {
10680 this.buffer.update(cx, |buffer, cx| {
10681 buffer.edit(edits, None, cx);
10682 });
10683 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10684 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10685 });
10686 });
10687 }
10688
10689 pub fn clear_selection_drag_state(&mut self) {
10690 self.selection_drag_state = SelectionDragState::None;
10691 }
10692
10693 pub fn duplicate(
10694 &mut self,
10695 upwards: bool,
10696 whole_lines: bool,
10697 window: &mut Window,
10698 cx: &mut Context<Self>,
10699 ) {
10700 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10701
10702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10703 let buffer = &display_map.buffer_snapshot;
10704 let selections = self.selections.all::<Point>(cx);
10705
10706 let mut edits = Vec::new();
10707 let mut selections_iter = selections.iter().peekable();
10708 while let Some(selection) = selections_iter.next() {
10709 let mut rows = selection.spanned_rows(false, &display_map);
10710 // duplicate line-wise
10711 if whole_lines || selection.start == selection.end {
10712 // Avoid duplicating the same lines twice.
10713 while let Some(next_selection) = selections_iter.peek() {
10714 let next_rows = next_selection.spanned_rows(false, &display_map);
10715 if next_rows.start < rows.end {
10716 rows.end = next_rows.end;
10717 selections_iter.next().unwrap();
10718 } else {
10719 break;
10720 }
10721 }
10722
10723 // Copy the text from the selected row region and splice it either at the start
10724 // or end of the region.
10725 let start = Point::new(rows.start.0, 0);
10726 let end = Point::new(
10727 rows.end.previous_row().0,
10728 buffer.line_len(rows.end.previous_row()),
10729 );
10730 let text = buffer
10731 .text_for_range(start..end)
10732 .chain(Some("\n"))
10733 .collect::<String>();
10734 let insert_location = if upwards {
10735 Point::new(rows.end.0, 0)
10736 } else {
10737 start
10738 };
10739 edits.push((insert_location..insert_location, text));
10740 } else {
10741 // duplicate character-wise
10742 let start = selection.start;
10743 let end = selection.end;
10744 let text = buffer.text_for_range(start..end).collect::<String>();
10745 edits.push((selection.end..selection.end, text));
10746 }
10747 }
10748
10749 self.transact(window, cx, |this, _, cx| {
10750 this.buffer.update(cx, |buffer, cx| {
10751 buffer.edit(edits, None, cx);
10752 });
10753
10754 this.request_autoscroll(Autoscroll::fit(), cx);
10755 });
10756 }
10757
10758 pub fn duplicate_line_up(
10759 &mut self,
10760 _: &DuplicateLineUp,
10761 window: &mut Window,
10762 cx: &mut Context<Self>,
10763 ) {
10764 self.duplicate(true, true, window, cx);
10765 }
10766
10767 pub fn duplicate_line_down(
10768 &mut self,
10769 _: &DuplicateLineDown,
10770 window: &mut Window,
10771 cx: &mut Context<Self>,
10772 ) {
10773 self.duplicate(false, true, window, cx);
10774 }
10775
10776 pub fn duplicate_selection(
10777 &mut self,
10778 _: &DuplicateSelection,
10779 window: &mut Window,
10780 cx: &mut Context<Self>,
10781 ) {
10782 self.duplicate(false, false, window, cx);
10783 }
10784
10785 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10786 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10787
10788 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10789 let buffer = self.buffer.read(cx).snapshot(cx);
10790
10791 let mut edits = Vec::new();
10792 let mut unfold_ranges = Vec::new();
10793 let mut refold_creases = Vec::new();
10794
10795 let selections = self.selections.all::<Point>(cx);
10796 let mut selections = selections.iter().peekable();
10797 let mut contiguous_row_selections = Vec::new();
10798 let mut new_selections = Vec::new();
10799
10800 while let Some(selection) = selections.next() {
10801 // Find all the selections that span a contiguous row range
10802 let (start_row, end_row) = consume_contiguous_rows(
10803 &mut contiguous_row_selections,
10804 selection,
10805 &display_map,
10806 &mut selections,
10807 );
10808
10809 // Move the text spanned by the row range to be before the line preceding the row range
10810 if start_row.0 > 0 {
10811 let range_to_move = Point::new(
10812 start_row.previous_row().0,
10813 buffer.line_len(start_row.previous_row()),
10814 )
10815 ..Point::new(
10816 end_row.previous_row().0,
10817 buffer.line_len(end_row.previous_row()),
10818 );
10819 let insertion_point = display_map
10820 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10821 .0;
10822
10823 // Don't move lines across excerpts
10824 if buffer
10825 .excerpt_containing(insertion_point..range_to_move.end)
10826 .is_some()
10827 {
10828 let text = buffer
10829 .text_for_range(range_to_move.clone())
10830 .flat_map(|s| s.chars())
10831 .skip(1)
10832 .chain(['\n'])
10833 .collect::<String>();
10834
10835 edits.push((
10836 buffer.anchor_after(range_to_move.start)
10837 ..buffer.anchor_before(range_to_move.end),
10838 String::new(),
10839 ));
10840 let insertion_anchor = buffer.anchor_after(insertion_point);
10841 edits.push((insertion_anchor..insertion_anchor, text));
10842
10843 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10844
10845 // Move selections up
10846 new_selections.extend(contiguous_row_selections.drain(..).map(
10847 |mut selection| {
10848 selection.start.row -= row_delta;
10849 selection.end.row -= row_delta;
10850 selection
10851 },
10852 ));
10853
10854 // Move folds up
10855 unfold_ranges.push(range_to_move.clone());
10856 for fold in display_map.folds_in_range(
10857 buffer.anchor_before(range_to_move.start)
10858 ..buffer.anchor_after(range_to_move.end),
10859 ) {
10860 let mut start = fold.range.start.to_point(&buffer);
10861 let mut end = fold.range.end.to_point(&buffer);
10862 start.row -= row_delta;
10863 end.row -= row_delta;
10864 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10865 }
10866 }
10867 }
10868
10869 // If we didn't move line(s), preserve the existing selections
10870 new_selections.append(&mut contiguous_row_selections);
10871 }
10872
10873 self.transact(window, cx, |this, window, cx| {
10874 this.unfold_ranges(&unfold_ranges, true, true, cx);
10875 this.buffer.update(cx, |buffer, cx| {
10876 for (range, text) in edits {
10877 buffer.edit([(range, text)], None, cx);
10878 }
10879 });
10880 this.fold_creases(refold_creases, true, window, cx);
10881 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10882 s.select(new_selections);
10883 })
10884 });
10885 }
10886
10887 pub fn move_line_down(
10888 &mut self,
10889 _: &MoveLineDown,
10890 window: &mut Window,
10891 cx: &mut Context<Self>,
10892 ) {
10893 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10894
10895 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10896 let buffer = self.buffer.read(cx).snapshot(cx);
10897
10898 let mut edits = Vec::new();
10899 let mut unfold_ranges = Vec::new();
10900 let mut refold_creases = Vec::new();
10901
10902 let selections = self.selections.all::<Point>(cx);
10903 let mut selections = selections.iter().peekable();
10904 let mut contiguous_row_selections = Vec::new();
10905 let mut new_selections = Vec::new();
10906
10907 while let Some(selection) = selections.next() {
10908 // Find all the selections that span a contiguous row range
10909 let (start_row, end_row) = consume_contiguous_rows(
10910 &mut contiguous_row_selections,
10911 selection,
10912 &display_map,
10913 &mut selections,
10914 );
10915
10916 // Move the text spanned by the row range to be after the last line of the row range
10917 if end_row.0 <= buffer.max_point().row {
10918 let range_to_move =
10919 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10920 let insertion_point = display_map
10921 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10922 .0;
10923
10924 // Don't move lines across excerpt boundaries
10925 if buffer
10926 .excerpt_containing(range_to_move.start..insertion_point)
10927 .is_some()
10928 {
10929 let mut text = String::from("\n");
10930 text.extend(buffer.text_for_range(range_to_move.clone()));
10931 text.pop(); // Drop trailing newline
10932 edits.push((
10933 buffer.anchor_after(range_to_move.start)
10934 ..buffer.anchor_before(range_to_move.end),
10935 String::new(),
10936 ));
10937 let insertion_anchor = buffer.anchor_after(insertion_point);
10938 edits.push((insertion_anchor..insertion_anchor, text));
10939
10940 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10941
10942 // Move selections down
10943 new_selections.extend(contiguous_row_selections.drain(..).map(
10944 |mut selection| {
10945 selection.start.row += row_delta;
10946 selection.end.row += row_delta;
10947 selection
10948 },
10949 ));
10950
10951 // Move folds down
10952 unfold_ranges.push(range_to_move.clone());
10953 for fold in display_map.folds_in_range(
10954 buffer.anchor_before(range_to_move.start)
10955 ..buffer.anchor_after(range_to_move.end),
10956 ) {
10957 let mut start = fold.range.start.to_point(&buffer);
10958 let mut end = fold.range.end.to_point(&buffer);
10959 start.row += row_delta;
10960 end.row += row_delta;
10961 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10962 }
10963 }
10964 }
10965
10966 // If we didn't move line(s), preserve the existing selections
10967 new_selections.append(&mut contiguous_row_selections);
10968 }
10969
10970 self.transact(window, cx, |this, window, cx| {
10971 this.unfold_ranges(&unfold_ranges, true, true, cx);
10972 this.buffer.update(cx, |buffer, cx| {
10973 for (range, text) in edits {
10974 buffer.edit([(range, text)], None, cx);
10975 }
10976 });
10977 this.fold_creases(refold_creases, true, window, cx);
10978 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10979 s.select(new_selections)
10980 });
10981 });
10982 }
10983
10984 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10985 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10986 let text_layout_details = &self.text_layout_details(window);
10987 self.transact(window, cx, |this, window, cx| {
10988 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10989 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10990 s.move_with(|display_map, selection| {
10991 if !selection.is_empty() {
10992 return;
10993 }
10994
10995 let mut head = selection.head();
10996 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10997 if head.column() == display_map.line_len(head.row()) {
10998 transpose_offset = display_map
10999 .buffer_snapshot
11000 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11001 }
11002
11003 if transpose_offset == 0 {
11004 return;
11005 }
11006
11007 *head.column_mut() += 1;
11008 head = display_map.clip_point(head, Bias::Right);
11009 let goal = SelectionGoal::HorizontalPosition(
11010 display_map
11011 .x_for_display_point(head, text_layout_details)
11012 .into(),
11013 );
11014 selection.collapse_to(head, goal);
11015
11016 let transpose_start = display_map
11017 .buffer_snapshot
11018 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11019 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11020 let transpose_end = display_map
11021 .buffer_snapshot
11022 .clip_offset(transpose_offset + 1, Bias::Right);
11023 if let Some(ch) =
11024 display_map.buffer_snapshot.chars_at(transpose_start).next()
11025 {
11026 edits.push((transpose_start..transpose_offset, String::new()));
11027 edits.push((transpose_end..transpose_end, ch.to_string()));
11028 }
11029 }
11030 });
11031 edits
11032 });
11033 this.buffer
11034 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11035 let selections = this.selections.all::<usize>(cx);
11036 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11037 s.select(selections);
11038 });
11039 });
11040 }
11041
11042 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11043 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11044 self.rewrap_impl(RewrapOptions::default(), cx)
11045 }
11046
11047 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11048 let buffer = self.buffer.read(cx).snapshot(cx);
11049 let selections = self.selections.all::<Point>(cx);
11050
11051 // Shrink and split selections to respect paragraph boundaries.
11052 let ranges = selections.into_iter().flat_map(|selection| {
11053 let language_settings = buffer.language_settings_at(selection.head(), cx);
11054 let language_scope = buffer.language_scope_at(selection.head());
11055
11056 let Some(start_row) = (selection.start.row..=selection.end.row)
11057 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11058 else {
11059 return vec![];
11060 };
11061 let Some(end_row) = (selection.start.row..=selection.end.row)
11062 .rev()
11063 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11064 else {
11065 return vec![];
11066 };
11067
11068 let mut row = start_row;
11069 let mut ranges = Vec::new();
11070 while let Some(blank_row) =
11071 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11072 {
11073 let next_paragraph_start = (blank_row + 1..=end_row)
11074 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11075 .unwrap();
11076 ranges.push((
11077 language_settings.clone(),
11078 language_scope.clone(),
11079 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11080 ));
11081 row = next_paragraph_start;
11082 }
11083 ranges.push((
11084 language_settings.clone(),
11085 language_scope.clone(),
11086 Point::new(row, 0)..Point::new(end_row, 0),
11087 ));
11088
11089 ranges
11090 });
11091
11092 let mut edits = Vec::new();
11093 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11094
11095 for (language_settings, language_scope, range) in ranges {
11096 let mut start_row = range.start.row;
11097 let mut end_row = range.end.row;
11098
11099 // Skip selections that overlap with a range that has already been rewrapped.
11100 let selection_range = start_row..end_row;
11101 if rewrapped_row_ranges
11102 .iter()
11103 .any(|range| range.overlaps(&selection_range))
11104 {
11105 continue;
11106 }
11107
11108 let tab_size = language_settings.tab_size;
11109
11110 // Since not all lines in the selection may be at the same indent
11111 // level, choose the indent size that is the most common between all
11112 // of the lines.
11113 //
11114 // If there is a tie, we use the deepest indent.
11115 let (indent_size, indent_end) = {
11116 let mut indent_size_occurrences = HashMap::default();
11117 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11118
11119 for row in start_row..=end_row {
11120 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11121 rows_by_indent_size.entry(indent).or_default().push(row);
11122 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11123 }
11124
11125 let indent_size = indent_size_occurrences
11126 .into_iter()
11127 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11128 .map(|(indent, _)| indent)
11129 .unwrap_or_default();
11130 let row = rows_by_indent_size[&indent_size][0];
11131 let indent_end = Point::new(row, indent_size.len);
11132
11133 (indent_size, indent_end)
11134 };
11135
11136 let mut line_prefix = indent_size.chars().collect::<String>();
11137
11138 let mut inside_comment = false;
11139 if let Some(comment_prefix) = language_scope.and_then(|language| {
11140 language
11141 .line_comment_prefixes()
11142 .iter()
11143 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11144 .cloned()
11145 }) {
11146 line_prefix.push_str(&comment_prefix);
11147 inside_comment = true;
11148 }
11149
11150 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11151 RewrapBehavior::InComments => inside_comment,
11152 RewrapBehavior::InSelections => !range.is_empty(),
11153 RewrapBehavior::Anywhere => true,
11154 };
11155
11156 let should_rewrap = options.override_language_settings
11157 || allow_rewrap_based_on_language
11158 || self.hard_wrap.is_some();
11159 if !should_rewrap {
11160 continue;
11161 }
11162
11163 if range.is_empty() {
11164 'expand_upwards: while start_row > 0 {
11165 let prev_row = start_row - 1;
11166 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11167 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11168 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11169 {
11170 start_row = prev_row;
11171 } else {
11172 break 'expand_upwards;
11173 }
11174 }
11175
11176 'expand_downwards: while end_row < buffer.max_point().row {
11177 let next_row = end_row + 1;
11178 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11179 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11180 && !buffer.is_line_blank(MultiBufferRow(next_row))
11181 {
11182 end_row = next_row;
11183 } else {
11184 break 'expand_downwards;
11185 }
11186 }
11187 }
11188
11189 let start = Point::new(start_row, 0);
11190 let start_offset = start.to_offset(&buffer);
11191 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11192 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11193 let Some(lines_without_prefixes) = selection_text
11194 .lines()
11195 .map(|line| {
11196 line.strip_prefix(&line_prefix)
11197 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11198 .with_context(|| {
11199 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11200 })
11201 })
11202 .collect::<Result<Vec<_>, _>>()
11203 .log_err()
11204 else {
11205 continue;
11206 };
11207
11208 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11209 buffer
11210 .language_settings_at(Point::new(start_row, 0), cx)
11211 .preferred_line_length as usize
11212 });
11213 let wrapped_text = wrap_with_prefix(
11214 line_prefix,
11215 lines_without_prefixes.join("\n"),
11216 wrap_column,
11217 tab_size,
11218 options.preserve_existing_whitespace,
11219 );
11220
11221 // TODO: should always use char-based diff while still supporting cursor behavior that
11222 // matches vim.
11223 let mut diff_options = DiffOptions::default();
11224 if options.override_language_settings {
11225 diff_options.max_word_diff_len = 0;
11226 diff_options.max_word_diff_line_count = 0;
11227 } else {
11228 diff_options.max_word_diff_len = usize::MAX;
11229 diff_options.max_word_diff_line_count = usize::MAX;
11230 }
11231
11232 for (old_range, new_text) in
11233 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11234 {
11235 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11236 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11237 edits.push((edit_start..edit_end, new_text));
11238 }
11239
11240 rewrapped_row_ranges.push(start_row..=end_row);
11241 }
11242
11243 self.buffer
11244 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11245 }
11246
11247 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11248 let mut text = String::new();
11249 let buffer = self.buffer.read(cx).snapshot(cx);
11250 let mut selections = self.selections.all::<Point>(cx);
11251 let mut clipboard_selections = Vec::with_capacity(selections.len());
11252 {
11253 let max_point = buffer.max_point();
11254 let mut is_first = true;
11255 for selection in &mut selections {
11256 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11257 if is_entire_line {
11258 selection.start = Point::new(selection.start.row, 0);
11259 if !selection.is_empty() && selection.end.column == 0 {
11260 selection.end = cmp::min(max_point, selection.end);
11261 } else {
11262 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11263 }
11264 selection.goal = SelectionGoal::None;
11265 }
11266 if is_first {
11267 is_first = false;
11268 } else {
11269 text += "\n";
11270 }
11271 let mut len = 0;
11272 for chunk in buffer.text_for_range(selection.start..selection.end) {
11273 text.push_str(chunk);
11274 len += chunk.len();
11275 }
11276 clipboard_selections.push(ClipboardSelection {
11277 len,
11278 is_entire_line,
11279 first_line_indent: buffer
11280 .indent_size_for_line(MultiBufferRow(selection.start.row))
11281 .len,
11282 });
11283 }
11284 }
11285
11286 self.transact(window, cx, |this, window, cx| {
11287 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11288 s.select(selections);
11289 });
11290 this.insert("", window, cx);
11291 });
11292 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11293 }
11294
11295 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11296 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11297 let item = self.cut_common(window, cx);
11298 cx.write_to_clipboard(item);
11299 }
11300
11301 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11302 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11303 self.change_selections(None, window, cx, |s| {
11304 s.move_with(|snapshot, sel| {
11305 if sel.is_empty() {
11306 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11307 }
11308 });
11309 });
11310 let item = self.cut_common(window, cx);
11311 cx.set_global(KillRing(item))
11312 }
11313
11314 pub fn kill_ring_yank(
11315 &mut self,
11316 _: &KillRingYank,
11317 window: &mut Window,
11318 cx: &mut Context<Self>,
11319 ) {
11320 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11321 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11322 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11323 (kill_ring.text().to_string(), kill_ring.metadata_json())
11324 } else {
11325 return;
11326 }
11327 } else {
11328 return;
11329 };
11330 self.do_paste(&text, metadata, false, window, cx);
11331 }
11332
11333 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11334 self.do_copy(true, cx);
11335 }
11336
11337 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11338 self.do_copy(false, cx);
11339 }
11340
11341 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11342 let selections = self.selections.all::<Point>(cx);
11343 let buffer = self.buffer.read(cx).read(cx);
11344 let mut text = String::new();
11345
11346 let mut clipboard_selections = Vec::with_capacity(selections.len());
11347 {
11348 let max_point = buffer.max_point();
11349 let mut is_first = true;
11350 for selection in &selections {
11351 let mut start = selection.start;
11352 let mut end = selection.end;
11353 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11354 if is_entire_line {
11355 start = Point::new(start.row, 0);
11356 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11357 }
11358
11359 let mut trimmed_selections = Vec::new();
11360 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11361 let row = MultiBufferRow(start.row);
11362 let first_indent = buffer.indent_size_for_line(row);
11363 if first_indent.len == 0 || start.column > first_indent.len {
11364 trimmed_selections.push(start..end);
11365 } else {
11366 trimmed_selections.push(
11367 Point::new(row.0, first_indent.len)
11368 ..Point::new(row.0, buffer.line_len(row)),
11369 );
11370 for row in start.row + 1..=end.row {
11371 let mut line_len = buffer.line_len(MultiBufferRow(row));
11372 if row == end.row {
11373 line_len = end.column;
11374 }
11375 if line_len == 0 {
11376 trimmed_selections
11377 .push(Point::new(row, 0)..Point::new(row, line_len));
11378 continue;
11379 }
11380 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11381 if row_indent_size.len >= first_indent.len {
11382 trimmed_selections.push(
11383 Point::new(row, first_indent.len)..Point::new(row, line_len),
11384 );
11385 } else {
11386 trimmed_selections.clear();
11387 trimmed_selections.push(start..end);
11388 break;
11389 }
11390 }
11391 }
11392 } else {
11393 trimmed_selections.push(start..end);
11394 }
11395
11396 for trimmed_range in trimmed_selections {
11397 if is_first {
11398 is_first = false;
11399 } else {
11400 text += "\n";
11401 }
11402 let mut len = 0;
11403 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11404 text.push_str(chunk);
11405 len += chunk.len();
11406 }
11407 clipboard_selections.push(ClipboardSelection {
11408 len,
11409 is_entire_line,
11410 first_line_indent: buffer
11411 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11412 .len,
11413 });
11414 }
11415 }
11416 }
11417
11418 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11419 text,
11420 clipboard_selections,
11421 ));
11422 }
11423
11424 pub fn do_paste(
11425 &mut self,
11426 text: &String,
11427 clipboard_selections: Option<Vec<ClipboardSelection>>,
11428 handle_entire_lines: bool,
11429 window: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 if self.read_only(cx) {
11433 return;
11434 }
11435
11436 let clipboard_text = Cow::Borrowed(text);
11437
11438 self.transact(window, cx, |this, window, cx| {
11439 if let Some(mut clipboard_selections) = clipboard_selections {
11440 let old_selections = this.selections.all::<usize>(cx);
11441 let all_selections_were_entire_line =
11442 clipboard_selections.iter().all(|s| s.is_entire_line);
11443 let first_selection_indent_column =
11444 clipboard_selections.first().map(|s| s.first_line_indent);
11445 if clipboard_selections.len() != old_selections.len() {
11446 clipboard_selections.drain(..);
11447 }
11448 let cursor_offset = this.selections.last::<usize>(cx).head();
11449 let mut auto_indent_on_paste = true;
11450
11451 this.buffer.update(cx, |buffer, cx| {
11452 let snapshot = buffer.read(cx);
11453 auto_indent_on_paste = snapshot
11454 .language_settings_at(cursor_offset, cx)
11455 .auto_indent_on_paste;
11456
11457 let mut start_offset = 0;
11458 let mut edits = Vec::new();
11459 let mut original_indent_columns = Vec::new();
11460 for (ix, selection) in old_selections.iter().enumerate() {
11461 let to_insert;
11462 let entire_line;
11463 let original_indent_column;
11464 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11465 let end_offset = start_offset + clipboard_selection.len;
11466 to_insert = &clipboard_text[start_offset..end_offset];
11467 entire_line = clipboard_selection.is_entire_line;
11468 start_offset = end_offset + 1;
11469 original_indent_column = Some(clipboard_selection.first_line_indent);
11470 } else {
11471 to_insert = clipboard_text.as_str();
11472 entire_line = all_selections_were_entire_line;
11473 original_indent_column = first_selection_indent_column
11474 }
11475
11476 // If the corresponding selection was empty when this slice of the
11477 // clipboard text was written, then the entire line containing the
11478 // selection was copied. If this selection is also currently empty,
11479 // then paste the line before the current line of the buffer.
11480 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11481 let column = selection.start.to_point(&snapshot).column as usize;
11482 let line_start = selection.start - column;
11483 line_start..line_start
11484 } else {
11485 selection.range()
11486 };
11487
11488 edits.push((range, to_insert));
11489 original_indent_columns.push(original_indent_column);
11490 }
11491 drop(snapshot);
11492
11493 buffer.edit(
11494 edits,
11495 if auto_indent_on_paste {
11496 Some(AutoindentMode::Block {
11497 original_indent_columns,
11498 })
11499 } else {
11500 None
11501 },
11502 cx,
11503 );
11504 });
11505
11506 let selections = this.selections.all::<usize>(cx);
11507 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11508 s.select(selections)
11509 });
11510 } else {
11511 this.insert(&clipboard_text, window, cx);
11512 }
11513 });
11514 }
11515
11516 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11517 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11518 if let Some(item) = cx.read_from_clipboard() {
11519 let entries = item.entries();
11520
11521 match entries.first() {
11522 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11523 // of all the pasted entries.
11524 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11525 .do_paste(
11526 clipboard_string.text(),
11527 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11528 true,
11529 window,
11530 cx,
11531 ),
11532 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11533 }
11534 }
11535 }
11536
11537 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11538 if self.read_only(cx) {
11539 return;
11540 }
11541
11542 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11543
11544 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11545 if let Some((selections, _)) =
11546 self.selection_history.transaction(transaction_id).cloned()
11547 {
11548 self.change_selections(None, window, cx, |s| {
11549 s.select_anchors(selections.to_vec());
11550 });
11551 } else {
11552 log::error!(
11553 "No entry in selection_history found for undo. \
11554 This may correspond to a bug where undo does not update the selection. \
11555 If this is occurring, please add details to \
11556 https://github.com/zed-industries/zed/issues/22692"
11557 );
11558 }
11559 self.request_autoscroll(Autoscroll::fit(), cx);
11560 self.unmark_text(window, cx);
11561 self.refresh_inline_completion(true, false, window, cx);
11562 cx.emit(EditorEvent::Edited { transaction_id });
11563 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11564 }
11565 }
11566
11567 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11568 if self.read_only(cx) {
11569 return;
11570 }
11571
11572 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11573
11574 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11575 if let Some((_, Some(selections))) =
11576 self.selection_history.transaction(transaction_id).cloned()
11577 {
11578 self.change_selections(None, window, cx, |s| {
11579 s.select_anchors(selections.to_vec());
11580 });
11581 } else {
11582 log::error!(
11583 "No entry in selection_history found for redo. \
11584 This may correspond to a bug where undo does not update the selection. \
11585 If this is occurring, please add details to \
11586 https://github.com/zed-industries/zed/issues/22692"
11587 );
11588 }
11589 self.request_autoscroll(Autoscroll::fit(), cx);
11590 self.unmark_text(window, cx);
11591 self.refresh_inline_completion(true, false, window, cx);
11592 cx.emit(EditorEvent::Edited { transaction_id });
11593 }
11594 }
11595
11596 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11597 self.buffer
11598 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11599 }
11600
11601 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11602 self.buffer
11603 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11604 }
11605
11606 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11607 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11608 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11609 s.move_with(|map, selection| {
11610 let cursor = if selection.is_empty() {
11611 movement::left(map, selection.start)
11612 } else {
11613 selection.start
11614 };
11615 selection.collapse_to(cursor, SelectionGoal::None);
11616 });
11617 })
11618 }
11619
11620 pub fn select_left(&mut self, _: &SelectLeft, 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_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11624 })
11625 }
11626
11627 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11628 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11629 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11630 s.move_with(|map, selection| {
11631 let cursor = if selection.is_empty() {
11632 movement::right(map, selection.end)
11633 } else {
11634 selection.end
11635 };
11636 selection.collapse_to(cursor, SelectionGoal::None)
11637 });
11638 })
11639 }
11640
11641 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11642 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11643 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11644 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11645 })
11646 }
11647
11648 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11649 if self.take_rename(true, window, cx).is_some() {
11650 return;
11651 }
11652
11653 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11654 cx.propagate();
11655 return;
11656 }
11657
11658 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11659
11660 let text_layout_details = &self.text_layout_details(window);
11661 let selection_count = self.selections.count();
11662 let first_selection = self.selections.first_anchor();
11663
11664 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11665 s.move_with(|map, selection| {
11666 if !selection.is_empty() {
11667 selection.goal = SelectionGoal::None;
11668 }
11669 let (cursor, goal) = movement::up(
11670 map,
11671 selection.start,
11672 selection.goal,
11673 false,
11674 text_layout_details,
11675 );
11676 selection.collapse_to(cursor, goal);
11677 });
11678 });
11679
11680 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11681 {
11682 cx.propagate();
11683 }
11684 }
11685
11686 pub fn move_up_by_lines(
11687 &mut self,
11688 action: &MoveUpByLines,
11689 window: &mut Window,
11690 cx: &mut Context<Self>,
11691 ) {
11692 if self.take_rename(true, window, cx).is_some() {
11693 return;
11694 }
11695
11696 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11697 cx.propagate();
11698 return;
11699 }
11700
11701 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11702
11703 let text_layout_details = &self.text_layout_details(window);
11704
11705 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11706 s.move_with(|map, selection| {
11707 if !selection.is_empty() {
11708 selection.goal = SelectionGoal::None;
11709 }
11710 let (cursor, goal) = movement::up_by_rows(
11711 map,
11712 selection.start,
11713 action.lines,
11714 selection.goal,
11715 false,
11716 text_layout_details,
11717 );
11718 selection.collapse_to(cursor, goal);
11719 });
11720 })
11721 }
11722
11723 pub fn move_down_by_lines(
11724 &mut self,
11725 action: &MoveDownByLines,
11726 window: &mut Window,
11727 cx: &mut Context<Self>,
11728 ) {
11729 if self.take_rename(true, window, cx).is_some() {
11730 return;
11731 }
11732
11733 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11734 cx.propagate();
11735 return;
11736 }
11737
11738 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11739
11740 let text_layout_details = &self.text_layout_details(window);
11741
11742 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11743 s.move_with(|map, selection| {
11744 if !selection.is_empty() {
11745 selection.goal = SelectionGoal::None;
11746 }
11747 let (cursor, goal) = movement::down_by_rows(
11748 map,
11749 selection.start,
11750 action.lines,
11751 selection.goal,
11752 false,
11753 text_layout_details,
11754 );
11755 selection.collapse_to(cursor, goal);
11756 });
11757 })
11758 }
11759
11760 pub fn select_down_by_lines(
11761 &mut self,
11762 action: &SelectDownByLines,
11763 window: &mut Window,
11764 cx: &mut Context<Self>,
11765 ) {
11766 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11767 let text_layout_details = &self.text_layout_details(window);
11768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11769 s.move_heads_with(|map, head, goal| {
11770 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11771 })
11772 })
11773 }
11774
11775 pub fn select_up_by_lines(
11776 &mut self,
11777 action: &SelectUpByLines,
11778 window: &mut Window,
11779 cx: &mut Context<Self>,
11780 ) {
11781 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11782 let text_layout_details = &self.text_layout_details(window);
11783 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11784 s.move_heads_with(|map, head, goal| {
11785 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11786 })
11787 })
11788 }
11789
11790 pub fn select_page_up(
11791 &mut self,
11792 _: &SelectPageUp,
11793 window: &mut Window,
11794 cx: &mut Context<Self>,
11795 ) {
11796 let Some(row_count) = self.visible_row_count() else {
11797 return;
11798 };
11799
11800 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11801
11802 let text_layout_details = &self.text_layout_details(window);
11803
11804 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11805 s.move_heads_with(|map, head, goal| {
11806 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11807 })
11808 })
11809 }
11810
11811 pub fn move_page_up(
11812 &mut self,
11813 action: &MovePageUp,
11814 window: &mut Window,
11815 cx: &mut Context<Self>,
11816 ) {
11817 if self.take_rename(true, window, cx).is_some() {
11818 return;
11819 }
11820
11821 if self
11822 .context_menu
11823 .borrow_mut()
11824 .as_mut()
11825 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11826 .unwrap_or(false)
11827 {
11828 return;
11829 }
11830
11831 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11832 cx.propagate();
11833 return;
11834 }
11835
11836 let Some(row_count) = self.visible_row_count() else {
11837 return;
11838 };
11839
11840 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11841
11842 let autoscroll = if action.center_cursor {
11843 Autoscroll::center()
11844 } else {
11845 Autoscroll::fit()
11846 };
11847
11848 let text_layout_details = &self.text_layout_details(window);
11849
11850 self.change_selections(Some(autoscroll), window, cx, |s| {
11851 s.move_with(|map, selection| {
11852 if !selection.is_empty() {
11853 selection.goal = SelectionGoal::None;
11854 }
11855 let (cursor, goal) = movement::up_by_rows(
11856 map,
11857 selection.end,
11858 row_count,
11859 selection.goal,
11860 false,
11861 text_layout_details,
11862 );
11863 selection.collapse_to(cursor, goal);
11864 });
11865 });
11866 }
11867
11868 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11869 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11870 let text_layout_details = &self.text_layout_details(window);
11871 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11872 s.move_heads_with(|map, head, goal| {
11873 movement::up(map, head, goal, false, text_layout_details)
11874 })
11875 })
11876 }
11877
11878 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11879 self.take_rename(true, window, cx);
11880
11881 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11882 cx.propagate();
11883 return;
11884 }
11885
11886 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11887
11888 let text_layout_details = &self.text_layout_details(window);
11889 let selection_count = self.selections.count();
11890 let first_selection = self.selections.first_anchor();
11891
11892 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11893 s.move_with(|map, selection| {
11894 if !selection.is_empty() {
11895 selection.goal = SelectionGoal::None;
11896 }
11897 let (cursor, goal) = movement::down(
11898 map,
11899 selection.end,
11900 selection.goal,
11901 false,
11902 text_layout_details,
11903 );
11904 selection.collapse_to(cursor, goal);
11905 });
11906 });
11907
11908 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11909 {
11910 cx.propagate();
11911 }
11912 }
11913
11914 pub fn select_page_down(
11915 &mut self,
11916 _: &SelectPageDown,
11917 window: &mut Window,
11918 cx: &mut Context<Self>,
11919 ) {
11920 let Some(row_count) = self.visible_row_count() else {
11921 return;
11922 };
11923
11924 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11925
11926 let text_layout_details = &self.text_layout_details(window);
11927
11928 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11929 s.move_heads_with(|map, head, goal| {
11930 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11931 })
11932 })
11933 }
11934
11935 pub fn move_page_down(
11936 &mut self,
11937 action: &MovePageDown,
11938 window: &mut Window,
11939 cx: &mut Context<Self>,
11940 ) {
11941 if self.take_rename(true, window, cx).is_some() {
11942 return;
11943 }
11944
11945 if self
11946 .context_menu
11947 .borrow_mut()
11948 .as_mut()
11949 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11950 .unwrap_or(false)
11951 {
11952 return;
11953 }
11954
11955 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11956 cx.propagate();
11957 return;
11958 }
11959
11960 let Some(row_count) = self.visible_row_count() else {
11961 return;
11962 };
11963
11964 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11965
11966 let autoscroll = if action.center_cursor {
11967 Autoscroll::center()
11968 } else {
11969 Autoscroll::fit()
11970 };
11971
11972 let text_layout_details = &self.text_layout_details(window);
11973 self.change_selections(Some(autoscroll), window, cx, |s| {
11974 s.move_with(|map, selection| {
11975 if !selection.is_empty() {
11976 selection.goal = SelectionGoal::None;
11977 }
11978 let (cursor, goal) = movement::down_by_rows(
11979 map,
11980 selection.end,
11981 row_count,
11982 selection.goal,
11983 false,
11984 text_layout_details,
11985 );
11986 selection.collapse_to(cursor, goal);
11987 });
11988 });
11989 }
11990
11991 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11992 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11993 let text_layout_details = &self.text_layout_details(window);
11994 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11995 s.move_heads_with(|map, head, goal| {
11996 movement::down(map, head, goal, false, text_layout_details)
11997 })
11998 });
11999 }
12000
12001 pub fn context_menu_first(
12002 &mut self,
12003 _: &ContextMenuFirst,
12004 window: &mut Window,
12005 cx: &mut Context<Self>,
12006 ) {
12007 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12008 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12009 }
12010 }
12011
12012 pub fn context_menu_prev(
12013 &mut self,
12014 _: &ContextMenuPrevious,
12015 window: &mut Window,
12016 cx: &mut Context<Self>,
12017 ) {
12018 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12019 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12020 }
12021 }
12022
12023 pub fn context_menu_next(
12024 &mut self,
12025 _: &ContextMenuNext,
12026 window: &mut Window,
12027 cx: &mut Context<Self>,
12028 ) {
12029 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12030 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12031 }
12032 }
12033
12034 pub fn context_menu_last(
12035 &mut self,
12036 _: &ContextMenuLast,
12037 window: &mut Window,
12038 cx: &mut Context<Self>,
12039 ) {
12040 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12041 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12042 }
12043 }
12044
12045 pub fn move_to_previous_word_start(
12046 &mut self,
12047 _: &MoveToPreviousWordStart,
12048 window: &mut Window,
12049 cx: &mut Context<Self>,
12050 ) {
12051 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12052 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12053 s.move_cursors_with(|map, head, _| {
12054 (
12055 movement::previous_word_start(map, head),
12056 SelectionGoal::None,
12057 )
12058 });
12059 })
12060 }
12061
12062 pub fn move_to_previous_subword_start(
12063 &mut self,
12064 _: &MoveToPreviousSubwordStart,
12065 window: &mut Window,
12066 cx: &mut Context<Self>,
12067 ) {
12068 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12069 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12070 s.move_cursors_with(|map, head, _| {
12071 (
12072 movement::previous_subword_start(map, head),
12073 SelectionGoal::None,
12074 )
12075 });
12076 })
12077 }
12078
12079 pub fn select_to_previous_word_start(
12080 &mut self,
12081 _: &SelectToPreviousWordStart,
12082 window: &mut Window,
12083 cx: &mut Context<Self>,
12084 ) {
12085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12086 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12087 s.move_heads_with(|map, head, _| {
12088 (
12089 movement::previous_word_start(map, head),
12090 SelectionGoal::None,
12091 )
12092 });
12093 })
12094 }
12095
12096 pub fn select_to_previous_subword_start(
12097 &mut self,
12098 _: &SelectToPreviousSubwordStart,
12099 window: &mut Window,
12100 cx: &mut Context<Self>,
12101 ) {
12102 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12103 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12104 s.move_heads_with(|map, head, _| {
12105 (
12106 movement::previous_subword_start(map, head),
12107 SelectionGoal::None,
12108 )
12109 });
12110 })
12111 }
12112
12113 pub fn delete_to_previous_word_start(
12114 &mut self,
12115 action: &DeleteToPreviousWordStart,
12116 window: &mut Window,
12117 cx: &mut Context<Self>,
12118 ) {
12119 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12120 self.transact(window, cx, |this, window, cx| {
12121 this.select_autoclose_pair(window, cx);
12122 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12123 s.move_with(|map, selection| {
12124 if selection.is_empty() {
12125 let cursor = if action.ignore_newlines {
12126 movement::previous_word_start(map, selection.head())
12127 } else {
12128 movement::previous_word_start_or_newline(map, selection.head())
12129 };
12130 selection.set_head(cursor, SelectionGoal::None);
12131 }
12132 });
12133 });
12134 this.insert("", window, cx);
12135 });
12136 }
12137
12138 pub fn delete_to_previous_subword_start(
12139 &mut self,
12140 _: &DeleteToPreviousSubwordStart,
12141 window: &mut Window,
12142 cx: &mut Context<Self>,
12143 ) {
12144 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12145 self.transact(window, cx, |this, window, cx| {
12146 this.select_autoclose_pair(window, cx);
12147 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12148 s.move_with(|map, selection| {
12149 if selection.is_empty() {
12150 let cursor = movement::previous_subword_start(map, selection.head());
12151 selection.set_head(cursor, SelectionGoal::None);
12152 }
12153 });
12154 });
12155 this.insert("", window, cx);
12156 });
12157 }
12158
12159 pub fn move_to_next_word_end(
12160 &mut self,
12161 _: &MoveToNextWordEnd,
12162 window: &mut Window,
12163 cx: &mut Context<Self>,
12164 ) {
12165 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12166 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12167 s.move_cursors_with(|map, head, _| {
12168 (movement::next_word_end(map, head), SelectionGoal::None)
12169 });
12170 })
12171 }
12172
12173 pub fn move_to_next_subword_end(
12174 &mut self,
12175 _: &MoveToNextSubwordEnd,
12176 window: &mut Window,
12177 cx: &mut Context<Self>,
12178 ) {
12179 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12180 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12181 s.move_cursors_with(|map, head, _| {
12182 (movement::next_subword_end(map, head), SelectionGoal::None)
12183 });
12184 })
12185 }
12186
12187 pub fn select_to_next_word_end(
12188 &mut self,
12189 _: &SelectToNextWordEnd,
12190 window: &mut Window,
12191 cx: &mut Context<Self>,
12192 ) {
12193 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12194 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12195 s.move_heads_with(|map, head, _| {
12196 (movement::next_word_end(map, head), SelectionGoal::None)
12197 });
12198 })
12199 }
12200
12201 pub fn select_to_next_subword_end(
12202 &mut self,
12203 _: &SelectToNextSubwordEnd,
12204 window: &mut Window,
12205 cx: &mut Context<Self>,
12206 ) {
12207 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12209 s.move_heads_with(|map, head, _| {
12210 (movement::next_subword_end(map, head), SelectionGoal::None)
12211 });
12212 })
12213 }
12214
12215 pub fn delete_to_next_word_end(
12216 &mut self,
12217 action: &DeleteToNextWordEnd,
12218 window: &mut Window,
12219 cx: &mut Context<Self>,
12220 ) {
12221 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12222 self.transact(window, cx, |this, window, cx| {
12223 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12224 s.move_with(|map, selection| {
12225 if selection.is_empty() {
12226 let cursor = if action.ignore_newlines {
12227 movement::next_word_end(map, selection.head())
12228 } else {
12229 movement::next_word_end_or_newline(map, selection.head())
12230 };
12231 selection.set_head(cursor, SelectionGoal::None);
12232 }
12233 });
12234 });
12235 this.insert("", window, cx);
12236 });
12237 }
12238
12239 pub fn delete_to_next_subword_end(
12240 &mut self,
12241 _: &DeleteToNextSubwordEnd,
12242 window: &mut Window,
12243 cx: &mut Context<Self>,
12244 ) {
12245 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12246 self.transact(window, cx, |this, window, cx| {
12247 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12248 s.move_with(|map, selection| {
12249 if selection.is_empty() {
12250 let cursor = movement::next_subword_end(map, selection.head());
12251 selection.set_head(cursor, SelectionGoal::None);
12252 }
12253 });
12254 });
12255 this.insert("", window, cx);
12256 });
12257 }
12258
12259 pub fn move_to_beginning_of_line(
12260 &mut self,
12261 action: &MoveToBeginningOfLine,
12262 window: &mut Window,
12263 cx: &mut Context<Self>,
12264 ) {
12265 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12266 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12267 s.move_cursors_with(|map, head, _| {
12268 (
12269 movement::indented_line_beginning(
12270 map,
12271 head,
12272 action.stop_at_soft_wraps,
12273 action.stop_at_indent,
12274 ),
12275 SelectionGoal::None,
12276 )
12277 });
12278 })
12279 }
12280
12281 pub fn select_to_beginning_of_line(
12282 &mut self,
12283 action: &SelectToBeginningOfLine,
12284 window: &mut Window,
12285 cx: &mut Context<Self>,
12286 ) {
12287 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12288 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12289 s.move_heads_with(|map, head, _| {
12290 (
12291 movement::indented_line_beginning(
12292 map,
12293 head,
12294 action.stop_at_soft_wraps,
12295 action.stop_at_indent,
12296 ),
12297 SelectionGoal::None,
12298 )
12299 });
12300 });
12301 }
12302
12303 pub fn delete_to_beginning_of_line(
12304 &mut self,
12305 action: &DeleteToBeginningOfLine,
12306 window: &mut Window,
12307 cx: &mut Context<Self>,
12308 ) {
12309 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12310 self.transact(window, cx, |this, window, cx| {
12311 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12312 s.move_with(|_, selection| {
12313 selection.reversed = true;
12314 });
12315 });
12316
12317 this.select_to_beginning_of_line(
12318 &SelectToBeginningOfLine {
12319 stop_at_soft_wraps: false,
12320 stop_at_indent: action.stop_at_indent,
12321 },
12322 window,
12323 cx,
12324 );
12325 this.backspace(&Backspace, window, cx);
12326 });
12327 }
12328
12329 pub fn move_to_end_of_line(
12330 &mut self,
12331 action: &MoveToEndOfLine,
12332 window: &mut Window,
12333 cx: &mut Context<Self>,
12334 ) {
12335 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12336 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12337 s.move_cursors_with(|map, head, _| {
12338 (
12339 movement::line_end(map, head, action.stop_at_soft_wraps),
12340 SelectionGoal::None,
12341 )
12342 });
12343 })
12344 }
12345
12346 pub fn select_to_end_of_line(
12347 &mut self,
12348 action: &SelectToEndOfLine,
12349 window: &mut Window,
12350 cx: &mut Context<Self>,
12351 ) {
12352 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12354 s.move_heads_with(|map, head, _| {
12355 (
12356 movement::line_end(map, head, action.stop_at_soft_wraps),
12357 SelectionGoal::None,
12358 )
12359 });
12360 })
12361 }
12362
12363 pub fn delete_to_end_of_line(
12364 &mut self,
12365 _: &DeleteToEndOfLine,
12366 window: &mut Window,
12367 cx: &mut Context<Self>,
12368 ) {
12369 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12370 self.transact(window, cx, |this, window, cx| {
12371 this.select_to_end_of_line(
12372 &SelectToEndOfLine {
12373 stop_at_soft_wraps: false,
12374 },
12375 window,
12376 cx,
12377 );
12378 this.delete(&Delete, window, cx);
12379 });
12380 }
12381
12382 pub fn cut_to_end_of_line(
12383 &mut self,
12384 _: &CutToEndOfLine,
12385 window: &mut Window,
12386 cx: &mut Context<Self>,
12387 ) {
12388 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12389 self.transact(window, cx, |this, window, cx| {
12390 this.select_to_end_of_line(
12391 &SelectToEndOfLine {
12392 stop_at_soft_wraps: false,
12393 },
12394 window,
12395 cx,
12396 );
12397 this.cut(&Cut, window, cx);
12398 });
12399 }
12400
12401 pub fn move_to_start_of_paragraph(
12402 &mut self,
12403 _: &MoveToStartOfParagraph,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12408 cx.propagate();
12409 return;
12410 }
12411 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12412 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12413 s.move_with(|map, selection| {
12414 selection.collapse_to(
12415 movement::start_of_paragraph(map, selection.head(), 1),
12416 SelectionGoal::None,
12417 )
12418 });
12419 })
12420 }
12421
12422 pub fn move_to_end_of_paragraph(
12423 &mut self,
12424 _: &MoveToEndOfParagraph,
12425 window: &mut Window,
12426 cx: &mut Context<Self>,
12427 ) {
12428 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12429 cx.propagate();
12430 return;
12431 }
12432 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12433 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12434 s.move_with(|map, selection| {
12435 selection.collapse_to(
12436 movement::end_of_paragraph(map, selection.head(), 1),
12437 SelectionGoal::None,
12438 )
12439 });
12440 })
12441 }
12442
12443 pub fn select_to_start_of_paragraph(
12444 &mut self,
12445 _: &SelectToStartOfParagraph,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12450 cx.propagate();
12451 return;
12452 }
12453 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12454 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12455 s.move_heads_with(|map, head, _| {
12456 (
12457 movement::start_of_paragraph(map, head, 1),
12458 SelectionGoal::None,
12459 )
12460 });
12461 })
12462 }
12463
12464 pub fn select_to_end_of_paragraph(
12465 &mut self,
12466 _: &SelectToEndOfParagraph,
12467 window: &mut Window,
12468 cx: &mut Context<Self>,
12469 ) {
12470 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12471 cx.propagate();
12472 return;
12473 }
12474 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12475 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12476 s.move_heads_with(|map, head, _| {
12477 (
12478 movement::end_of_paragraph(map, head, 1),
12479 SelectionGoal::None,
12480 )
12481 });
12482 })
12483 }
12484
12485 pub fn move_to_start_of_excerpt(
12486 &mut self,
12487 _: &MoveToStartOfExcerpt,
12488 window: &mut Window,
12489 cx: &mut Context<Self>,
12490 ) {
12491 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12492 cx.propagate();
12493 return;
12494 }
12495 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12496 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12497 s.move_with(|map, selection| {
12498 selection.collapse_to(
12499 movement::start_of_excerpt(
12500 map,
12501 selection.head(),
12502 workspace::searchable::Direction::Prev,
12503 ),
12504 SelectionGoal::None,
12505 )
12506 });
12507 })
12508 }
12509
12510 pub fn move_to_start_of_next_excerpt(
12511 &mut self,
12512 _: &MoveToStartOfNextExcerpt,
12513 window: &mut Window,
12514 cx: &mut Context<Self>,
12515 ) {
12516 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12517 cx.propagate();
12518 return;
12519 }
12520
12521 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12522 s.move_with(|map, selection| {
12523 selection.collapse_to(
12524 movement::start_of_excerpt(
12525 map,
12526 selection.head(),
12527 workspace::searchable::Direction::Next,
12528 ),
12529 SelectionGoal::None,
12530 )
12531 });
12532 })
12533 }
12534
12535 pub fn move_to_end_of_excerpt(
12536 &mut self,
12537 _: &MoveToEndOfExcerpt,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12542 cx.propagate();
12543 return;
12544 }
12545 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12546 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12547 s.move_with(|map, selection| {
12548 selection.collapse_to(
12549 movement::end_of_excerpt(
12550 map,
12551 selection.head(),
12552 workspace::searchable::Direction::Next,
12553 ),
12554 SelectionGoal::None,
12555 )
12556 });
12557 })
12558 }
12559
12560 pub fn move_to_end_of_previous_excerpt(
12561 &mut self,
12562 _: &MoveToEndOfPreviousExcerpt,
12563 window: &mut Window,
12564 cx: &mut Context<Self>,
12565 ) {
12566 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12567 cx.propagate();
12568 return;
12569 }
12570 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12571 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12572 s.move_with(|map, selection| {
12573 selection.collapse_to(
12574 movement::end_of_excerpt(
12575 map,
12576 selection.head(),
12577 workspace::searchable::Direction::Prev,
12578 ),
12579 SelectionGoal::None,
12580 )
12581 });
12582 })
12583 }
12584
12585 pub fn select_to_start_of_excerpt(
12586 &mut self,
12587 _: &SelectToStartOfExcerpt,
12588 window: &mut Window,
12589 cx: &mut Context<Self>,
12590 ) {
12591 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12592 cx.propagate();
12593 return;
12594 }
12595 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12596 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12597 s.move_heads_with(|map, head, _| {
12598 (
12599 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12600 SelectionGoal::None,
12601 )
12602 });
12603 })
12604 }
12605
12606 pub fn select_to_start_of_next_excerpt(
12607 &mut self,
12608 _: &SelectToStartOfNextExcerpt,
12609 window: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12613 cx.propagate();
12614 return;
12615 }
12616 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12617 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12618 s.move_heads_with(|map, head, _| {
12619 (
12620 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12621 SelectionGoal::None,
12622 )
12623 });
12624 })
12625 }
12626
12627 pub fn select_to_end_of_excerpt(
12628 &mut self,
12629 _: &SelectToEndOfExcerpt,
12630 window: &mut Window,
12631 cx: &mut Context<Self>,
12632 ) {
12633 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12634 cx.propagate();
12635 return;
12636 }
12637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12638 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12639 s.move_heads_with(|map, head, _| {
12640 (
12641 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12642 SelectionGoal::None,
12643 )
12644 });
12645 })
12646 }
12647
12648 pub fn select_to_end_of_previous_excerpt(
12649 &mut self,
12650 _: &SelectToEndOfPreviousExcerpt,
12651 window: &mut Window,
12652 cx: &mut Context<Self>,
12653 ) {
12654 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12655 cx.propagate();
12656 return;
12657 }
12658 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12659 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12660 s.move_heads_with(|map, head, _| {
12661 (
12662 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12663 SelectionGoal::None,
12664 )
12665 });
12666 })
12667 }
12668
12669 pub fn move_to_beginning(
12670 &mut self,
12671 _: &MoveToBeginning,
12672 window: &mut Window,
12673 cx: &mut Context<Self>,
12674 ) {
12675 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12676 cx.propagate();
12677 return;
12678 }
12679 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12680 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12681 s.select_ranges(vec![0..0]);
12682 });
12683 }
12684
12685 pub fn select_to_beginning(
12686 &mut self,
12687 _: &SelectToBeginning,
12688 window: &mut Window,
12689 cx: &mut Context<Self>,
12690 ) {
12691 let mut selection = self.selections.last::<Point>(cx);
12692 selection.set_head(Point::zero(), SelectionGoal::None);
12693 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12694 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12695 s.select(vec![selection]);
12696 });
12697 }
12698
12699 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12700 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12701 cx.propagate();
12702 return;
12703 }
12704 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12705 let cursor = self.buffer.read(cx).read(cx).len();
12706 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12707 s.select_ranges(vec![cursor..cursor])
12708 });
12709 }
12710
12711 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12712 self.nav_history = nav_history;
12713 }
12714
12715 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12716 self.nav_history.as_ref()
12717 }
12718
12719 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12720 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12721 }
12722
12723 fn push_to_nav_history(
12724 &mut self,
12725 cursor_anchor: Anchor,
12726 new_position: Option<Point>,
12727 is_deactivate: bool,
12728 cx: &mut Context<Self>,
12729 ) {
12730 if let Some(nav_history) = self.nav_history.as_mut() {
12731 let buffer = self.buffer.read(cx).read(cx);
12732 let cursor_position = cursor_anchor.to_point(&buffer);
12733 let scroll_state = self.scroll_manager.anchor();
12734 let scroll_top_row = scroll_state.top_row(&buffer);
12735 drop(buffer);
12736
12737 if let Some(new_position) = new_position {
12738 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12739 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12740 return;
12741 }
12742 }
12743
12744 nav_history.push(
12745 Some(NavigationData {
12746 cursor_anchor,
12747 cursor_position,
12748 scroll_anchor: scroll_state,
12749 scroll_top_row,
12750 }),
12751 cx,
12752 );
12753 cx.emit(EditorEvent::PushedToNavHistory {
12754 anchor: cursor_anchor,
12755 is_deactivate,
12756 })
12757 }
12758 }
12759
12760 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12761 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12762 let buffer = self.buffer.read(cx).snapshot(cx);
12763 let mut selection = self.selections.first::<usize>(cx);
12764 selection.set_head(buffer.len(), SelectionGoal::None);
12765 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12766 s.select(vec![selection]);
12767 });
12768 }
12769
12770 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12771 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12772 let end = self.buffer.read(cx).read(cx).len();
12773 self.change_selections(None, window, cx, |s| {
12774 s.select_ranges(vec![0..end]);
12775 });
12776 }
12777
12778 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12779 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12780 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12781 let mut selections = self.selections.all::<Point>(cx);
12782 let max_point = display_map.buffer_snapshot.max_point();
12783 for selection in &mut selections {
12784 let rows = selection.spanned_rows(true, &display_map);
12785 selection.start = Point::new(rows.start.0, 0);
12786 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12787 selection.reversed = false;
12788 }
12789 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12790 s.select(selections);
12791 });
12792 }
12793
12794 pub fn split_selection_into_lines(
12795 &mut self,
12796 _: &SplitSelectionIntoLines,
12797 window: &mut Window,
12798 cx: &mut Context<Self>,
12799 ) {
12800 let selections = self
12801 .selections
12802 .all::<Point>(cx)
12803 .into_iter()
12804 .map(|selection| selection.start..selection.end)
12805 .collect::<Vec<_>>();
12806 self.unfold_ranges(&selections, true, true, cx);
12807
12808 let mut new_selection_ranges = Vec::new();
12809 {
12810 let buffer = self.buffer.read(cx).read(cx);
12811 for selection in selections {
12812 for row in selection.start.row..selection.end.row {
12813 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12814 new_selection_ranges.push(cursor..cursor);
12815 }
12816
12817 let is_multiline_selection = selection.start.row != selection.end.row;
12818 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12819 // so this action feels more ergonomic when paired with other selection operations
12820 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12821 if !should_skip_last {
12822 new_selection_ranges.push(selection.end..selection.end);
12823 }
12824 }
12825 }
12826 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12827 s.select_ranges(new_selection_ranges);
12828 });
12829 }
12830
12831 pub fn add_selection_above(
12832 &mut self,
12833 _: &AddSelectionAbove,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 self.add_selection(true, window, cx);
12838 }
12839
12840 pub fn add_selection_below(
12841 &mut self,
12842 _: &AddSelectionBelow,
12843 window: &mut Window,
12844 cx: &mut Context<Self>,
12845 ) {
12846 self.add_selection(false, window, cx);
12847 }
12848
12849 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12850 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12851
12852 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12853 let all_selections = self.selections.all::<Point>(cx);
12854 let text_layout_details = self.text_layout_details(window);
12855
12856 let (mut columnar_selections, new_selections_to_columnarize) = {
12857 if let Some(state) = self.add_selections_state.as_ref() {
12858 let columnar_selection_ids: HashSet<_> = state
12859 .groups
12860 .iter()
12861 .flat_map(|group| group.stack.iter())
12862 .copied()
12863 .collect();
12864
12865 all_selections
12866 .into_iter()
12867 .partition(|s| columnar_selection_ids.contains(&s.id))
12868 } else {
12869 (Vec::new(), all_selections)
12870 }
12871 };
12872
12873 let mut state = self
12874 .add_selections_state
12875 .take()
12876 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12877
12878 for selection in new_selections_to_columnarize {
12879 let range = selection.display_range(&display_map).sorted();
12880 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12881 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12882 let positions = start_x.min(end_x)..start_x.max(end_x);
12883 let mut stack = Vec::new();
12884 for row in range.start.row().0..=range.end.row().0 {
12885 if let Some(selection) = self.selections.build_columnar_selection(
12886 &display_map,
12887 DisplayRow(row),
12888 &positions,
12889 selection.reversed,
12890 &text_layout_details,
12891 ) {
12892 stack.push(selection.id);
12893 columnar_selections.push(selection);
12894 }
12895 }
12896 if !stack.is_empty() {
12897 if above {
12898 stack.reverse();
12899 }
12900 state.groups.push(AddSelectionsGroup { above, stack });
12901 }
12902 }
12903
12904 let mut final_selections = Vec::new();
12905 let end_row = if above {
12906 DisplayRow(0)
12907 } else {
12908 display_map.max_point().row()
12909 };
12910
12911 let mut last_added_item_per_group = HashMap::default();
12912 for group in state.groups.iter_mut() {
12913 if let Some(last_id) = group.stack.last() {
12914 last_added_item_per_group.insert(*last_id, group);
12915 }
12916 }
12917
12918 for selection in columnar_selections {
12919 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12920 if above == group.above {
12921 let range = selection.display_range(&display_map).sorted();
12922 debug_assert_eq!(range.start.row(), range.end.row());
12923 let mut row = range.start.row();
12924 let positions =
12925 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12926 px(start)..px(end)
12927 } else {
12928 let start_x =
12929 display_map.x_for_display_point(range.start, &text_layout_details);
12930 let end_x =
12931 display_map.x_for_display_point(range.end, &text_layout_details);
12932 start_x.min(end_x)..start_x.max(end_x)
12933 };
12934
12935 let mut maybe_new_selection = None;
12936 while row != end_row {
12937 if above {
12938 row.0 -= 1;
12939 } else {
12940 row.0 += 1;
12941 }
12942 if let Some(new_selection) = self.selections.build_columnar_selection(
12943 &display_map,
12944 row,
12945 &positions,
12946 selection.reversed,
12947 &text_layout_details,
12948 ) {
12949 maybe_new_selection = Some(new_selection);
12950 break;
12951 }
12952 }
12953
12954 if let Some(new_selection) = maybe_new_selection {
12955 group.stack.push(new_selection.id);
12956 if above {
12957 final_selections.push(new_selection);
12958 final_selections.push(selection);
12959 } else {
12960 final_selections.push(selection);
12961 final_selections.push(new_selection);
12962 }
12963 } else {
12964 final_selections.push(selection);
12965 }
12966 } else {
12967 group.stack.pop();
12968 }
12969 } else {
12970 final_selections.push(selection);
12971 }
12972 }
12973
12974 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12975 s.select(final_selections);
12976 });
12977
12978 let final_selection_ids: HashSet<_> = self
12979 .selections
12980 .all::<Point>(cx)
12981 .iter()
12982 .map(|s| s.id)
12983 .collect();
12984 state.groups.retain_mut(|group| {
12985 // selections might get merged above so we remove invalid items from stacks
12986 group.stack.retain(|id| final_selection_ids.contains(id));
12987
12988 // single selection in stack can be treated as initial state
12989 group.stack.len() > 1
12990 });
12991
12992 if !state.groups.is_empty() {
12993 self.add_selections_state = Some(state);
12994 }
12995 }
12996
12997 fn select_match_ranges(
12998 &mut self,
12999 range: Range<usize>,
13000 reversed: bool,
13001 replace_newest: bool,
13002 auto_scroll: Option<Autoscroll>,
13003 window: &mut Window,
13004 cx: &mut Context<Editor>,
13005 ) {
13006 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13007 self.change_selections(auto_scroll, window, cx, |s| {
13008 if replace_newest {
13009 s.delete(s.newest_anchor().id);
13010 }
13011 if reversed {
13012 s.insert_range(range.end..range.start);
13013 } else {
13014 s.insert_range(range);
13015 }
13016 });
13017 }
13018
13019 pub fn select_next_match_internal(
13020 &mut self,
13021 display_map: &DisplaySnapshot,
13022 replace_newest: bool,
13023 autoscroll: Option<Autoscroll>,
13024 window: &mut Window,
13025 cx: &mut Context<Self>,
13026 ) -> Result<()> {
13027 let buffer = &display_map.buffer_snapshot;
13028 let mut selections = self.selections.all::<usize>(cx);
13029 if let Some(mut select_next_state) = self.select_next_state.take() {
13030 let query = &select_next_state.query;
13031 if !select_next_state.done {
13032 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13033 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13034 let mut next_selected_range = None;
13035
13036 let bytes_after_last_selection =
13037 buffer.bytes_in_range(last_selection.end..buffer.len());
13038 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13039 let query_matches = query
13040 .stream_find_iter(bytes_after_last_selection)
13041 .map(|result| (last_selection.end, result))
13042 .chain(
13043 query
13044 .stream_find_iter(bytes_before_first_selection)
13045 .map(|result| (0, result)),
13046 );
13047
13048 for (start_offset, query_match) in query_matches {
13049 let query_match = query_match.unwrap(); // can only fail due to I/O
13050 let offset_range =
13051 start_offset + query_match.start()..start_offset + query_match.end();
13052 let display_range = offset_range.start.to_display_point(display_map)
13053 ..offset_range.end.to_display_point(display_map);
13054
13055 if !select_next_state.wordwise
13056 || (!movement::is_inside_word(display_map, display_range.start)
13057 && !movement::is_inside_word(display_map, display_range.end))
13058 {
13059 // TODO: This is n^2, because we might check all the selections
13060 if !selections
13061 .iter()
13062 .any(|selection| selection.range().overlaps(&offset_range))
13063 {
13064 next_selected_range = Some(offset_range);
13065 break;
13066 }
13067 }
13068 }
13069
13070 if let Some(next_selected_range) = next_selected_range {
13071 self.select_match_ranges(
13072 next_selected_range,
13073 last_selection.reversed,
13074 replace_newest,
13075 autoscroll,
13076 window,
13077 cx,
13078 );
13079 } else {
13080 select_next_state.done = true;
13081 }
13082 }
13083
13084 self.select_next_state = Some(select_next_state);
13085 } else {
13086 let mut only_carets = true;
13087 let mut same_text_selected = true;
13088 let mut selected_text = None;
13089
13090 let mut selections_iter = selections.iter().peekable();
13091 while let Some(selection) = selections_iter.next() {
13092 if selection.start != selection.end {
13093 only_carets = false;
13094 }
13095
13096 if same_text_selected {
13097 if selected_text.is_none() {
13098 selected_text =
13099 Some(buffer.text_for_range(selection.range()).collect::<String>());
13100 }
13101
13102 if let Some(next_selection) = selections_iter.peek() {
13103 if next_selection.range().len() == selection.range().len() {
13104 let next_selected_text = buffer
13105 .text_for_range(next_selection.range())
13106 .collect::<String>();
13107 if Some(next_selected_text) != selected_text {
13108 same_text_selected = false;
13109 selected_text = None;
13110 }
13111 } else {
13112 same_text_selected = false;
13113 selected_text = None;
13114 }
13115 }
13116 }
13117 }
13118
13119 if only_carets {
13120 for selection in &mut selections {
13121 let word_range = movement::surrounding_word(
13122 display_map,
13123 selection.start.to_display_point(display_map),
13124 );
13125 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13126 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13127 selection.goal = SelectionGoal::None;
13128 selection.reversed = false;
13129 self.select_match_ranges(
13130 selection.start..selection.end,
13131 selection.reversed,
13132 replace_newest,
13133 autoscroll,
13134 window,
13135 cx,
13136 );
13137 }
13138
13139 if selections.len() == 1 {
13140 let selection = selections
13141 .last()
13142 .expect("ensured that there's only one selection");
13143 let query = buffer
13144 .text_for_range(selection.start..selection.end)
13145 .collect::<String>();
13146 let is_empty = query.is_empty();
13147 let select_state = SelectNextState {
13148 query: AhoCorasick::new(&[query])?,
13149 wordwise: true,
13150 done: is_empty,
13151 };
13152 self.select_next_state = Some(select_state);
13153 } else {
13154 self.select_next_state = None;
13155 }
13156 } else if let Some(selected_text) = selected_text {
13157 self.select_next_state = Some(SelectNextState {
13158 query: AhoCorasick::new(&[selected_text])?,
13159 wordwise: false,
13160 done: false,
13161 });
13162 self.select_next_match_internal(
13163 display_map,
13164 replace_newest,
13165 autoscroll,
13166 window,
13167 cx,
13168 )?;
13169 }
13170 }
13171 Ok(())
13172 }
13173
13174 pub fn select_all_matches(
13175 &mut self,
13176 _action: &SelectAllMatches,
13177 window: &mut Window,
13178 cx: &mut Context<Self>,
13179 ) -> Result<()> {
13180 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13181
13182 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13183
13184 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13185 let Some(select_next_state) = self.select_next_state.as_mut() else {
13186 return Ok(());
13187 };
13188 if select_next_state.done {
13189 return Ok(());
13190 }
13191
13192 let mut new_selections = Vec::new();
13193
13194 let reversed = self.selections.oldest::<usize>(cx).reversed;
13195 let buffer = &display_map.buffer_snapshot;
13196 let query_matches = select_next_state
13197 .query
13198 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13199
13200 for query_match in query_matches.into_iter() {
13201 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13202 let offset_range = if reversed {
13203 query_match.end()..query_match.start()
13204 } else {
13205 query_match.start()..query_match.end()
13206 };
13207 let display_range = offset_range.start.to_display_point(&display_map)
13208 ..offset_range.end.to_display_point(&display_map);
13209
13210 if !select_next_state.wordwise
13211 || (!movement::is_inside_word(&display_map, display_range.start)
13212 && !movement::is_inside_word(&display_map, display_range.end))
13213 {
13214 new_selections.push(offset_range.start..offset_range.end);
13215 }
13216 }
13217
13218 select_next_state.done = true;
13219 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13220 self.change_selections(None, window, cx, |selections| {
13221 selections.select_ranges(new_selections)
13222 });
13223
13224 Ok(())
13225 }
13226
13227 pub fn select_next(
13228 &mut self,
13229 action: &SelectNext,
13230 window: &mut Window,
13231 cx: &mut Context<Self>,
13232 ) -> Result<()> {
13233 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13234 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13235 self.select_next_match_internal(
13236 &display_map,
13237 action.replace_newest,
13238 Some(Autoscroll::newest()),
13239 window,
13240 cx,
13241 )?;
13242 Ok(())
13243 }
13244
13245 pub fn select_previous(
13246 &mut self,
13247 action: &SelectPrevious,
13248 window: &mut Window,
13249 cx: &mut Context<Self>,
13250 ) -> Result<()> {
13251 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13252 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13253 let buffer = &display_map.buffer_snapshot;
13254 let mut selections = self.selections.all::<usize>(cx);
13255 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13256 let query = &select_prev_state.query;
13257 if !select_prev_state.done {
13258 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13259 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13260 let mut next_selected_range = None;
13261 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13262 let bytes_before_last_selection =
13263 buffer.reversed_bytes_in_range(0..last_selection.start);
13264 let bytes_after_first_selection =
13265 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13266 let query_matches = query
13267 .stream_find_iter(bytes_before_last_selection)
13268 .map(|result| (last_selection.start, result))
13269 .chain(
13270 query
13271 .stream_find_iter(bytes_after_first_selection)
13272 .map(|result| (buffer.len(), result)),
13273 );
13274 for (end_offset, query_match) in query_matches {
13275 let query_match = query_match.unwrap(); // can only fail due to I/O
13276 let offset_range =
13277 end_offset - query_match.end()..end_offset - query_match.start();
13278 let display_range = offset_range.start.to_display_point(&display_map)
13279 ..offset_range.end.to_display_point(&display_map);
13280
13281 if !select_prev_state.wordwise
13282 || (!movement::is_inside_word(&display_map, display_range.start)
13283 && !movement::is_inside_word(&display_map, display_range.end))
13284 {
13285 next_selected_range = Some(offset_range);
13286 break;
13287 }
13288 }
13289
13290 if let Some(next_selected_range) = next_selected_range {
13291 self.select_match_ranges(
13292 next_selected_range,
13293 last_selection.reversed,
13294 action.replace_newest,
13295 Some(Autoscroll::newest()),
13296 window,
13297 cx,
13298 );
13299 } else {
13300 select_prev_state.done = true;
13301 }
13302 }
13303
13304 self.select_prev_state = Some(select_prev_state);
13305 } else {
13306 let mut only_carets = true;
13307 let mut same_text_selected = true;
13308 let mut selected_text = None;
13309
13310 let mut selections_iter = selections.iter().peekable();
13311 while let Some(selection) = selections_iter.next() {
13312 if selection.start != selection.end {
13313 only_carets = false;
13314 }
13315
13316 if same_text_selected {
13317 if selected_text.is_none() {
13318 selected_text =
13319 Some(buffer.text_for_range(selection.range()).collect::<String>());
13320 }
13321
13322 if let Some(next_selection) = selections_iter.peek() {
13323 if next_selection.range().len() == selection.range().len() {
13324 let next_selected_text = buffer
13325 .text_for_range(next_selection.range())
13326 .collect::<String>();
13327 if Some(next_selected_text) != selected_text {
13328 same_text_selected = false;
13329 selected_text = None;
13330 }
13331 } else {
13332 same_text_selected = false;
13333 selected_text = None;
13334 }
13335 }
13336 }
13337 }
13338
13339 if only_carets {
13340 for selection in &mut selections {
13341 let word_range = movement::surrounding_word(
13342 &display_map,
13343 selection.start.to_display_point(&display_map),
13344 );
13345 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13346 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13347 selection.goal = SelectionGoal::None;
13348 selection.reversed = false;
13349 self.select_match_ranges(
13350 selection.start..selection.end,
13351 selection.reversed,
13352 action.replace_newest,
13353 Some(Autoscroll::newest()),
13354 window,
13355 cx,
13356 );
13357 }
13358 if selections.len() == 1 {
13359 let selection = selections
13360 .last()
13361 .expect("ensured that there's only one selection");
13362 let query = buffer
13363 .text_for_range(selection.start..selection.end)
13364 .collect::<String>();
13365 let is_empty = query.is_empty();
13366 let select_state = SelectNextState {
13367 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13368 wordwise: true,
13369 done: is_empty,
13370 };
13371 self.select_prev_state = Some(select_state);
13372 } else {
13373 self.select_prev_state = None;
13374 }
13375 } else if let Some(selected_text) = selected_text {
13376 self.select_prev_state = Some(SelectNextState {
13377 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13378 wordwise: false,
13379 done: false,
13380 });
13381 self.select_previous(action, window, cx)?;
13382 }
13383 }
13384 Ok(())
13385 }
13386
13387 pub fn find_next_match(
13388 &mut self,
13389 _: &FindNextMatch,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) -> Result<()> {
13393 let selections = self.selections.disjoint_anchors();
13394 match selections.first() {
13395 Some(first) if selections.len() >= 2 => {
13396 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13397 s.select_ranges([first.range()]);
13398 });
13399 }
13400 _ => self.select_next(
13401 &SelectNext {
13402 replace_newest: true,
13403 },
13404 window,
13405 cx,
13406 )?,
13407 }
13408 Ok(())
13409 }
13410
13411 pub fn find_previous_match(
13412 &mut self,
13413 _: &FindPreviousMatch,
13414 window: &mut Window,
13415 cx: &mut Context<Self>,
13416 ) -> Result<()> {
13417 let selections = self.selections.disjoint_anchors();
13418 match selections.last() {
13419 Some(last) if selections.len() >= 2 => {
13420 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13421 s.select_ranges([last.range()]);
13422 });
13423 }
13424 _ => self.select_previous(
13425 &SelectPrevious {
13426 replace_newest: true,
13427 },
13428 window,
13429 cx,
13430 )?,
13431 }
13432 Ok(())
13433 }
13434
13435 pub fn toggle_comments(
13436 &mut self,
13437 action: &ToggleComments,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 if self.read_only(cx) {
13442 return;
13443 }
13444 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13445 let text_layout_details = &self.text_layout_details(window);
13446 self.transact(window, cx, |this, window, cx| {
13447 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13448 let mut edits = Vec::new();
13449 let mut selection_edit_ranges = Vec::new();
13450 let mut last_toggled_row = None;
13451 let snapshot = this.buffer.read(cx).read(cx);
13452 let empty_str: Arc<str> = Arc::default();
13453 let mut suffixes_inserted = Vec::new();
13454 let ignore_indent = action.ignore_indent;
13455
13456 fn comment_prefix_range(
13457 snapshot: &MultiBufferSnapshot,
13458 row: MultiBufferRow,
13459 comment_prefix: &str,
13460 comment_prefix_whitespace: &str,
13461 ignore_indent: bool,
13462 ) -> Range<Point> {
13463 let indent_size = if ignore_indent {
13464 0
13465 } else {
13466 snapshot.indent_size_for_line(row).len
13467 };
13468
13469 let start = Point::new(row.0, indent_size);
13470
13471 let mut line_bytes = snapshot
13472 .bytes_in_range(start..snapshot.max_point())
13473 .flatten()
13474 .copied();
13475
13476 // If this line currently begins with the line comment prefix, then record
13477 // the range containing the prefix.
13478 if line_bytes
13479 .by_ref()
13480 .take(comment_prefix.len())
13481 .eq(comment_prefix.bytes())
13482 {
13483 // Include any whitespace that matches the comment prefix.
13484 let matching_whitespace_len = line_bytes
13485 .zip(comment_prefix_whitespace.bytes())
13486 .take_while(|(a, b)| a == b)
13487 .count() as u32;
13488 let end = Point::new(
13489 start.row,
13490 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13491 );
13492 start..end
13493 } else {
13494 start..start
13495 }
13496 }
13497
13498 fn comment_suffix_range(
13499 snapshot: &MultiBufferSnapshot,
13500 row: MultiBufferRow,
13501 comment_suffix: &str,
13502 comment_suffix_has_leading_space: bool,
13503 ) -> Range<Point> {
13504 let end = Point::new(row.0, snapshot.line_len(row));
13505 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13506
13507 let mut line_end_bytes = snapshot
13508 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13509 .flatten()
13510 .copied();
13511
13512 let leading_space_len = if suffix_start_column > 0
13513 && line_end_bytes.next() == Some(b' ')
13514 && comment_suffix_has_leading_space
13515 {
13516 1
13517 } else {
13518 0
13519 };
13520
13521 // If this line currently begins with the line comment prefix, then record
13522 // the range containing the prefix.
13523 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13524 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13525 start..end
13526 } else {
13527 end..end
13528 }
13529 }
13530
13531 // TODO: Handle selections that cross excerpts
13532 for selection in &mut selections {
13533 let start_column = snapshot
13534 .indent_size_for_line(MultiBufferRow(selection.start.row))
13535 .len;
13536 let language = if let Some(language) =
13537 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13538 {
13539 language
13540 } else {
13541 continue;
13542 };
13543
13544 selection_edit_ranges.clear();
13545
13546 // If multiple selections contain a given row, avoid processing that
13547 // row more than once.
13548 let mut start_row = MultiBufferRow(selection.start.row);
13549 if last_toggled_row == Some(start_row) {
13550 start_row = start_row.next_row();
13551 }
13552 let end_row =
13553 if selection.end.row > selection.start.row && selection.end.column == 0 {
13554 MultiBufferRow(selection.end.row - 1)
13555 } else {
13556 MultiBufferRow(selection.end.row)
13557 };
13558 last_toggled_row = Some(end_row);
13559
13560 if start_row > end_row {
13561 continue;
13562 }
13563
13564 // If the language has line comments, toggle those.
13565 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13566
13567 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13568 if ignore_indent {
13569 full_comment_prefixes = full_comment_prefixes
13570 .into_iter()
13571 .map(|s| Arc::from(s.trim_end()))
13572 .collect();
13573 }
13574
13575 if !full_comment_prefixes.is_empty() {
13576 let first_prefix = full_comment_prefixes
13577 .first()
13578 .expect("prefixes is non-empty");
13579 let prefix_trimmed_lengths = full_comment_prefixes
13580 .iter()
13581 .map(|p| p.trim_end_matches(' ').len())
13582 .collect::<SmallVec<[usize; 4]>>();
13583
13584 let mut all_selection_lines_are_comments = true;
13585
13586 for row in start_row.0..=end_row.0 {
13587 let row = MultiBufferRow(row);
13588 if start_row < end_row && snapshot.is_line_blank(row) {
13589 continue;
13590 }
13591
13592 let prefix_range = full_comment_prefixes
13593 .iter()
13594 .zip(prefix_trimmed_lengths.iter().copied())
13595 .map(|(prefix, trimmed_prefix_len)| {
13596 comment_prefix_range(
13597 snapshot.deref(),
13598 row,
13599 &prefix[..trimmed_prefix_len],
13600 &prefix[trimmed_prefix_len..],
13601 ignore_indent,
13602 )
13603 })
13604 .max_by_key(|range| range.end.column - range.start.column)
13605 .expect("prefixes is non-empty");
13606
13607 if prefix_range.is_empty() {
13608 all_selection_lines_are_comments = false;
13609 }
13610
13611 selection_edit_ranges.push(prefix_range);
13612 }
13613
13614 if all_selection_lines_are_comments {
13615 edits.extend(
13616 selection_edit_ranges
13617 .iter()
13618 .cloned()
13619 .map(|range| (range, empty_str.clone())),
13620 );
13621 } else {
13622 let min_column = selection_edit_ranges
13623 .iter()
13624 .map(|range| range.start.column)
13625 .min()
13626 .unwrap_or(0);
13627 edits.extend(selection_edit_ranges.iter().map(|range| {
13628 let position = Point::new(range.start.row, min_column);
13629 (position..position, first_prefix.clone())
13630 }));
13631 }
13632 } else if let Some((full_comment_prefix, comment_suffix)) =
13633 language.block_comment_delimiters()
13634 {
13635 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13636 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13637 let prefix_range = comment_prefix_range(
13638 snapshot.deref(),
13639 start_row,
13640 comment_prefix,
13641 comment_prefix_whitespace,
13642 ignore_indent,
13643 );
13644 let suffix_range = comment_suffix_range(
13645 snapshot.deref(),
13646 end_row,
13647 comment_suffix.trim_start_matches(' '),
13648 comment_suffix.starts_with(' '),
13649 );
13650
13651 if prefix_range.is_empty() || suffix_range.is_empty() {
13652 edits.push((
13653 prefix_range.start..prefix_range.start,
13654 full_comment_prefix.clone(),
13655 ));
13656 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13657 suffixes_inserted.push((end_row, comment_suffix.len()));
13658 } else {
13659 edits.push((prefix_range, empty_str.clone()));
13660 edits.push((suffix_range, empty_str.clone()));
13661 }
13662 } else {
13663 continue;
13664 }
13665 }
13666
13667 drop(snapshot);
13668 this.buffer.update(cx, |buffer, cx| {
13669 buffer.edit(edits, None, cx);
13670 });
13671
13672 // Adjust selections so that they end before any comment suffixes that
13673 // were inserted.
13674 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13675 let mut selections = this.selections.all::<Point>(cx);
13676 let snapshot = this.buffer.read(cx).read(cx);
13677 for selection in &mut selections {
13678 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13679 match row.cmp(&MultiBufferRow(selection.end.row)) {
13680 Ordering::Less => {
13681 suffixes_inserted.next();
13682 continue;
13683 }
13684 Ordering::Greater => break,
13685 Ordering::Equal => {
13686 if selection.end.column == snapshot.line_len(row) {
13687 if selection.is_empty() {
13688 selection.start.column -= suffix_len as u32;
13689 }
13690 selection.end.column -= suffix_len as u32;
13691 }
13692 break;
13693 }
13694 }
13695 }
13696 }
13697
13698 drop(snapshot);
13699 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13700 s.select(selections)
13701 });
13702
13703 let selections = this.selections.all::<Point>(cx);
13704 let selections_on_single_row = selections.windows(2).all(|selections| {
13705 selections[0].start.row == selections[1].start.row
13706 && selections[0].end.row == selections[1].end.row
13707 && selections[0].start.row == selections[0].end.row
13708 });
13709 let selections_selecting = selections
13710 .iter()
13711 .any(|selection| selection.start != selection.end);
13712 let advance_downwards = action.advance_downwards
13713 && selections_on_single_row
13714 && !selections_selecting
13715 && !matches!(this.mode, EditorMode::SingleLine { .. });
13716
13717 if advance_downwards {
13718 let snapshot = this.buffer.read(cx).snapshot(cx);
13719
13720 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13721 s.move_cursors_with(|display_snapshot, display_point, _| {
13722 let mut point = display_point.to_point(display_snapshot);
13723 point.row += 1;
13724 point = snapshot.clip_point(point, Bias::Left);
13725 let display_point = point.to_display_point(display_snapshot);
13726 let goal = SelectionGoal::HorizontalPosition(
13727 display_snapshot
13728 .x_for_display_point(display_point, text_layout_details)
13729 .into(),
13730 );
13731 (display_point, goal)
13732 })
13733 });
13734 }
13735 });
13736 }
13737
13738 pub fn select_enclosing_symbol(
13739 &mut self,
13740 _: &SelectEnclosingSymbol,
13741 window: &mut Window,
13742 cx: &mut Context<Self>,
13743 ) {
13744 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13745
13746 let buffer = self.buffer.read(cx).snapshot(cx);
13747 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13748
13749 fn update_selection(
13750 selection: &Selection<usize>,
13751 buffer_snap: &MultiBufferSnapshot,
13752 ) -> Option<Selection<usize>> {
13753 let cursor = selection.head();
13754 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13755 for symbol in symbols.iter().rev() {
13756 let start = symbol.range.start.to_offset(buffer_snap);
13757 let end = symbol.range.end.to_offset(buffer_snap);
13758 let new_range = start..end;
13759 if start < selection.start || end > selection.end {
13760 return Some(Selection {
13761 id: selection.id,
13762 start: new_range.start,
13763 end: new_range.end,
13764 goal: SelectionGoal::None,
13765 reversed: selection.reversed,
13766 });
13767 }
13768 }
13769 None
13770 }
13771
13772 let mut selected_larger_symbol = false;
13773 let new_selections = old_selections
13774 .iter()
13775 .map(|selection| match update_selection(selection, &buffer) {
13776 Some(new_selection) => {
13777 if new_selection.range() != selection.range() {
13778 selected_larger_symbol = true;
13779 }
13780 new_selection
13781 }
13782 None => selection.clone(),
13783 })
13784 .collect::<Vec<_>>();
13785
13786 if selected_larger_symbol {
13787 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13788 s.select(new_selections);
13789 });
13790 }
13791 }
13792
13793 pub fn select_larger_syntax_node(
13794 &mut self,
13795 _: &SelectLargerSyntaxNode,
13796 window: &mut Window,
13797 cx: &mut Context<Self>,
13798 ) {
13799 let Some(visible_row_count) = self.visible_row_count() else {
13800 return;
13801 };
13802 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13803 if old_selections.is_empty() {
13804 return;
13805 }
13806
13807 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13808
13809 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13810 let buffer = self.buffer.read(cx).snapshot(cx);
13811
13812 let mut selected_larger_node = false;
13813 let mut new_selections = old_selections
13814 .iter()
13815 .map(|selection| {
13816 let old_range = selection.start..selection.end;
13817
13818 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13819 // manually select word at selection
13820 if ["string_content", "inline"].contains(&node.kind()) {
13821 let word_range = {
13822 let display_point = buffer
13823 .offset_to_point(old_range.start)
13824 .to_display_point(&display_map);
13825 let Range { start, end } =
13826 movement::surrounding_word(&display_map, display_point);
13827 start.to_point(&display_map).to_offset(&buffer)
13828 ..end.to_point(&display_map).to_offset(&buffer)
13829 };
13830 // ignore if word is already selected
13831 if !word_range.is_empty() && old_range != word_range {
13832 let last_word_range = {
13833 let display_point = buffer
13834 .offset_to_point(old_range.end)
13835 .to_display_point(&display_map);
13836 let Range { start, end } =
13837 movement::surrounding_word(&display_map, display_point);
13838 start.to_point(&display_map).to_offset(&buffer)
13839 ..end.to_point(&display_map).to_offset(&buffer)
13840 };
13841 // only select word if start and end point belongs to same word
13842 if word_range == last_word_range {
13843 selected_larger_node = true;
13844 return Selection {
13845 id: selection.id,
13846 start: word_range.start,
13847 end: word_range.end,
13848 goal: SelectionGoal::None,
13849 reversed: selection.reversed,
13850 };
13851 }
13852 }
13853 }
13854 }
13855
13856 let mut new_range = old_range.clone();
13857 while let Some((_node, containing_range)) =
13858 buffer.syntax_ancestor(new_range.clone())
13859 {
13860 new_range = match containing_range {
13861 MultiOrSingleBufferOffsetRange::Single(_) => break,
13862 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13863 };
13864 if !display_map.intersects_fold(new_range.start)
13865 && !display_map.intersects_fold(new_range.end)
13866 {
13867 break;
13868 }
13869 }
13870
13871 selected_larger_node |= new_range != old_range;
13872 Selection {
13873 id: selection.id,
13874 start: new_range.start,
13875 end: new_range.end,
13876 goal: SelectionGoal::None,
13877 reversed: selection.reversed,
13878 }
13879 })
13880 .collect::<Vec<_>>();
13881
13882 if !selected_larger_node {
13883 return; // don't put this call in the history
13884 }
13885
13886 // scroll based on transformation done to the last selection created by the user
13887 let (last_old, last_new) = old_selections
13888 .last()
13889 .zip(new_selections.last().cloned())
13890 .expect("old_selections isn't empty");
13891
13892 // revert selection
13893 let is_selection_reversed = {
13894 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13895 new_selections.last_mut().expect("checked above").reversed =
13896 should_newest_selection_be_reversed;
13897 should_newest_selection_be_reversed
13898 };
13899
13900 if selected_larger_node {
13901 self.select_syntax_node_history.disable_clearing = true;
13902 self.change_selections(None, window, cx, |s| {
13903 s.select(new_selections.clone());
13904 });
13905 self.select_syntax_node_history.disable_clearing = false;
13906 }
13907
13908 let start_row = last_new.start.to_display_point(&display_map).row().0;
13909 let end_row = last_new.end.to_display_point(&display_map).row().0;
13910 let selection_height = end_row - start_row + 1;
13911 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13912
13913 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13914 let scroll_behavior = if fits_on_the_screen {
13915 self.request_autoscroll(Autoscroll::fit(), cx);
13916 SelectSyntaxNodeScrollBehavior::FitSelection
13917 } else if is_selection_reversed {
13918 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13919 SelectSyntaxNodeScrollBehavior::CursorTop
13920 } else {
13921 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13922 SelectSyntaxNodeScrollBehavior::CursorBottom
13923 };
13924
13925 self.select_syntax_node_history.push((
13926 old_selections,
13927 scroll_behavior,
13928 is_selection_reversed,
13929 ));
13930 }
13931
13932 pub fn select_smaller_syntax_node(
13933 &mut self,
13934 _: &SelectSmallerSyntaxNode,
13935 window: &mut Window,
13936 cx: &mut Context<Self>,
13937 ) {
13938 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13939
13940 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13941 self.select_syntax_node_history.pop()
13942 {
13943 if let Some(selection) = selections.last_mut() {
13944 selection.reversed = is_selection_reversed;
13945 }
13946
13947 self.select_syntax_node_history.disable_clearing = true;
13948 self.change_selections(None, window, cx, |s| {
13949 s.select(selections.to_vec());
13950 });
13951 self.select_syntax_node_history.disable_clearing = false;
13952
13953 match scroll_behavior {
13954 SelectSyntaxNodeScrollBehavior::CursorTop => {
13955 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13956 }
13957 SelectSyntaxNodeScrollBehavior::FitSelection => {
13958 self.request_autoscroll(Autoscroll::fit(), cx);
13959 }
13960 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13961 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13962 }
13963 }
13964 }
13965 }
13966
13967 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13968 if !EditorSettings::get_global(cx).gutter.runnables {
13969 self.clear_tasks();
13970 return Task::ready(());
13971 }
13972 let project = self.project.as_ref().map(Entity::downgrade);
13973 let task_sources = self.lsp_task_sources(cx);
13974 let multi_buffer = self.buffer.downgrade();
13975 cx.spawn_in(window, async move |editor, cx| {
13976 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13977 let Some(project) = project.and_then(|p| p.upgrade()) else {
13978 return;
13979 };
13980 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13981 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13982 }) else {
13983 return;
13984 };
13985
13986 let hide_runnables = project
13987 .update(cx, |project, cx| {
13988 // Do not display any test indicators in non-dev server remote projects.
13989 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13990 })
13991 .unwrap_or(true);
13992 if hide_runnables {
13993 return;
13994 }
13995 let new_rows =
13996 cx.background_spawn({
13997 let snapshot = display_snapshot.clone();
13998 async move {
13999 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14000 }
14001 })
14002 .await;
14003 let Ok(lsp_tasks) =
14004 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14005 else {
14006 return;
14007 };
14008 let lsp_tasks = lsp_tasks.await;
14009
14010 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14011 lsp_tasks
14012 .into_iter()
14013 .flat_map(|(kind, tasks)| {
14014 tasks.into_iter().filter_map(move |(location, task)| {
14015 Some((kind.clone(), location?, task))
14016 })
14017 })
14018 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14019 let buffer = location.target.buffer;
14020 let buffer_snapshot = buffer.read(cx).snapshot();
14021 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14022 |(excerpt_id, snapshot, _)| {
14023 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14024 display_snapshot
14025 .buffer_snapshot
14026 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14027 } else {
14028 None
14029 }
14030 },
14031 );
14032 if let Some(offset) = offset {
14033 let task_buffer_range =
14034 location.target.range.to_point(&buffer_snapshot);
14035 let context_buffer_range =
14036 task_buffer_range.to_offset(&buffer_snapshot);
14037 let context_range = BufferOffset(context_buffer_range.start)
14038 ..BufferOffset(context_buffer_range.end);
14039
14040 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14041 .or_insert_with(|| RunnableTasks {
14042 templates: Vec::new(),
14043 offset,
14044 column: task_buffer_range.start.column,
14045 extra_variables: HashMap::default(),
14046 context_range,
14047 })
14048 .templates
14049 .push((kind, task.original_task().clone()));
14050 }
14051
14052 acc
14053 })
14054 }) else {
14055 return;
14056 };
14057
14058 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14059 buffer.language_settings(cx).tasks.prefer_lsp
14060 }) else {
14061 return;
14062 };
14063
14064 let rows = Self::runnable_rows(
14065 project,
14066 display_snapshot,
14067 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14068 new_rows,
14069 cx.clone(),
14070 )
14071 .await;
14072 editor
14073 .update(cx, |editor, _| {
14074 editor.clear_tasks();
14075 for (key, mut value) in rows {
14076 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14077 value.templates.extend(lsp_tasks.templates);
14078 }
14079
14080 editor.insert_tasks(key, value);
14081 }
14082 for (key, value) in lsp_tasks_by_rows {
14083 editor.insert_tasks(key, value);
14084 }
14085 })
14086 .ok();
14087 })
14088 }
14089 fn fetch_runnable_ranges(
14090 snapshot: &DisplaySnapshot,
14091 range: Range<Anchor>,
14092 ) -> Vec<language::RunnableRange> {
14093 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14094 }
14095
14096 fn runnable_rows(
14097 project: Entity<Project>,
14098 snapshot: DisplaySnapshot,
14099 prefer_lsp: bool,
14100 runnable_ranges: Vec<RunnableRange>,
14101 cx: AsyncWindowContext,
14102 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14103 cx.spawn(async move |cx| {
14104 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14105 for mut runnable in runnable_ranges {
14106 let Some(tasks) = cx
14107 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14108 .ok()
14109 else {
14110 continue;
14111 };
14112 let mut tasks = tasks.await;
14113
14114 if prefer_lsp {
14115 tasks.retain(|(task_kind, _)| {
14116 !matches!(task_kind, TaskSourceKind::Language { .. })
14117 });
14118 }
14119 if tasks.is_empty() {
14120 continue;
14121 }
14122
14123 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14124 let Some(row) = snapshot
14125 .buffer_snapshot
14126 .buffer_line_for_row(MultiBufferRow(point.row))
14127 .map(|(_, range)| range.start.row)
14128 else {
14129 continue;
14130 };
14131
14132 let context_range =
14133 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14134 runnable_rows.push((
14135 (runnable.buffer_id, row),
14136 RunnableTasks {
14137 templates: tasks,
14138 offset: snapshot
14139 .buffer_snapshot
14140 .anchor_before(runnable.run_range.start),
14141 context_range,
14142 column: point.column,
14143 extra_variables: runnable.extra_captures,
14144 },
14145 ));
14146 }
14147 runnable_rows
14148 })
14149 }
14150
14151 fn templates_with_tags(
14152 project: &Entity<Project>,
14153 runnable: &mut Runnable,
14154 cx: &mut App,
14155 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14156 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14157 let (worktree_id, file) = project
14158 .buffer_for_id(runnable.buffer, cx)
14159 .and_then(|buffer| buffer.read(cx).file())
14160 .map(|file| (file.worktree_id(cx), file.clone()))
14161 .unzip();
14162
14163 (
14164 project.task_store().read(cx).task_inventory().cloned(),
14165 worktree_id,
14166 file,
14167 )
14168 });
14169
14170 let tags = mem::take(&mut runnable.tags);
14171 let language = runnable.language.clone();
14172 cx.spawn(async move |cx| {
14173 let mut templates_with_tags = Vec::new();
14174 if let Some(inventory) = inventory {
14175 for RunnableTag(tag) in tags {
14176 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14177 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14178 }) else {
14179 return templates_with_tags;
14180 };
14181 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14182 move |(_, template)| {
14183 template.tags.iter().any(|source_tag| source_tag == &tag)
14184 },
14185 ));
14186 }
14187 }
14188 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14189
14190 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14191 // Strongest source wins; if we have worktree tag binding, prefer that to
14192 // global and language bindings;
14193 // if we have a global binding, prefer that to language binding.
14194 let first_mismatch = templates_with_tags
14195 .iter()
14196 .position(|(tag_source, _)| tag_source != leading_tag_source);
14197 if let Some(index) = first_mismatch {
14198 templates_with_tags.truncate(index);
14199 }
14200 }
14201
14202 templates_with_tags
14203 })
14204 }
14205
14206 pub fn move_to_enclosing_bracket(
14207 &mut self,
14208 _: &MoveToEnclosingBracket,
14209 window: &mut Window,
14210 cx: &mut Context<Self>,
14211 ) {
14212 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14213 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14214 s.move_offsets_with(|snapshot, selection| {
14215 let Some(enclosing_bracket_ranges) =
14216 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14217 else {
14218 return;
14219 };
14220
14221 let mut best_length = usize::MAX;
14222 let mut best_inside = false;
14223 let mut best_in_bracket_range = false;
14224 let mut best_destination = None;
14225 for (open, close) in enclosing_bracket_ranges {
14226 let close = close.to_inclusive();
14227 let length = close.end() - open.start;
14228 let inside = selection.start >= open.end && selection.end <= *close.start();
14229 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14230 || close.contains(&selection.head());
14231
14232 // If best is next to a bracket and current isn't, skip
14233 if !in_bracket_range && best_in_bracket_range {
14234 continue;
14235 }
14236
14237 // Prefer smaller lengths unless best is inside and current isn't
14238 if length > best_length && (best_inside || !inside) {
14239 continue;
14240 }
14241
14242 best_length = length;
14243 best_inside = inside;
14244 best_in_bracket_range = in_bracket_range;
14245 best_destination = Some(
14246 if close.contains(&selection.start) && close.contains(&selection.end) {
14247 if inside { open.end } else { open.start }
14248 } else if inside {
14249 *close.start()
14250 } else {
14251 *close.end()
14252 },
14253 );
14254 }
14255
14256 if let Some(destination) = best_destination {
14257 selection.collapse_to(destination, SelectionGoal::None);
14258 }
14259 })
14260 });
14261 }
14262
14263 pub fn undo_selection(
14264 &mut self,
14265 _: &UndoSelection,
14266 window: &mut Window,
14267 cx: &mut Context<Self>,
14268 ) {
14269 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14270 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14271 self.selection_history.mode = SelectionHistoryMode::Undoing;
14272 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14273 this.end_selection(window, cx);
14274 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14275 s.select_anchors(entry.selections.to_vec())
14276 });
14277 });
14278 self.selection_history.mode = SelectionHistoryMode::Normal;
14279
14280 self.select_next_state = entry.select_next_state;
14281 self.select_prev_state = entry.select_prev_state;
14282 self.add_selections_state = entry.add_selections_state;
14283 }
14284 }
14285
14286 pub fn redo_selection(
14287 &mut self,
14288 _: &RedoSelection,
14289 window: &mut Window,
14290 cx: &mut Context<Self>,
14291 ) {
14292 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14293 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14294 self.selection_history.mode = SelectionHistoryMode::Redoing;
14295 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14296 this.end_selection(window, cx);
14297 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14298 s.select_anchors(entry.selections.to_vec())
14299 });
14300 });
14301 self.selection_history.mode = SelectionHistoryMode::Normal;
14302
14303 self.select_next_state = entry.select_next_state;
14304 self.select_prev_state = entry.select_prev_state;
14305 self.add_selections_state = entry.add_selections_state;
14306 }
14307 }
14308
14309 pub fn expand_excerpts(
14310 &mut self,
14311 action: &ExpandExcerpts,
14312 _: &mut Window,
14313 cx: &mut Context<Self>,
14314 ) {
14315 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14316 }
14317
14318 pub fn expand_excerpts_down(
14319 &mut self,
14320 action: &ExpandExcerptsDown,
14321 _: &mut Window,
14322 cx: &mut Context<Self>,
14323 ) {
14324 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14325 }
14326
14327 pub fn expand_excerpts_up(
14328 &mut self,
14329 action: &ExpandExcerptsUp,
14330 _: &mut Window,
14331 cx: &mut Context<Self>,
14332 ) {
14333 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14334 }
14335
14336 pub fn expand_excerpts_for_direction(
14337 &mut self,
14338 lines: u32,
14339 direction: ExpandExcerptDirection,
14340
14341 cx: &mut Context<Self>,
14342 ) {
14343 let selections = self.selections.disjoint_anchors();
14344
14345 let lines = if lines == 0 {
14346 EditorSettings::get_global(cx).expand_excerpt_lines
14347 } else {
14348 lines
14349 };
14350
14351 self.buffer.update(cx, |buffer, cx| {
14352 let snapshot = buffer.snapshot(cx);
14353 let mut excerpt_ids = selections
14354 .iter()
14355 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14356 .collect::<Vec<_>>();
14357 excerpt_ids.sort();
14358 excerpt_ids.dedup();
14359 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14360 })
14361 }
14362
14363 pub fn expand_excerpt(
14364 &mut self,
14365 excerpt: ExcerptId,
14366 direction: ExpandExcerptDirection,
14367 window: &mut Window,
14368 cx: &mut Context<Self>,
14369 ) {
14370 let current_scroll_position = self.scroll_position(cx);
14371 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14372 let mut should_scroll_up = false;
14373
14374 if direction == ExpandExcerptDirection::Down {
14375 let multi_buffer = self.buffer.read(cx);
14376 let snapshot = multi_buffer.snapshot(cx);
14377 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14378 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14379 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14380 let buffer_snapshot = buffer.read(cx).snapshot();
14381 let excerpt_end_row =
14382 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14383 let last_row = buffer_snapshot.max_point().row;
14384 let lines_below = last_row.saturating_sub(excerpt_end_row);
14385 should_scroll_up = lines_below >= lines_to_expand;
14386 }
14387 }
14388 }
14389 }
14390
14391 self.buffer.update(cx, |buffer, cx| {
14392 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14393 });
14394
14395 if should_scroll_up {
14396 let new_scroll_position =
14397 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14398 self.set_scroll_position(new_scroll_position, window, cx);
14399 }
14400 }
14401
14402 pub fn go_to_singleton_buffer_point(
14403 &mut self,
14404 point: Point,
14405 window: &mut Window,
14406 cx: &mut Context<Self>,
14407 ) {
14408 self.go_to_singleton_buffer_range(point..point, window, cx);
14409 }
14410
14411 pub fn go_to_singleton_buffer_range(
14412 &mut self,
14413 range: Range<Point>,
14414 window: &mut Window,
14415 cx: &mut Context<Self>,
14416 ) {
14417 let multibuffer = self.buffer().read(cx);
14418 let Some(buffer) = multibuffer.as_singleton() else {
14419 return;
14420 };
14421 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14422 return;
14423 };
14424 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14425 return;
14426 };
14427 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14428 s.select_anchor_ranges([start..end])
14429 });
14430 }
14431
14432 pub fn go_to_diagnostic(
14433 &mut self,
14434 _: &GoToDiagnostic,
14435 window: &mut Window,
14436 cx: &mut Context<Self>,
14437 ) {
14438 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14439 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14440 }
14441
14442 pub fn go_to_prev_diagnostic(
14443 &mut self,
14444 _: &GoToPreviousDiagnostic,
14445 window: &mut Window,
14446 cx: &mut Context<Self>,
14447 ) {
14448 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14449 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14450 }
14451
14452 pub fn go_to_diagnostic_impl(
14453 &mut self,
14454 direction: Direction,
14455 window: &mut Window,
14456 cx: &mut Context<Self>,
14457 ) {
14458 let buffer = self.buffer.read(cx).snapshot(cx);
14459 let selection = self.selections.newest::<usize>(cx);
14460
14461 let mut active_group_id = None;
14462 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14463 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14464 active_group_id = Some(active_group.group_id);
14465 }
14466 }
14467
14468 fn filtered(
14469 snapshot: EditorSnapshot,
14470 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14471 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14472 diagnostics
14473 .filter(|entry| entry.range.start != entry.range.end)
14474 .filter(|entry| !entry.diagnostic.is_unnecessary)
14475 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14476 }
14477
14478 let snapshot = self.snapshot(window, cx);
14479 let before = filtered(
14480 snapshot.clone(),
14481 buffer
14482 .diagnostics_in_range(0..selection.start)
14483 .filter(|entry| entry.range.start <= selection.start),
14484 );
14485 let after = filtered(
14486 snapshot,
14487 buffer
14488 .diagnostics_in_range(selection.start..buffer.len())
14489 .filter(|entry| entry.range.start >= selection.start),
14490 );
14491
14492 let mut found: Option<DiagnosticEntry<usize>> = None;
14493 if direction == Direction::Prev {
14494 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14495 {
14496 for diagnostic in prev_diagnostics.into_iter().rev() {
14497 if diagnostic.range.start != selection.start
14498 || active_group_id
14499 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14500 {
14501 found = Some(diagnostic);
14502 break 'outer;
14503 }
14504 }
14505 }
14506 } else {
14507 for diagnostic in after.chain(before) {
14508 if diagnostic.range.start != selection.start
14509 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14510 {
14511 found = Some(diagnostic);
14512 break;
14513 }
14514 }
14515 }
14516 let Some(next_diagnostic) = found else {
14517 return;
14518 };
14519
14520 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14521 return;
14522 };
14523 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14524 s.select_ranges(vec![
14525 next_diagnostic.range.start..next_diagnostic.range.start,
14526 ])
14527 });
14528 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14529 self.refresh_inline_completion(false, true, window, cx);
14530 }
14531
14532 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14533 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14534 let snapshot = self.snapshot(window, cx);
14535 let selection = self.selections.newest::<Point>(cx);
14536 self.go_to_hunk_before_or_after_position(
14537 &snapshot,
14538 selection.head(),
14539 Direction::Next,
14540 window,
14541 cx,
14542 );
14543 }
14544
14545 pub fn go_to_hunk_before_or_after_position(
14546 &mut self,
14547 snapshot: &EditorSnapshot,
14548 position: Point,
14549 direction: Direction,
14550 window: &mut Window,
14551 cx: &mut Context<Editor>,
14552 ) {
14553 let row = if direction == Direction::Next {
14554 self.hunk_after_position(snapshot, position)
14555 .map(|hunk| hunk.row_range.start)
14556 } else {
14557 self.hunk_before_position(snapshot, position)
14558 };
14559
14560 if let Some(row) = row {
14561 let destination = Point::new(row.0, 0);
14562 let autoscroll = Autoscroll::center();
14563
14564 self.unfold_ranges(&[destination..destination], false, false, cx);
14565 self.change_selections(Some(autoscroll), window, cx, |s| {
14566 s.select_ranges([destination..destination]);
14567 });
14568 }
14569 }
14570
14571 fn hunk_after_position(
14572 &mut self,
14573 snapshot: &EditorSnapshot,
14574 position: Point,
14575 ) -> Option<MultiBufferDiffHunk> {
14576 snapshot
14577 .buffer_snapshot
14578 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14579 .find(|hunk| hunk.row_range.start.0 > position.row)
14580 .or_else(|| {
14581 snapshot
14582 .buffer_snapshot
14583 .diff_hunks_in_range(Point::zero()..position)
14584 .find(|hunk| hunk.row_range.end.0 < position.row)
14585 })
14586 }
14587
14588 fn go_to_prev_hunk(
14589 &mut self,
14590 _: &GoToPreviousHunk,
14591 window: &mut Window,
14592 cx: &mut Context<Self>,
14593 ) {
14594 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14595 let snapshot = self.snapshot(window, cx);
14596 let selection = self.selections.newest::<Point>(cx);
14597 self.go_to_hunk_before_or_after_position(
14598 &snapshot,
14599 selection.head(),
14600 Direction::Prev,
14601 window,
14602 cx,
14603 );
14604 }
14605
14606 fn hunk_before_position(
14607 &mut self,
14608 snapshot: &EditorSnapshot,
14609 position: Point,
14610 ) -> Option<MultiBufferRow> {
14611 snapshot
14612 .buffer_snapshot
14613 .diff_hunk_before(position)
14614 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14615 }
14616
14617 fn go_to_next_change(
14618 &mut self,
14619 _: &GoToNextChange,
14620 window: &mut Window,
14621 cx: &mut Context<Self>,
14622 ) {
14623 if let Some(selections) = self
14624 .change_list
14625 .next_change(1, Direction::Next)
14626 .map(|s| s.to_vec())
14627 {
14628 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14629 let map = s.display_map();
14630 s.select_display_ranges(selections.iter().map(|a| {
14631 let point = a.to_display_point(&map);
14632 point..point
14633 }))
14634 })
14635 }
14636 }
14637
14638 fn go_to_previous_change(
14639 &mut self,
14640 _: &GoToPreviousChange,
14641 window: &mut Window,
14642 cx: &mut Context<Self>,
14643 ) {
14644 if let Some(selections) = self
14645 .change_list
14646 .next_change(1, Direction::Prev)
14647 .map(|s| s.to_vec())
14648 {
14649 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14650 let map = s.display_map();
14651 s.select_display_ranges(selections.iter().map(|a| {
14652 let point = a.to_display_point(&map);
14653 point..point
14654 }))
14655 })
14656 }
14657 }
14658
14659 fn go_to_line<T: 'static>(
14660 &mut self,
14661 position: Anchor,
14662 highlight_color: Option<Hsla>,
14663 window: &mut Window,
14664 cx: &mut Context<Self>,
14665 ) {
14666 let snapshot = self.snapshot(window, cx).display_snapshot;
14667 let position = position.to_point(&snapshot.buffer_snapshot);
14668 let start = snapshot
14669 .buffer_snapshot
14670 .clip_point(Point::new(position.row, 0), Bias::Left);
14671 let end = start + Point::new(1, 0);
14672 let start = snapshot.buffer_snapshot.anchor_before(start);
14673 let end = snapshot.buffer_snapshot.anchor_before(end);
14674
14675 self.highlight_rows::<T>(
14676 start..end,
14677 highlight_color
14678 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14679 Default::default(),
14680 cx,
14681 );
14682
14683 if self.buffer.read(cx).is_singleton() {
14684 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14685 }
14686 }
14687
14688 pub fn go_to_definition(
14689 &mut self,
14690 _: &GoToDefinition,
14691 window: &mut Window,
14692 cx: &mut Context<Self>,
14693 ) -> Task<Result<Navigated>> {
14694 let definition =
14695 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14696 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14697 cx.spawn_in(window, async move |editor, cx| {
14698 if definition.await? == Navigated::Yes {
14699 return Ok(Navigated::Yes);
14700 }
14701 match fallback_strategy {
14702 GoToDefinitionFallback::None => Ok(Navigated::No),
14703 GoToDefinitionFallback::FindAllReferences => {
14704 match editor.update_in(cx, |editor, window, cx| {
14705 editor.find_all_references(&FindAllReferences, window, cx)
14706 })? {
14707 Some(references) => references.await,
14708 None => Ok(Navigated::No),
14709 }
14710 }
14711 }
14712 })
14713 }
14714
14715 pub fn go_to_declaration(
14716 &mut self,
14717 _: &GoToDeclaration,
14718 window: &mut Window,
14719 cx: &mut Context<Self>,
14720 ) -> Task<Result<Navigated>> {
14721 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14722 }
14723
14724 pub fn go_to_declaration_split(
14725 &mut self,
14726 _: &GoToDeclaration,
14727 window: &mut Window,
14728 cx: &mut Context<Self>,
14729 ) -> Task<Result<Navigated>> {
14730 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14731 }
14732
14733 pub fn go_to_implementation(
14734 &mut self,
14735 _: &GoToImplementation,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) -> Task<Result<Navigated>> {
14739 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14740 }
14741
14742 pub fn go_to_implementation_split(
14743 &mut self,
14744 _: &GoToImplementationSplit,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) -> Task<Result<Navigated>> {
14748 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14749 }
14750
14751 pub fn go_to_type_definition(
14752 &mut self,
14753 _: &GoToTypeDefinition,
14754 window: &mut Window,
14755 cx: &mut Context<Self>,
14756 ) -> Task<Result<Navigated>> {
14757 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14758 }
14759
14760 pub fn go_to_definition_split(
14761 &mut self,
14762 _: &GoToDefinitionSplit,
14763 window: &mut Window,
14764 cx: &mut Context<Self>,
14765 ) -> Task<Result<Navigated>> {
14766 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14767 }
14768
14769 pub fn go_to_type_definition_split(
14770 &mut self,
14771 _: &GoToTypeDefinitionSplit,
14772 window: &mut Window,
14773 cx: &mut Context<Self>,
14774 ) -> Task<Result<Navigated>> {
14775 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14776 }
14777
14778 fn go_to_definition_of_kind(
14779 &mut self,
14780 kind: GotoDefinitionKind,
14781 split: bool,
14782 window: &mut Window,
14783 cx: &mut Context<Self>,
14784 ) -> Task<Result<Navigated>> {
14785 let Some(provider) = self.semantics_provider.clone() else {
14786 return Task::ready(Ok(Navigated::No));
14787 };
14788 let head = self.selections.newest::<usize>(cx).head();
14789 let buffer = self.buffer.read(cx);
14790 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14791 text_anchor
14792 } else {
14793 return Task::ready(Ok(Navigated::No));
14794 };
14795
14796 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14797 return Task::ready(Ok(Navigated::No));
14798 };
14799
14800 cx.spawn_in(window, async move |editor, cx| {
14801 let definitions = definitions.await?;
14802 let navigated = editor
14803 .update_in(cx, |editor, window, cx| {
14804 editor.navigate_to_hover_links(
14805 Some(kind),
14806 definitions
14807 .into_iter()
14808 .filter(|location| {
14809 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14810 })
14811 .map(HoverLink::Text)
14812 .collect::<Vec<_>>(),
14813 split,
14814 window,
14815 cx,
14816 )
14817 })?
14818 .await?;
14819 anyhow::Ok(navigated)
14820 })
14821 }
14822
14823 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14824 let selection = self.selections.newest_anchor();
14825 let head = selection.head();
14826 let tail = selection.tail();
14827
14828 let Some((buffer, start_position)) =
14829 self.buffer.read(cx).text_anchor_for_position(head, cx)
14830 else {
14831 return;
14832 };
14833
14834 let end_position = if head != tail {
14835 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14836 return;
14837 };
14838 Some(pos)
14839 } else {
14840 None
14841 };
14842
14843 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14844 let url = if let Some(end_pos) = end_position {
14845 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14846 } else {
14847 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14848 };
14849
14850 if let Some(url) = url {
14851 editor.update(cx, |_, cx| {
14852 cx.open_url(&url);
14853 })
14854 } else {
14855 Ok(())
14856 }
14857 });
14858
14859 url_finder.detach();
14860 }
14861
14862 pub fn open_selected_filename(
14863 &mut self,
14864 _: &OpenSelectedFilename,
14865 window: &mut Window,
14866 cx: &mut Context<Self>,
14867 ) {
14868 let Some(workspace) = self.workspace() else {
14869 return;
14870 };
14871
14872 let position = self.selections.newest_anchor().head();
14873
14874 let Some((buffer, buffer_position)) =
14875 self.buffer.read(cx).text_anchor_for_position(position, cx)
14876 else {
14877 return;
14878 };
14879
14880 let project = self.project.clone();
14881
14882 cx.spawn_in(window, async move |_, cx| {
14883 let result = find_file(&buffer, project, buffer_position, cx).await;
14884
14885 if let Some((_, path)) = result {
14886 workspace
14887 .update_in(cx, |workspace, window, cx| {
14888 workspace.open_resolved_path(path, window, cx)
14889 })?
14890 .await?;
14891 }
14892 anyhow::Ok(())
14893 })
14894 .detach();
14895 }
14896
14897 pub(crate) fn navigate_to_hover_links(
14898 &mut self,
14899 kind: Option<GotoDefinitionKind>,
14900 mut definitions: Vec<HoverLink>,
14901 split: bool,
14902 window: &mut Window,
14903 cx: &mut Context<Editor>,
14904 ) -> Task<Result<Navigated>> {
14905 // If there is one definition, just open it directly
14906 if definitions.len() == 1 {
14907 let definition = definitions.pop().unwrap();
14908
14909 enum TargetTaskResult {
14910 Location(Option<Location>),
14911 AlreadyNavigated,
14912 }
14913
14914 let target_task = match definition {
14915 HoverLink::Text(link) => {
14916 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14917 }
14918 HoverLink::InlayHint(lsp_location, server_id) => {
14919 let computation =
14920 self.compute_target_location(lsp_location, server_id, window, cx);
14921 cx.background_spawn(async move {
14922 let location = computation.await?;
14923 Ok(TargetTaskResult::Location(location))
14924 })
14925 }
14926 HoverLink::Url(url) => {
14927 cx.open_url(&url);
14928 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14929 }
14930 HoverLink::File(path) => {
14931 if let Some(workspace) = self.workspace() {
14932 cx.spawn_in(window, async move |_, cx| {
14933 workspace
14934 .update_in(cx, |workspace, window, cx| {
14935 workspace.open_resolved_path(path, window, cx)
14936 })?
14937 .await
14938 .map(|_| TargetTaskResult::AlreadyNavigated)
14939 })
14940 } else {
14941 Task::ready(Ok(TargetTaskResult::Location(None)))
14942 }
14943 }
14944 };
14945 cx.spawn_in(window, async move |editor, cx| {
14946 let target = match target_task.await.context("target resolution task")? {
14947 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14948 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14949 TargetTaskResult::Location(Some(target)) => target,
14950 };
14951
14952 editor.update_in(cx, |editor, window, cx| {
14953 let Some(workspace) = editor.workspace() else {
14954 return Navigated::No;
14955 };
14956 let pane = workspace.read(cx).active_pane().clone();
14957
14958 let range = target.range.to_point(target.buffer.read(cx));
14959 let range = editor.range_for_match(&range);
14960 let range = collapse_multiline_range(range);
14961
14962 if !split
14963 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14964 {
14965 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14966 } else {
14967 window.defer(cx, move |window, cx| {
14968 let target_editor: Entity<Self> =
14969 workspace.update(cx, |workspace, cx| {
14970 let pane = if split {
14971 workspace.adjacent_pane(window, cx)
14972 } else {
14973 workspace.active_pane().clone()
14974 };
14975
14976 workspace.open_project_item(
14977 pane,
14978 target.buffer.clone(),
14979 true,
14980 true,
14981 window,
14982 cx,
14983 )
14984 });
14985 target_editor.update(cx, |target_editor, cx| {
14986 // When selecting a definition in a different buffer, disable the nav history
14987 // to avoid creating a history entry at the previous cursor location.
14988 pane.update(cx, |pane, _| pane.disable_history());
14989 target_editor.go_to_singleton_buffer_range(range, window, cx);
14990 pane.update(cx, |pane, _| pane.enable_history());
14991 });
14992 });
14993 }
14994 Navigated::Yes
14995 })
14996 })
14997 } else if !definitions.is_empty() {
14998 cx.spawn_in(window, async move |editor, cx| {
14999 let (title, location_tasks, workspace) = editor
15000 .update_in(cx, |editor, window, cx| {
15001 let tab_kind = match kind {
15002 Some(GotoDefinitionKind::Implementation) => "Implementations",
15003 _ => "Definitions",
15004 };
15005 let title = definitions
15006 .iter()
15007 .find_map(|definition| match definition {
15008 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15009 let buffer = origin.buffer.read(cx);
15010 format!(
15011 "{} for {}",
15012 tab_kind,
15013 buffer
15014 .text_for_range(origin.range.clone())
15015 .collect::<String>()
15016 )
15017 }),
15018 HoverLink::InlayHint(_, _) => None,
15019 HoverLink::Url(_) => None,
15020 HoverLink::File(_) => None,
15021 })
15022 .unwrap_or(tab_kind.to_string());
15023 let location_tasks = definitions
15024 .into_iter()
15025 .map(|definition| match definition {
15026 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15027 HoverLink::InlayHint(lsp_location, server_id) => editor
15028 .compute_target_location(lsp_location, server_id, window, cx),
15029 HoverLink::Url(_) => Task::ready(Ok(None)),
15030 HoverLink::File(_) => Task::ready(Ok(None)),
15031 })
15032 .collect::<Vec<_>>();
15033 (title, location_tasks, editor.workspace().clone())
15034 })
15035 .context("location tasks preparation")?;
15036
15037 let locations = future::join_all(location_tasks)
15038 .await
15039 .into_iter()
15040 .filter_map(|location| location.transpose())
15041 .collect::<Result<_>>()
15042 .context("location tasks")?;
15043
15044 let Some(workspace) = workspace else {
15045 return Ok(Navigated::No);
15046 };
15047 let opened = workspace
15048 .update_in(cx, |workspace, window, cx| {
15049 Self::open_locations_in_multibuffer(
15050 workspace,
15051 locations,
15052 title,
15053 split,
15054 MultibufferSelectionMode::First,
15055 window,
15056 cx,
15057 )
15058 })
15059 .ok();
15060
15061 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15062 })
15063 } else {
15064 Task::ready(Ok(Navigated::No))
15065 }
15066 }
15067
15068 fn compute_target_location(
15069 &self,
15070 lsp_location: lsp::Location,
15071 server_id: LanguageServerId,
15072 window: &mut Window,
15073 cx: &mut Context<Self>,
15074 ) -> Task<anyhow::Result<Option<Location>>> {
15075 let Some(project) = self.project.clone() else {
15076 return Task::ready(Ok(None));
15077 };
15078
15079 cx.spawn_in(window, async move |editor, cx| {
15080 let location_task = editor.update(cx, |_, cx| {
15081 project.update(cx, |project, cx| {
15082 let language_server_name = project
15083 .language_server_statuses(cx)
15084 .find(|(id, _)| server_id == *id)
15085 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15086 language_server_name.map(|language_server_name| {
15087 project.open_local_buffer_via_lsp(
15088 lsp_location.uri.clone(),
15089 server_id,
15090 language_server_name,
15091 cx,
15092 )
15093 })
15094 })
15095 })?;
15096 let location = match location_task {
15097 Some(task) => Some({
15098 let target_buffer_handle = task.await.context("open local buffer")?;
15099 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15100 let target_start = target_buffer
15101 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15102 let target_end = target_buffer
15103 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15104 target_buffer.anchor_after(target_start)
15105 ..target_buffer.anchor_before(target_end)
15106 })?;
15107 Location {
15108 buffer: target_buffer_handle,
15109 range,
15110 }
15111 }),
15112 None => None,
15113 };
15114 Ok(location)
15115 })
15116 }
15117
15118 pub fn find_all_references(
15119 &mut self,
15120 _: &FindAllReferences,
15121 window: &mut Window,
15122 cx: &mut Context<Self>,
15123 ) -> Option<Task<Result<Navigated>>> {
15124 let selection = self.selections.newest::<usize>(cx);
15125 let multi_buffer = self.buffer.read(cx);
15126 let head = selection.head();
15127
15128 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15129 let head_anchor = multi_buffer_snapshot.anchor_at(
15130 head,
15131 if head < selection.tail() {
15132 Bias::Right
15133 } else {
15134 Bias::Left
15135 },
15136 );
15137
15138 match self
15139 .find_all_references_task_sources
15140 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15141 {
15142 Ok(_) => {
15143 log::info!(
15144 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15145 );
15146 return None;
15147 }
15148 Err(i) => {
15149 self.find_all_references_task_sources.insert(i, head_anchor);
15150 }
15151 }
15152
15153 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15154 let workspace = self.workspace()?;
15155 let project = workspace.read(cx).project().clone();
15156 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15157 Some(cx.spawn_in(window, async move |editor, cx| {
15158 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15159 if let Ok(i) = editor
15160 .find_all_references_task_sources
15161 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15162 {
15163 editor.find_all_references_task_sources.remove(i);
15164 }
15165 });
15166
15167 let locations = references.await?;
15168 if locations.is_empty() {
15169 return anyhow::Ok(Navigated::No);
15170 }
15171
15172 workspace.update_in(cx, |workspace, window, cx| {
15173 let title = locations
15174 .first()
15175 .as_ref()
15176 .map(|location| {
15177 let buffer = location.buffer.read(cx);
15178 format!(
15179 "References to `{}`",
15180 buffer
15181 .text_for_range(location.range.clone())
15182 .collect::<String>()
15183 )
15184 })
15185 .unwrap();
15186 Self::open_locations_in_multibuffer(
15187 workspace,
15188 locations,
15189 title,
15190 false,
15191 MultibufferSelectionMode::First,
15192 window,
15193 cx,
15194 );
15195 Navigated::Yes
15196 })
15197 }))
15198 }
15199
15200 /// Opens a multibuffer with the given project locations in it
15201 pub fn open_locations_in_multibuffer(
15202 workspace: &mut Workspace,
15203 mut locations: Vec<Location>,
15204 title: String,
15205 split: bool,
15206 multibuffer_selection_mode: MultibufferSelectionMode,
15207 window: &mut Window,
15208 cx: &mut Context<Workspace>,
15209 ) {
15210 // If there are multiple definitions, open them in a multibuffer
15211 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15212 let mut locations = locations.into_iter().peekable();
15213 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15214 let capability = workspace.project().read(cx).capability();
15215
15216 let excerpt_buffer = cx.new(|cx| {
15217 let mut multibuffer = MultiBuffer::new(capability);
15218 while let Some(location) = locations.next() {
15219 let buffer = location.buffer.read(cx);
15220 let mut ranges_for_buffer = Vec::new();
15221 let range = location.range.to_point(buffer);
15222 ranges_for_buffer.push(range.clone());
15223
15224 while let Some(next_location) = locations.peek() {
15225 if next_location.buffer == location.buffer {
15226 ranges_for_buffer.push(next_location.range.to_point(buffer));
15227 locations.next();
15228 } else {
15229 break;
15230 }
15231 }
15232
15233 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15234 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15235 PathKey::for_buffer(&location.buffer, cx),
15236 location.buffer.clone(),
15237 ranges_for_buffer,
15238 DEFAULT_MULTIBUFFER_CONTEXT,
15239 cx,
15240 );
15241 ranges.extend(new_ranges)
15242 }
15243
15244 multibuffer.with_title(title)
15245 });
15246
15247 let editor = cx.new(|cx| {
15248 Editor::for_multibuffer(
15249 excerpt_buffer,
15250 Some(workspace.project().clone()),
15251 window,
15252 cx,
15253 )
15254 });
15255 editor.update(cx, |editor, cx| {
15256 match multibuffer_selection_mode {
15257 MultibufferSelectionMode::First => {
15258 if let Some(first_range) = ranges.first() {
15259 editor.change_selections(None, window, cx, |selections| {
15260 selections.clear_disjoint();
15261 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15262 });
15263 }
15264 editor.highlight_background::<Self>(
15265 &ranges,
15266 |theme| theme.editor_highlighted_line_background,
15267 cx,
15268 );
15269 }
15270 MultibufferSelectionMode::All => {
15271 editor.change_selections(None, window, cx, |selections| {
15272 selections.clear_disjoint();
15273 selections.select_anchor_ranges(ranges);
15274 });
15275 }
15276 }
15277 editor.register_buffers_with_language_servers(cx);
15278 });
15279
15280 let item = Box::new(editor);
15281 let item_id = item.item_id();
15282
15283 if split {
15284 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15285 } else {
15286 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15287 let (preview_item_id, preview_item_idx) =
15288 workspace.active_pane().read_with(cx, |pane, _| {
15289 (pane.preview_item_id(), pane.preview_item_idx())
15290 });
15291
15292 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15293
15294 if let Some(preview_item_id) = preview_item_id {
15295 workspace.active_pane().update(cx, |pane, cx| {
15296 pane.remove_item(preview_item_id, false, false, window, cx);
15297 });
15298 }
15299 } else {
15300 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15301 }
15302 }
15303 workspace.active_pane().update(cx, |pane, cx| {
15304 pane.set_preview_item_id(Some(item_id), cx);
15305 });
15306 }
15307
15308 pub fn rename(
15309 &mut self,
15310 _: &Rename,
15311 window: &mut Window,
15312 cx: &mut Context<Self>,
15313 ) -> Option<Task<Result<()>>> {
15314 use language::ToOffset as _;
15315
15316 let provider = self.semantics_provider.clone()?;
15317 let selection = self.selections.newest_anchor().clone();
15318 let (cursor_buffer, cursor_buffer_position) = self
15319 .buffer
15320 .read(cx)
15321 .text_anchor_for_position(selection.head(), cx)?;
15322 let (tail_buffer, cursor_buffer_position_end) = self
15323 .buffer
15324 .read(cx)
15325 .text_anchor_for_position(selection.tail(), cx)?;
15326 if tail_buffer != cursor_buffer {
15327 return None;
15328 }
15329
15330 let snapshot = cursor_buffer.read(cx).snapshot();
15331 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15332 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15333 let prepare_rename = provider
15334 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15335 .unwrap_or_else(|| Task::ready(Ok(None)));
15336 drop(snapshot);
15337
15338 Some(cx.spawn_in(window, async move |this, cx| {
15339 let rename_range = if let Some(range) = prepare_rename.await? {
15340 Some(range)
15341 } else {
15342 this.update(cx, |this, cx| {
15343 let buffer = this.buffer.read(cx).snapshot(cx);
15344 let mut buffer_highlights = this
15345 .document_highlights_for_position(selection.head(), &buffer)
15346 .filter(|highlight| {
15347 highlight.start.excerpt_id == selection.head().excerpt_id
15348 && highlight.end.excerpt_id == selection.head().excerpt_id
15349 });
15350 buffer_highlights
15351 .next()
15352 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15353 })?
15354 };
15355 if let Some(rename_range) = rename_range {
15356 this.update_in(cx, |this, window, cx| {
15357 let snapshot = cursor_buffer.read(cx).snapshot();
15358 let rename_buffer_range = rename_range.to_offset(&snapshot);
15359 let cursor_offset_in_rename_range =
15360 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15361 let cursor_offset_in_rename_range_end =
15362 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15363
15364 this.take_rename(false, window, cx);
15365 let buffer = this.buffer.read(cx).read(cx);
15366 let cursor_offset = selection.head().to_offset(&buffer);
15367 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15368 let rename_end = rename_start + rename_buffer_range.len();
15369 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15370 let mut old_highlight_id = None;
15371 let old_name: Arc<str> = buffer
15372 .chunks(rename_start..rename_end, true)
15373 .map(|chunk| {
15374 if old_highlight_id.is_none() {
15375 old_highlight_id = chunk.syntax_highlight_id;
15376 }
15377 chunk.text
15378 })
15379 .collect::<String>()
15380 .into();
15381
15382 drop(buffer);
15383
15384 // Position the selection in the rename editor so that it matches the current selection.
15385 this.show_local_selections = false;
15386 let rename_editor = cx.new(|cx| {
15387 let mut editor = Editor::single_line(window, cx);
15388 editor.buffer.update(cx, |buffer, cx| {
15389 buffer.edit([(0..0, old_name.clone())], None, cx)
15390 });
15391 let rename_selection_range = match cursor_offset_in_rename_range
15392 .cmp(&cursor_offset_in_rename_range_end)
15393 {
15394 Ordering::Equal => {
15395 editor.select_all(&SelectAll, window, cx);
15396 return editor;
15397 }
15398 Ordering::Less => {
15399 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15400 }
15401 Ordering::Greater => {
15402 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15403 }
15404 };
15405 if rename_selection_range.end > old_name.len() {
15406 editor.select_all(&SelectAll, window, cx);
15407 } else {
15408 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15409 s.select_ranges([rename_selection_range]);
15410 });
15411 }
15412 editor
15413 });
15414 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15415 if e == &EditorEvent::Focused {
15416 cx.emit(EditorEvent::FocusedIn)
15417 }
15418 })
15419 .detach();
15420
15421 let write_highlights =
15422 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15423 let read_highlights =
15424 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15425 let ranges = write_highlights
15426 .iter()
15427 .flat_map(|(_, ranges)| ranges.iter())
15428 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15429 .cloned()
15430 .collect();
15431
15432 this.highlight_text::<Rename>(
15433 ranges,
15434 HighlightStyle {
15435 fade_out: Some(0.6),
15436 ..Default::default()
15437 },
15438 cx,
15439 );
15440 let rename_focus_handle = rename_editor.focus_handle(cx);
15441 window.focus(&rename_focus_handle);
15442 let block_id = this.insert_blocks(
15443 [BlockProperties {
15444 style: BlockStyle::Flex,
15445 placement: BlockPlacement::Below(range.start),
15446 height: Some(1),
15447 render: Arc::new({
15448 let rename_editor = rename_editor.clone();
15449 move |cx: &mut BlockContext| {
15450 let mut text_style = cx.editor_style.text.clone();
15451 if let Some(highlight_style) = old_highlight_id
15452 .and_then(|h| h.style(&cx.editor_style.syntax))
15453 {
15454 text_style = text_style.highlight(highlight_style);
15455 }
15456 div()
15457 .block_mouse_except_scroll()
15458 .pl(cx.anchor_x)
15459 .child(EditorElement::new(
15460 &rename_editor,
15461 EditorStyle {
15462 background: cx.theme().system().transparent,
15463 local_player: cx.editor_style.local_player,
15464 text: text_style,
15465 scrollbar_width: cx.editor_style.scrollbar_width,
15466 syntax: cx.editor_style.syntax.clone(),
15467 status: cx.editor_style.status.clone(),
15468 inlay_hints_style: HighlightStyle {
15469 font_weight: Some(FontWeight::BOLD),
15470 ..make_inlay_hints_style(cx.app)
15471 },
15472 inline_completion_styles: make_suggestion_styles(
15473 cx.app,
15474 ),
15475 ..EditorStyle::default()
15476 },
15477 ))
15478 .into_any_element()
15479 }
15480 }),
15481 priority: 0,
15482 render_in_minimap: true,
15483 }],
15484 Some(Autoscroll::fit()),
15485 cx,
15486 )[0];
15487 this.pending_rename = Some(RenameState {
15488 range,
15489 old_name,
15490 editor: rename_editor,
15491 block_id,
15492 });
15493 })?;
15494 }
15495
15496 Ok(())
15497 }))
15498 }
15499
15500 pub fn confirm_rename(
15501 &mut self,
15502 _: &ConfirmRename,
15503 window: &mut Window,
15504 cx: &mut Context<Self>,
15505 ) -> Option<Task<Result<()>>> {
15506 let rename = self.take_rename(false, window, cx)?;
15507 let workspace = self.workspace()?.downgrade();
15508 let (buffer, start) = self
15509 .buffer
15510 .read(cx)
15511 .text_anchor_for_position(rename.range.start, cx)?;
15512 let (end_buffer, _) = self
15513 .buffer
15514 .read(cx)
15515 .text_anchor_for_position(rename.range.end, cx)?;
15516 if buffer != end_buffer {
15517 return None;
15518 }
15519
15520 let old_name = rename.old_name;
15521 let new_name = rename.editor.read(cx).text(cx);
15522
15523 let rename = self.semantics_provider.as_ref()?.perform_rename(
15524 &buffer,
15525 start,
15526 new_name.clone(),
15527 cx,
15528 )?;
15529
15530 Some(cx.spawn_in(window, async move |editor, cx| {
15531 let project_transaction = rename.await?;
15532 Self::open_project_transaction(
15533 &editor,
15534 workspace,
15535 project_transaction,
15536 format!("Rename: {} → {}", old_name, new_name),
15537 cx,
15538 )
15539 .await?;
15540
15541 editor.update(cx, |editor, cx| {
15542 editor.refresh_document_highlights(cx);
15543 })?;
15544 Ok(())
15545 }))
15546 }
15547
15548 fn take_rename(
15549 &mut self,
15550 moving_cursor: bool,
15551 window: &mut Window,
15552 cx: &mut Context<Self>,
15553 ) -> Option<RenameState> {
15554 let rename = self.pending_rename.take()?;
15555 if rename.editor.focus_handle(cx).is_focused(window) {
15556 window.focus(&self.focus_handle);
15557 }
15558
15559 self.remove_blocks(
15560 [rename.block_id].into_iter().collect(),
15561 Some(Autoscroll::fit()),
15562 cx,
15563 );
15564 self.clear_highlights::<Rename>(cx);
15565 self.show_local_selections = true;
15566
15567 if moving_cursor {
15568 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15569 editor.selections.newest::<usize>(cx).head()
15570 });
15571
15572 // Update the selection to match the position of the selection inside
15573 // the rename editor.
15574 let snapshot = self.buffer.read(cx).read(cx);
15575 let rename_range = rename.range.to_offset(&snapshot);
15576 let cursor_in_editor = snapshot
15577 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15578 .min(rename_range.end);
15579 drop(snapshot);
15580
15581 self.change_selections(None, window, cx, |s| {
15582 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15583 });
15584 } else {
15585 self.refresh_document_highlights(cx);
15586 }
15587
15588 Some(rename)
15589 }
15590
15591 pub fn pending_rename(&self) -> Option<&RenameState> {
15592 self.pending_rename.as_ref()
15593 }
15594
15595 fn format(
15596 &mut self,
15597 _: &Format,
15598 window: &mut Window,
15599 cx: &mut Context<Self>,
15600 ) -> Option<Task<Result<()>>> {
15601 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15602
15603 let project = match &self.project {
15604 Some(project) => project.clone(),
15605 None => return None,
15606 };
15607
15608 Some(self.perform_format(
15609 project,
15610 FormatTrigger::Manual,
15611 FormatTarget::Buffers,
15612 window,
15613 cx,
15614 ))
15615 }
15616
15617 fn format_selections(
15618 &mut self,
15619 _: &FormatSelections,
15620 window: &mut Window,
15621 cx: &mut Context<Self>,
15622 ) -> Option<Task<Result<()>>> {
15623 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15624
15625 let project = match &self.project {
15626 Some(project) => project.clone(),
15627 None => return None,
15628 };
15629
15630 let ranges = self
15631 .selections
15632 .all_adjusted(cx)
15633 .into_iter()
15634 .map(|selection| selection.range())
15635 .collect_vec();
15636
15637 Some(self.perform_format(
15638 project,
15639 FormatTrigger::Manual,
15640 FormatTarget::Ranges(ranges),
15641 window,
15642 cx,
15643 ))
15644 }
15645
15646 fn perform_format(
15647 &mut self,
15648 project: Entity<Project>,
15649 trigger: FormatTrigger,
15650 target: FormatTarget,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) -> Task<Result<()>> {
15654 let buffer = self.buffer.clone();
15655 let (buffers, target) = match target {
15656 FormatTarget::Buffers => {
15657 let mut buffers = buffer.read(cx).all_buffers();
15658 if trigger == FormatTrigger::Save {
15659 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15660 }
15661 (buffers, LspFormatTarget::Buffers)
15662 }
15663 FormatTarget::Ranges(selection_ranges) => {
15664 let multi_buffer = buffer.read(cx);
15665 let snapshot = multi_buffer.read(cx);
15666 let mut buffers = HashSet::default();
15667 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15668 BTreeMap::new();
15669 for selection_range in selection_ranges {
15670 for (buffer, buffer_range, _) in
15671 snapshot.range_to_buffer_ranges(selection_range)
15672 {
15673 let buffer_id = buffer.remote_id();
15674 let start = buffer.anchor_before(buffer_range.start);
15675 let end = buffer.anchor_after(buffer_range.end);
15676 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15677 buffer_id_to_ranges
15678 .entry(buffer_id)
15679 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15680 .or_insert_with(|| vec![start..end]);
15681 }
15682 }
15683 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15684 }
15685 };
15686
15687 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15688 let selections_prev = transaction_id_prev
15689 .and_then(|transaction_id_prev| {
15690 // default to selections as they were after the last edit, if we have them,
15691 // instead of how they are now.
15692 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15693 // will take you back to where you made the last edit, instead of staying where you scrolled
15694 self.selection_history
15695 .transaction(transaction_id_prev)
15696 .map(|t| t.0.clone())
15697 })
15698 .unwrap_or_else(|| {
15699 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15700 self.selections.disjoint_anchors()
15701 });
15702
15703 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15704 let format = project.update(cx, |project, cx| {
15705 project.format(buffers, target, true, trigger, cx)
15706 });
15707
15708 cx.spawn_in(window, async move |editor, cx| {
15709 let transaction = futures::select_biased! {
15710 transaction = format.log_err().fuse() => transaction,
15711 () = timeout => {
15712 log::warn!("timed out waiting for formatting");
15713 None
15714 }
15715 };
15716
15717 buffer
15718 .update(cx, |buffer, cx| {
15719 if let Some(transaction) = transaction {
15720 if !buffer.is_singleton() {
15721 buffer.push_transaction(&transaction.0, cx);
15722 }
15723 }
15724 cx.notify();
15725 })
15726 .ok();
15727
15728 if let Some(transaction_id_now) =
15729 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15730 {
15731 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15732 if has_new_transaction {
15733 _ = editor.update(cx, |editor, _| {
15734 editor
15735 .selection_history
15736 .insert_transaction(transaction_id_now, selections_prev);
15737 });
15738 }
15739 }
15740
15741 Ok(())
15742 })
15743 }
15744
15745 fn organize_imports(
15746 &mut self,
15747 _: &OrganizeImports,
15748 window: &mut Window,
15749 cx: &mut Context<Self>,
15750 ) -> Option<Task<Result<()>>> {
15751 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15752 let project = match &self.project {
15753 Some(project) => project.clone(),
15754 None => return None,
15755 };
15756 Some(self.perform_code_action_kind(
15757 project,
15758 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15759 window,
15760 cx,
15761 ))
15762 }
15763
15764 fn perform_code_action_kind(
15765 &mut self,
15766 project: Entity<Project>,
15767 kind: CodeActionKind,
15768 window: &mut Window,
15769 cx: &mut Context<Self>,
15770 ) -> Task<Result<()>> {
15771 let buffer = self.buffer.clone();
15772 let buffers = buffer.read(cx).all_buffers();
15773 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15774 let apply_action = project.update(cx, |project, cx| {
15775 project.apply_code_action_kind(buffers, kind, true, cx)
15776 });
15777 cx.spawn_in(window, async move |_, cx| {
15778 let transaction = futures::select_biased! {
15779 () = timeout => {
15780 log::warn!("timed out waiting for executing code action");
15781 None
15782 }
15783 transaction = apply_action.log_err().fuse() => transaction,
15784 };
15785 buffer
15786 .update(cx, |buffer, cx| {
15787 // check if we need this
15788 if let Some(transaction) = transaction {
15789 if !buffer.is_singleton() {
15790 buffer.push_transaction(&transaction.0, cx);
15791 }
15792 }
15793 cx.notify();
15794 })
15795 .ok();
15796 Ok(())
15797 })
15798 }
15799
15800 fn restart_language_server(
15801 &mut self,
15802 _: &RestartLanguageServer,
15803 _: &mut Window,
15804 cx: &mut Context<Self>,
15805 ) {
15806 if let Some(project) = self.project.clone() {
15807 self.buffer.update(cx, |multi_buffer, cx| {
15808 project.update(cx, |project, cx| {
15809 project.restart_language_servers_for_buffers(
15810 multi_buffer.all_buffers().into_iter().collect(),
15811 cx,
15812 );
15813 });
15814 })
15815 }
15816 }
15817
15818 fn stop_language_server(
15819 &mut self,
15820 _: &StopLanguageServer,
15821 _: &mut Window,
15822 cx: &mut Context<Self>,
15823 ) {
15824 if let Some(project) = self.project.clone() {
15825 self.buffer.update(cx, |multi_buffer, cx| {
15826 project.update(cx, |project, cx| {
15827 project.stop_language_servers_for_buffers(
15828 multi_buffer.all_buffers().into_iter().collect(),
15829 cx,
15830 );
15831 cx.emit(project::Event::RefreshInlayHints);
15832 });
15833 });
15834 }
15835 }
15836
15837 fn cancel_language_server_work(
15838 workspace: &mut Workspace,
15839 _: &actions::CancelLanguageServerWork,
15840 _: &mut Window,
15841 cx: &mut Context<Workspace>,
15842 ) {
15843 let project = workspace.project();
15844 let buffers = workspace
15845 .active_item(cx)
15846 .and_then(|item| item.act_as::<Editor>(cx))
15847 .map_or(HashSet::default(), |editor| {
15848 editor.read(cx).buffer.read(cx).all_buffers()
15849 });
15850 project.update(cx, |project, cx| {
15851 project.cancel_language_server_work_for_buffers(buffers, cx);
15852 });
15853 }
15854
15855 fn show_character_palette(
15856 &mut self,
15857 _: &ShowCharacterPalette,
15858 window: &mut Window,
15859 _: &mut Context<Self>,
15860 ) {
15861 window.show_character_palette();
15862 }
15863
15864 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15865 if self.mode.is_minimap() {
15866 return;
15867 }
15868
15869 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15870 let buffer = self.buffer.read(cx).snapshot(cx);
15871 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15872 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15873 let is_valid = buffer
15874 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15875 .any(|entry| {
15876 entry.diagnostic.is_primary
15877 && !entry.range.is_empty()
15878 && entry.range.start == primary_range_start
15879 && entry.diagnostic.message == active_diagnostics.active_message
15880 });
15881
15882 if !is_valid {
15883 self.dismiss_diagnostics(cx);
15884 }
15885 }
15886 }
15887
15888 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15889 match &self.active_diagnostics {
15890 ActiveDiagnostic::Group(group) => Some(group),
15891 _ => None,
15892 }
15893 }
15894
15895 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15896 self.dismiss_diagnostics(cx);
15897 self.active_diagnostics = ActiveDiagnostic::All;
15898 }
15899
15900 fn activate_diagnostics(
15901 &mut self,
15902 buffer_id: BufferId,
15903 diagnostic: DiagnosticEntry<usize>,
15904 window: &mut Window,
15905 cx: &mut Context<Self>,
15906 ) {
15907 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15908 return;
15909 }
15910 self.dismiss_diagnostics(cx);
15911 let snapshot = self.snapshot(window, cx);
15912 let buffer = self.buffer.read(cx).snapshot(cx);
15913 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15914 return;
15915 };
15916
15917 let diagnostic_group = buffer
15918 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15919 .collect::<Vec<_>>();
15920
15921 let blocks =
15922 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15923
15924 let blocks = self.display_map.update(cx, |display_map, cx| {
15925 display_map.insert_blocks(blocks, cx).into_iter().collect()
15926 });
15927 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15928 active_range: buffer.anchor_before(diagnostic.range.start)
15929 ..buffer.anchor_after(diagnostic.range.end),
15930 active_message: diagnostic.diagnostic.message.clone(),
15931 group_id: diagnostic.diagnostic.group_id,
15932 blocks,
15933 });
15934 cx.notify();
15935 }
15936
15937 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15938 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15939 return;
15940 };
15941
15942 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15943 if let ActiveDiagnostic::Group(group) = prev {
15944 self.display_map.update(cx, |display_map, cx| {
15945 display_map.remove_blocks(group.blocks, cx);
15946 });
15947 cx.notify();
15948 }
15949 }
15950
15951 /// Disable inline diagnostics rendering for this editor.
15952 pub fn disable_inline_diagnostics(&mut self) {
15953 self.inline_diagnostics_enabled = false;
15954 self.inline_diagnostics_update = Task::ready(());
15955 self.inline_diagnostics.clear();
15956 }
15957
15958 pub fn diagnostics_enabled(&self) -> bool {
15959 self.mode.is_full()
15960 }
15961
15962 pub fn inline_diagnostics_enabled(&self) -> bool {
15963 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15964 }
15965
15966 pub fn show_inline_diagnostics(&self) -> bool {
15967 self.show_inline_diagnostics
15968 }
15969
15970 pub fn toggle_inline_diagnostics(
15971 &mut self,
15972 _: &ToggleInlineDiagnostics,
15973 window: &mut Window,
15974 cx: &mut Context<Editor>,
15975 ) {
15976 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15977 self.refresh_inline_diagnostics(false, window, cx);
15978 }
15979
15980 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15981 self.diagnostics_max_severity = severity;
15982 self.display_map.update(cx, |display_map, _| {
15983 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15984 });
15985 }
15986
15987 pub fn toggle_diagnostics(
15988 &mut self,
15989 _: &ToggleDiagnostics,
15990 window: &mut Window,
15991 cx: &mut Context<Editor>,
15992 ) {
15993 if !self.diagnostics_enabled() {
15994 return;
15995 }
15996
15997 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15998 EditorSettings::get_global(cx)
15999 .diagnostics_max_severity
16000 .filter(|severity| severity != &DiagnosticSeverity::Off)
16001 .unwrap_or(DiagnosticSeverity::Hint)
16002 } else {
16003 DiagnosticSeverity::Off
16004 };
16005 self.set_max_diagnostics_severity(new_severity, cx);
16006 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16007 self.active_diagnostics = ActiveDiagnostic::None;
16008 self.inline_diagnostics_update = Task::ready(());
16009 self.inline_diagnostics.clear();
16010 } else {
16011 self.refresh_inline_diagnostics(false, window, cx);
16012 }
16013
16014 cx.notify();
16015 }
16016
16017 pub fn toggle_minimap(
16018 &mut self,
16019 _: &ToggleMinimap,
16020 window: &mut Window,
16021 cx: &mut Context<Editor>,
16022 ) {
16023 if self.supports_minimap(cx) {
16024 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16025 }
16026 }
16027
16028 fn refresh_inline_diagnostics(
16029 &mut self,
16030 debounce: bool,
16031 window: &mut Window,
16032 cx: &mut Context<Self>,
16033 ) {
16034 let max_severity = ProjectSettings::get_global(cx)
16035 .diagnostics
16036 .inline
16037 .max_severity
16038 .unwrap_or(self.diagnostics_max_severity);
16039
16040 if !self.inline_diagnostics_enabled()
16041 || !self.show_inline_diagnostics
16042 || max_severity == DiagnosticSeverity::Off
16043 {
16044 self.inline_diagnostics_update = Task::ready(());
16045 self.inline_diagnostics.clear();
16046 return;
16047 }
16048
16049 let debounce_ms = ProjectSettings::get_global(cx)
16050 .diagnostics
16051 .inline
16052 .update_debounce_ms;
16053 let debounce = if debounce && debounce_ms > 0 {
16054 Some(Duration::from_millis(debounce_ms))
16055 } else {
16056 None
16057 };
16058 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16059 if let Some(debounce) = debounce {
16060 cx.background_executor().timer(debounce).await;
16061 }
16062 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16063 editor
16064 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16065 .ok()
16066 }) else {
16067 return;
16068 };
16069
16070 let new_inline_diagnostics = cx
16071 .background_spawn(async move {
16072 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16073 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16074 let message = diagnostic_entry
16075 .diagnostic
16076 .message
16077 .split_once('\n')
16078 .map(|(line, _)| line)
16079 .map(SharedString::new)
16080 .unwrap_or_else(|| {
16081 SharedString::from(diagnostic_entry.diagnostic.message)
16082 });
16083 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16084 let (Ok(i) | Err(i)) = inline_diagnostics
16085 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16086 inline_diagnostics.insert(
16087 i,
16088 (
16089 start_anchor,
16090 InlineDiagnostic {
16091 message,
16092 group_id: diagnostic_entry.diagnostic.group_id,
16093 start: diagnostic_entry.range.start.to_point(&snapshot),
16094 is_primary: diagnostic_entry.diagnostic.is_primary,
16095 severity: diagnostic_entry.diagnostic.severity,
16096 },
16097 ),
16098 );
16099 }
16100 inline_diagnostics
16101 })
16102 .await;
16103
16104 editor
16105 .update(cx, |editor, cx| {
16106 editor.inline_diagnostics = new_inline_diagnostics;
16107 cx.notify();
16108 })
16109 .ok();
16110 });
16111 }
16112
16113 fn pull_diagnostics(
16114 &mut self,
16115 buffer_id: Option<BufferId>,
16116 window: &Window,
16117 cx: &mut Context<Self>,
16118 ) -> Option<()> {
16119 if !self.mode().is_full() {
16120 return None;
16121 }
16122 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16123 .diagnostics
16124 .lsp_pull_diagnostics;
16125 if !pull_diagnostics_settings.enabled {
16126 return None;
16127 }
16128 let project = self.project.as_ref()?.downgrade();
16129 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16130 let mut buffers = self.buffer.read(cx).all_buffers();
16131 if let Some(buffer_id) = buffer_id {
16132 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16133 }
16134
16135 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16136 cx.background_executor().timer(debounce).await;
16137
16138 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16139 buffers
16140 .into_iter()
16141 .flat_map(|buffer| {
16142 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16143 })
16144 .collect::<FuturesUnordered<_>>()
16145 }) else {
16146 return;
16147 };
16148
16149 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16150 match pull_task {
16151 Ok(()) => {
16152 if editor
16153 .update_in(cx, |editor, window, cx| {
16154 editor.update_diagnostics_state(window, cx);
16155 })
16156 .is_err()
16157 {
16158 return;
16159 }
16160 }
16161 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16162 }
16163 }
16164 });
16165
16166 Some(())
16167 }
16168
16169 pub fn set_selections_from_remote(
16170 &mut self,
16171 selections: Vec<Selection<Anchor>>,
16172 pending_selection: Option<Selection<Anchor>>,
16173 window: &mut Window,
16174 cx: &mut Context<Self>,
16175 ) {
16176 let old_cursor_position = self.selections.newest_anchor().head();
16177 self.selections.change_with(cx, |s| {
16178 s.select_anchors(selections);
16179 if let Some(pending_selection) = pending_selection {
16180 s.set_pending(pending_selection, SelectMode::Character);
16181 } else {
16182 s.clear_pending();
16183 }
16184 });
16185 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16186 }
16187
16188 pub fn transact(
16189 &mut self,
16190 window: &mut Window,
16191 cx: &mut Context<Self>,
16192 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16193 ) -> Option<TransactionId> {
16194 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16195 this.start_transaction_at(Instant::now(), window, cx);
16196 update(this, window, cx);
16197 this.end_transaction_at(Instant::now(), cx)
16198 })
16199 }
16200
16201 pub fn start_transaction_at(
16202 &mut self,
16203 now: Instant,
16204 window: &mut Window,
16205 cx: &mut Context<Self>,
16206 ) {
16207 self.end_selection(window, cx);
16208 if let Some(tx_id) = self
16209 .buffer
16210 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16211 {
16212 self.selection_history
16213 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16214 cx.emit(EditorEvent::TransactionBegun {
16215 transaction_id: tx_id,
16216 })
16217 }
16218 }
16219
16220 pub fn end_transaction_at(
16221 &mut self,
16222 now: Instant,
16223 cx: &mut Context<Self>,
16224 ) -> Option<TransactionId> {
16225 if let Some(transaction_id) = self
16226 .buffer
16227 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16228 {
16229 if let Some((_, end_selections)) =
16230 self.selection_history.transaction_mut(transaction_id)
16231 {
16232 *end_selections = Some(self.selections.disjoint_anchors());
16233 } else {
16234 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16235 }
16236
16237 cx.emit(EditorEvent::Edited { transaction_id });
16238 Some(transaction_id)
16239 } else {
16240 None
16241 }
16242 }
16243
16244 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16245 if self.selection_mark_mode {
16246 self.change_selections(None, window, cx, |s| {
16247 s.move_with(|_, sel| {
16248 sel.collapse_to(sel.head(), SelectionGoal::None);
16249 });
16250 })
16251 }
16252 self.selection_mark_mode = true;
16253 cx.notify();
16254 }
16255
16256 pub fn swap_selection_ends(
16257 &mut self,
16258 _: &actions::SwapSelectionEnds,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) {
16262 self.change_selections(None, window, cx, |s| {
16263 s.move_with(|_, sel| {
16264 if sel.start != sel.end {
16265 sel.reversed = !sel.reversed
16266 }
16267 });
16268 });
16269 self.request_autoscroll(Autoscroll::newest(), cx);
16270 cx.notify();
16271 }
16272
16273 pub fn toggle_fold(
16274 &mut self,
16275 _: &actions::ToggleFold,
16276 window: &mut Window,
16277 cx: &mut Context<Self>,
16278 ) {
16279 if self.is_singleton(cx) {
16280 let selection = self.selections.newest::<Point>(cx);
16281
16282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16283 let range = if selection.is_empty() {
16284 let point = selection.head().to_display_point(&display_map);
16285 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16286 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16287 .to_point(&display_map);
16288 start..end
16289 } else {
16290 selection.range()
16291 };
16292 if display_map.folds_in_range(range).next().is_some() {
16293 self.unfold_lines(&Default::default(), window, cx)
16294 } else {
16295 self.fold(&Default::default(), window, cx)
16296 }
16297 } else {
16298 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16299 let buffer_ids: HashSet<_> = self
16300 .selections
16301 .disjoint_anchor_ranges()
16302 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16303 .collect();
16304
16305 let should_unfold = buffer_ids
16306 .iter()
16307 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16308
16309 for buffer_id in buffer_ids {
16310 if should_unfold {
16311 self.unfold_buffer(buffer_id, cx);
16312 } else {
16313 self.fold_buffer(buffer_id, cx);
16314 }
16315 }
16316 }
16317 }
16318
16319 pub fn toggle_fold_recursive(
16320 &mut self,
16321 _: &actions::ToggleFoldRecursive,
16322 window: &mut Window,
16323 cx: &mut Context<Self>,
16324 ) {
16325 let selection = self.selections.newest::<Point>(cx);
16326
16327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16328 let range = if selection.is_empty() {
16329 let point = selection.head().to_display_point(&display_map);
16330 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16331 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16332 .to_point(&display_map);
16333 start..end
16334 } else {
16335 selection.range()
16336 };
16337 if display_map.folds_in_range(range).next().is_some() {
16338 self.unfold_recursive(&Default::default(), window, cx)
16339 } else {
16340 self.fold_recursive(&Default::default(), window, cx)
16341 }
16342 }
16343
16344 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16345 if self.is_singleton(cx) {
16346 let mut to_fold = Vec::new();
16347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16348 let selections = self.selections.all_adjusted(cx);
16349
16350 for selection in selections {
16351 let range = selection.range().sorted();
16352 let buffer_start_row = range.start.row;
16353
16354 if range.start.row != range.end.row {
16355 let mut found = false;
16356 let mut row = range.start.row;
16357 while row <= range.end.row {
16358 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16359 {
16360 found = true;
16361 row = crease.range().end.row + 1;
16362 to_fold.push(crease);
16363 } else {
16364 row += 1
16365 }
16366 }
16367 if found {
16368 continue;
16369 }
16370 }
16371
16372 for row in (0..=range.start.row).rev() {
16373 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16374 if crease.range().end.row >= buffer_start_row {
16375 to_fold.push(crease);
16376 if row <= range.start.row {
16377 break;
16378 }
16379 }
16380 }
16381 }
16382 }
16383
16384 self.fold_creases(to_fold, true, window, cx);
16385 } else {
16386 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16387 let buffer_ids = self
16388 .selections
16389 .disjoint_anchor_ranges()
16390 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16391 .collect::<HashSet<_>>();
16392 for buffer_id in buffer_ids {
16393 self.fold_buffer(buffer_id, cx);
16394 }
16395 }
16396 }
16397
16398 fn fold_at_level(
16399 &mut self,
16400 fold_at: &FoldAtLevel,
16401 window: &mut Window,
16402 cx: &mut Context<Self>,
16403 ) {
16404 if !self.buffer.read(cx).is_singleton() {
16405 return;
16406 }
16407
16408 let fold_at_level = fold_at.0;
16409 let snapshot = self.buffer.read(cx).snapshot(cx);
16410 let mut to_fold = Vec::new();
16411 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16412
16413 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16414 while start_row < end_row {
16415 match self
16416 .snapshot(window, cx)
16417 .crease_for_buffer_row(MultiBufferRow(start_row))
16418 {
16419 Some(crease) => {
16420 let nested_start_row = crease.range().start.row + 1;
16421 let nested_end_row = crease.range().end.row;
16422
16423 if current_level < fold_at_level {
16424 stack.push((nested_start_row, nested_end_row, current_level + 1));
16425 } else if current_level == fold_at_level {
16426 to_fold.push(crease);
16427 }
16428
16429 start_row = nested_end_row + 1;
16430 }
16431 None => start_row += 1,
16432 }
16433 }
16434 }
16435
16436 self.fold_creases(to_fold, true, window, cx);
16437 }
16438
16439 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16440 if self.buffer.read(cx).is_singleton() {
16441 let mut fold_ranges = Vec::new();
16442 let snapshot = self.buffer.read(cx).snapshot(cx);
16443
16444 for row in 0..snapshot.max_row().0 {
16445 if let Some(foldable_range) = self
16446 .snapshot(window, cx)
16447 .crease_for_buffer_row(MultiBufferRow(row))
16448 {
16449 fold_ranges.push(foldable_range);
16450 }
16451 }
16452
16453 self.fold_creases(fold_ranges, true, window, cx);
16454 } else {
16455 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16456 editor
16457 .update_in(cx, |editor, _, cx| {
16458 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16459 editor.fold_buffer(buffer_id, cx);
16460 }
16461 })
16462 .ok();
16463 });
16464 }
16465 }
16466
16467 pub fn fold_function_bodies(
16468 &mut self,
16469 _: &actions::FoldFunctionBodies,
16470 window: &mut Window,
16471 cx: &mut Context<Self>,
16472 ) {
16473 let snapshot = self.buffer.read(cx).snapshot(cx);
16474
16475 let ranges = snapshot
16476 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16477 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16478 .collect::<Vec<_>>();
16479
16480 let creases = ranges
16481 .into_iter()
16482 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16483 .collect();
16484
16485 self.fold_creases(creases, true, window, cx);
16486 }
16487
16488 pub fn fold_recursive(
16489 &mut self,
16490 _: &actions::FoldRecursive,
16491 window: &mut Window,
16492 cx: &mut Context<Self>,
16493 ) {
16494 let mut to_fold = Vec::new();
16495 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16496 let selections = self.selections.all_adjusted(cx);
16497
16498 for selection in selections {
16499 let range = selection.range().sorted();
16500 let buffer_start_row = range.start.row;
16501
16502 if range.start.row != range.end.row {
16503 let mut found = false;
16504 for row in range.start.row..=range.end.row {
16505 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16506 found = true;
16507 to_fold.push(crease);
16508 }
16509 }
16510 if found {
16511 continue;
16512 }
16513 }
16514
16515 for row in (0..=range.start.row).rev() {
16516 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16517 if crease.range().end.row >= buffer_start_row {
16518 to_fold.push(crease);
16519 } else {
16520 break;
16521 }
16522 }
16523 }
16524 }
16525
16526 self.fold_creases(to_fold, true, window, cx);
16527 }
16528
16529 pub fn fold_at(
16530 &mut self,
16531 buffer_row: MultiBufferRow,
16532 window: &mut Window,
16533 cx: &mut Context<Self>,
16534 ) {
16535 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16536
16537 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16538 let autoscroll = self
16539 .selections
16540 .all::<Point>(cx)
16541 .iter()
16542 .any(|selection| crease.range().overlaps(&selection.range()));
16543
16544 self.fold_creases(vec![crease], autoscroll, window, cx);
16545 }
16546 }
16547
16548 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16549 if self.is_singleton(cx) {
16550 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16551 let buffer = &display_map.buffer_snapshot;
16552 let selections = self.selections.all::<Point>(cx);
16553 let ranges = selections
16554 .iter()
16555 .map(|s| {
16556 let range = s.display_range(&display_map).sorted();
16557 let mut start = range.start.to_point(&display_map);
16558 let mut end = range.end.to_point(&display_map);
16559 start.column = 0;
16560 end.column = buffer.line_len(MultiBufferRow(end.row));
16561 start..end
16562 })
16563 .collect::<Vec<_>>();
16564
16565 self.unfold_ranges(&ranges, true, true, cx);
16566 } else {
16567 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16568 let buffer_ids = self
16569 .selections
16570 .disjoint_anchor_ranges()
16571 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16572 .collect::<HashSet<_>>();
16573 for buffer_id in buffer_ids {
16574 self.unfold_buffer(buffer_id, cx);
16575 }
16576 }
16577 }
16578
16579 pub fn unfold_recursive(
16580 &mut self,
16581 _: &UnfoldRecursive,
16582 _window: &mut Window,
16583 cx: &mut Context<Self>,
16584 ) {
16585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16586 let selections = self.selections.all::<Point>(cx);
16587 let ranges = selections
16588 .iter()
16589 .map(|s| {
16590 let mut range = s.display_range(&display_map).sorted();
16591 *range.start.column_mut() = 0;
16592 *range.end.column_mut() = display_map.line_len(range.end.row());
16593 let start = range.start.to_point(&display_map);
16594 let end = range.end.to_point(&display_map);
16595 start..end
16596 })
16597 .collect::<Vec<_>>();
16598
16599 self.unfold_ranges(&ranges, true, true, cx);
16600 }
16601
16602 pub fn unfold_at(
16603 &mut self,
16604 buffer_row: MultiBufferRow,
16605 _window: &mut Window,
16606 cx: &mut Context<Self>,
16607 ) {
16608 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16609
16610 let intersection_range = Point::new(buffer_row.0, 0)
16611 ..Point::new(
16612 buffer_row.0,
16613 display_map.buffer_snapshot.line_len(buffer_row),
16614 );
16615
16616 let autoscroll = self
16617 .selections
16618 .all::<Point>(cx)
16619 .iter()
16620 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16621
16622 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16623 }
16624
16625 pub fn unfold_all(
16626 &mut self,
16627 _: &actions::UnfoldAll,
16628 _window: &mut Window,
16629 cx: &mut Context<Self>,
16630 ) {
16631 if self.buffer.read(cx).is_singleton() {
16632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16633 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16634 } else {
16635 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16636 editor
16637 .update(cx, |editor, cx| {
16638 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16639 editor.unfold_buffer(buffer_id, cx);
16640 }
16641 })
16642 .ok();
16643 });
16644 }
16645 }
16646
16647 pub fn fold_selected_ranges(
16648 &mut self,
16649 _: &FoldSelectedRanges,
16650 window: &mut Window,
16651 cx: &mut Context<Self>,
16652 ) {
16653 let selections = self.selections.all_adjusted(cx);
16654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16655 let ranges = selections
16656 .into_iter()
16657 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16658 .collect::<Vec<_>>();
16659 self.fold_creases(ranges, true, window, cx);
16660 }
16661
16662 pub fn fold_ranges<T: ToOffset + Clone>(
16663 &mut self,
16664 ranges: Vec<Range<T>>,
16665 auto_scroll: bool,
16666 window: &mut Window,
16667 cx: &mut Context<Self>,
16668 ) {
16669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16670 let ranges = ranges
16671 .into_iter()
16672 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16673 .collect::<Vec<_>>();
16674 self.fold_creases(ranges, auto_scroll, window, cx);
16675 }
16676
16677 pub fn fold_creases<T: ToOffset + Clone>(
16678 &mut self,
16679 creases: Vec<Crease<T>>,
16680 auto_scroll: bool,
16681 _window: &mut Window,
16682 cx: &mut Context<Self>,
16683 ) {
16684 if creases.is_empty() {
16685 return;
16686 }
16687
16688 let mut buffers_affected = HashSet::default();
16689 let multi_buffer = self.buffer().read(cx);
16690 for crease in &creases {
16691 if let Some((_, buffer, _)) =
16692 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16693 {
16694 buffers_affected.insert(buffer.read(cx).remote_id());
16695 };
16696 }
16697
16698 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16699
16700 if auto_scroll {
16701 self.request_autoscroll(Autoscroll::fit(), cx);
16702 }
16703
16704 cx.notify();
16705
16706 self.scrollbar_marker_state.dirty = true;
16707 self.folds_did_change(cx);
16708 }
16709
16710 /// Removes any folds whose ranges intersect any of the given ranges.
16711 pub fn unfold_ranges<T: ToOffset + Clone>(
16712 &mut self,
16713 ranges: &[Range<T>],
16714 inclusive: bool,
16715 auto_scroll: bool,
16716 cx: &mut Context<Self>,
16717 ) {
16718 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16719 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16720 });
16721 self.folds_did_change(cx);
16722 }
16723
16724 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16725 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16726 return;
16727 }
16728 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16729 self.display_map.update(cx, |display_map, cx| {
16730 display_map.fold_buffers([buffer_id], cx)
16731 });
16732 cx.emit(EditorEvent::BufferFoldToggled {
16733 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16734 folded: true,
16735 });
16736 cx.notify();
16737 }
16738
16739 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16740 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16741 return;
16742 }
16743 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16744 self.display_map.update(cx, |display_map, cx| {
16745 display_map.unfold_buffers([buffer_id], cx);
16746 });
16747 cx.emit(EditorEvent::BufferFoldToggled {
16748 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16749 folded: false,
16750 });
16751 cx.notify();
16752 }
16753
16754 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16755 self.display_map.read(cx).is_buffer_folded(buffer)
16756 }
16757
16758 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16759 self.display_map.read(cx).folded_buffers()
16760 }
16761
16762 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16763 self.display_map.update(cx, |display_map, cx| {
16764 display_map.disable_header_for_buffer(buffer_id, cx);
16765 });
16766 cx.notify();
16767 }
16768
16769 /// Removes any folds with the given ranges.
16770 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16771 &mut self,
16772 ranges: &[Range<T>],
16773 type_id: TypeId,
16774 auto_scroll: bool,
16775 cx: &mut Context<Self>,
16776 ) {
16777 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16778 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16779 });
16780 self.folds_did_change(cx);
16781 }
16782
16783 fn remove_folds_with<T: ToOffset + Clone>(
16784 &mut self,
16785 ranges: &[Range<T>],
16786 auto_scroll: bool,
16787 cx: &mut Context<Self>,
16788 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16789 ) {
16790 if ranges.is_empty() {
16791 return;
16792 }
16793
16794 let mut buffers_affected = HashSet::default();
16795 let multi_buffer = self.buffer().read(cx);
16796 for range in ranges {
16797 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16798 buffers_affected.insert(buffer.read(cx).remote_id());
16799 };
16800 }
16801
16802 self.display_map.update(cx, update);
16803
16804 if auto_scroll {
16805 self.request_autoscroll(Autoscroll::fit(), cx);
16806 }
16807
16808 cx.notify();
16809 self.scrollbar_marker_state.dirty = true;
16810 self.active_indent_guides_state.dirty = true;
16811 }
16812
16813 pub fn update_fold_widths(
16814 &mut self,
16815 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16816 cx: &mut Context<Self>,
16817 ) -> bool {
16818 self.display_map
16819 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16820 }
16821
16822 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16823 self.display_map.read(cx).fold_placeholder.clone()
16824 }
16825
16826 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16827 self.buffer.update(cx, |buffer, cx| {
16828 buffer.set_all_diff_hunks_expanded(cx);
16829 });
16830 }
16831
16832 pub fn expand_all_diff_hunks(
16833 &mut self,
16834 _: &ExpandAllDiffHunks,
16835 _window: &mut Window,
16836 cx: &mut Context<Self>,
16837 ) {
16838 self.buffer.update(cx, |buffer, cx| {
16839 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16840 });
16841 }
16842
16843 pub fn toggle_selected_diff_hunks(
16844 &mut self,
16845 _: &ToggleSelectedDiffHunks,
16846 _window: &mut Window,
16847 cx: &mut Context<Self>,
16848 ) {
16849 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16850 self.toggle_diff_hunks_in_ranges(ranges, cx);
16851 }
16852
16853 pub fn diff_hunks_in_ranges<'a>(
16854 &'a self,
16855 ranges: &'a [Range<Anchor>],
16856 buffer: &'a MultiBufferSnapshot,
16857 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16858 ranges.iter().flat_map(move |range| {
16859 let end_excerpt_id = range.end.excerpt_id;
16860 let range = range.to_point(buffer);
16861 let mut peek_end = range.end;
16862 if range.end.row < buffer.max_row().0 {
16863 peek_end = Point::new(range.end.row + 1, 0);
16864 }
16865 buffer
16866 .diff_hunks_in_range(range.start..peek_end)
16867 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16868 })
16869 }
16870
16871 pub fn has_stageable_diff_hunks_in_ranges(
16872 &self,
16873 ranges: &[Range<Anchor>],
16874 snapshot: &MultiBufferSnapshot,
16875 ) -> bool {
16876 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16877 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16878 }
16879
16880 pub fn toggle_staged_selected_diff_hunks(
16881 &mut self,
16882 _: &::git::ToggleStaged,
16883 _: &mut Window,
16884 cx: &mut Context<Self>,
16885 ) {
16886 let snapshot = self.buffer.read(cx).snapshot(cx);
16887 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16888 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16889 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16890 }
16891
16892 pub fn set_render_diff_hunk_controls(
16893 &mut self,
16894 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16895 cx: &mut Context<Self>,
16896 ) {
16897 self.render_diff_hunk_controls = render_diff_hunk_controls;
16898 cx.notify();
16899 }
16900
16901 pub fn stage_and_next(
16902 &mut self,
16903 _: &::git::StageAndNext,
16904 window: &mut Window,
16905 cx: &mut Context<Self>,
16906 ) {
16907 self.do_stage_or_unstage_and_next(true, window, cx);
16908 }
16909
16910 pub fn unstage_and_next(
16911 &mut self,
16912 _: &::git::UnstageAndNext,
16913 window: &mut Window,
16914 cx: &mut Context<Self>,
16915 ) {
16916 self.do_stage_or_unstage_and_next(false, window, cx);
16917 }
16918
16919 pub fn stage_or_unstage_diff_hunks(
16920 &mut self,
16921 stage: bool,
16922 ranges: Vec<Range<Anchor>>,
16923 cx: &mut Context<Self>,
16924 ) {
16925 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16926 cx.spawn(async move |this, cx| {
16927 task.await?;
16928 this.update(cx, |this, cx| {
16929 let snapshot = this.buffer.read(cx).snapshot(cx);
16930 let chunk_by = this
16931 .diff_hunks_in_ranges(&ranges, &snapshot)
16932 .chunk_by(|hunk| hunk.buffer_id);
16933 for (buffer_id, hunks) in &chunk_by {
16934 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16935 }
16936 })
16937 })
16938 .detach_and_log_err(cx);
16939 }
16940
16941 fn save_buffers_for_ranges_if_needed(
16942 &mut self,
16943 ranges: &[Range<Anchor>],
16944 cx: &mut Context<Editor>,
16945 ) -> Task<Result<()>> {
16946 let multibuffer = self.buffer.read(cx);
16947 let snapshot = multibuffer.read(cx);
16948 let buffer_ids: HashSet<_> = ranges
16949 .iter()
16950 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16951 .collect();
16952 drop(snapshot);
16953
16954 let mut buffers = HashSet::default();
16955 for buffer_id in buffer_ids {
16956 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16957 let buffer = buffer_entity.read(cx);
16958 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16959 {
16960 buffers.insert(buffer_entity);
16961 }
16962 }
16963 }
16964
16965 if let Some(project) = &self.project {
16966 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16967 } else {
16968 Task::ready(Ok(()))
16969 }
16970 }
16971
16972 fn do_stage_or_unstage_and_next(
16973 &mut self,
16974 stage: bool,
16975 window: &mut Window,
16976 cx: &mut Context<Self>,
16977 ) {
16978 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16979
16980 if ranges.iter().any(|range| range.start != range.end) {
16981 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16982 return;
16983 }
16984
16985 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16986 let snapshot = self.snapshot(window, cx);
16987 let position = self.selections.newest::<Point>(cx).head();
16988 let mut row = snapshot
16989 .buffer_snapshot
16990 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16991 .find(|hunk| hunk.row_range.start.0 > position.row)
16992 .map(|hunk| hunk.row_range.start);
16993
16994 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16995 // Outside of the project diff editor, wrap around to the beginning.
16996 if !all_diff_hunks_expanded {
16997 row = row.or_else(|| {
16998 snapshot
16999 .buffer_snapshot
17000 .diff_hunks_in_range(Point::zero()..position)
17001 .find(|hunk| hunk.row_range.end.0 < position.row)
17002 .map(|hunk| hunk.row_range.start)
17003 });
17004 }
17005
17006 if let Some(row) = row {
17007 let destination = Point::new(row.0, 0);
17008 let autoscroll = Autoscroll::center();
17009
17010 self.unfold_ranges(&[destination..destination], false, false, cx);
17011 self.change_selections(Some(autoscroll), window, cx, |s| {
17012 s.select_ranges([destination..destination]);
17013 });
17014 }
17015 }
17016
17017 fn do_stage_or_unstage(
17018 &self,
17019 stage: bool,
17020 buffer_id: BufferId,
17021 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17022 cx: &mut App,
17023 ) -> Option<()> {
17024 let project = self.project.as_ref()?;
17025 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17026 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17027 let buffer_snapshot = buffer.read(cx).snapshot();
17028 let file_exists = buffer_snapshot
17029 .file()
17030 .is_some_and(|file| file.disk_state().exists());
17031 diff.update(cx, |diff, cx| {
17032 diff.stage_or_unstage_hunks(
17033 stage,
17034 &hunks
17035 .map(|hunk| buffer_diff::DiffHunk {
17036 buffer_range: hunk.buffer_range,
17037 diff_base_byte_range: hunk.diff_base_byte_range,
17038 secondary_status: hunk.secondary_status,
17039 range: Point::zero()..Point::zero(), // unused
17040 })
17041 .collect::<Vec<_>>(),
17042 &buffer_snapshot,
17043 file_exists,
17044 cx,
17045 )
17046 });
17047 None
17048 }
17049
17050 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17051 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17052 self.buffer
17053 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17054 }
17055
17056 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17057 self.buffer.update(cx, |buffer, cx| {
17058 let ranges = vec![Anchor::min()..Anchor::max()];
17059 if !buffer.all_diff_hunks_expanded()
17060 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17061 {
17062 buffer.collapse_diff_hunks(ranges, cx);
17063 true
17064 } else {
17065 false
17066 }
17067 })
17068 }
17069
17070 fn toggle_diff_hunks_in_ranges(
17071 &mut self,
17072 ranges: Vec<Range<Anchor>>,
17073 cx: &mut Context<Editor>,
17074 ) {
17075 self.buffer.update(cx, |buffer, cx| {
17076 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17077 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17078 })
17079 }
17080
17081 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17082 self.buffer.update(cx, |buffer, cx| {
17083 let snapshot = buffer.snapshot(cx);
17084 let excerpt_id = range.end.excerpt_id;
17085 let point_range = range.to_point(&snapshot);
17086 let expand = !buffer.single_hunk_is_expanded(range, cx);
17087 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17088 })
17089 }
17090
17091 pub(crate) fn apply_all_diff_hunks(
17092 &mut self,
17093 _: &ApplyAllDiffHunks,
17094 window: &mut Window,
17095 cx: &mut Context<Self>,
17096 ) {
17097 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17098
17099 let buffers = self.buffer.read(cx).all_buffers();
17100 for branch_buffer in buffers {
17101 branch_buffer.update(cx, |branch_buffer, cx| {
17102 branch_buffer.merge_into_base(Vec::new(), cx);
17103 });
17104 }
17105
17106 if let Some(project) = self.project.clone() {
17107 self.save(true, project, window, cx).detach_and_log_err(cx);
17108 }
17109 }
17110
17111 pub(crate) fn apply_selected_diff_hunks(
17112 &mut self,
17113 _: &ApplyDiffHunk,
17114 window: &mut Window,
17115 cx: &mut Context<Self>,
17116 ) {
17117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17118 let snapshot = self.snapshot(window, cx);
17119 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17120 let mut ranges_by_buffer = HashMap::default();
17121 self.transact(window, cx, |editor, _window, cx| {
17122 for hunk in hunks {
17123 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17124 ranges_by_buffer
17125 .entry(buffer.clone())
17126 .or_insert_with(Vec::new)
17127 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17128 }
17129 }
17130
17131 for (buffer, ranges) in ranges_by_buffer {
17132 buffer.update(cx, |buffer, cx| {
17133 buffer.merge_into_base(ranges, cx);
17134 });
17135 }
17136 });
17137
17138 if let Some(project) = self.project.clone() {
17139 self.save(true, project, window, cx).detach_and_log_err(cx);
17140 }
17141 }
17142
17143 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17144 if hovered != self.gutter_hovered {
17145 self.gutter_hovered = hovered;
17146 cx.notify();
17147 }
17148 }
17149
17150 pub fn insert_blocks(
17151 &mut self,
17152 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17153 autoscroll: Option<Autoscroll>,
17154 cx: &mut Context<Self>,
17155 ) -> Vec<CustomBlockId> {
17156 let blocks = self
17157 .display_map
17158 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17159 if let Some(autoscroll) = autoscroll {
17160 self.request_autoscroll(autoscroll, cx);
17161 }
17162 cx.notify();
17163 blocks
17164 }
17165
17166 pub fn resize_blocks(
17167 &mut self,
17168 heights: HashMap<CustomBlockId, u32>,
17169 autoscroll: Option<Autoscroll>,
17170 cx: &mut Context<Self>,
17171 ) {
17172 self.display_map
17173 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17174 if let Some(autoscroll) = autoscroll {
17175 self.request_autoscroll(autoscroll, cx);
17176 }
17177 cx.notify();
17178 }
17179
17180 pub fn replace_blocks(
17181 &mut self,
17182 renderers: HashMap<CustomBlockId, RenderBlock>,
17183 autoscroll: Option<Autoscroll>,
17184 cx: &mut Context<Self>,
17185 ) {
17186 self.display_map
17187 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17188 if let Some(autoscroll) = autoscroll {
17189 self.request_autoscroll(autoscroll, cx);
17190 }
17191 cx.notify();
17192 }
17193
17194 pub fn remove_blocks(
17195 &mut self,
17196 block_ids: HashSet<CustomBlockId>,
17197 autoscroll: Option<Autoscroll>,
17198 cx: &mut Context<Self>,
17199 ) {
17200 self.display_map.update(cx, |display_map, cx| {
17201 display_map.remove_blocks(block_ids, cx)
17202 });
17203 if let Some(autoscroll) = autoscroll {
17204 self.request_autoscroll(autoscroll, cx);
17205 }
17206 cx.notify();
17207 }
17208
17209 pub fn row_for_block(
17210 &self,
17211 block_id: CustomBlockId,
17212 cx: &mut Context<Self>,
17213 ) -> Option<DisplayRow> {
17214 self.display_map
17215 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17216 }
17217
17218 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17219 self.focused_block = Some(focused_block);
17220 }
17221
17222 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17223 self.focused_block.take()
17224 }
17225
17226 pub fn insert_creases(
17227 &mut self,
17228 creases: impl IntoIterator<Item = Crease<Anchor>>,
17229 cx: &mut Context<Self>,
17230 ) -> Vec<CreaseId> {
17231 self.display_map
17232 .update(cx, |map, cx| map.insert_creases(creases, cx))
17233 }
17234
17235 pub fn remove_creases(
17236 &mut self,
17237 ids: impl IntoIterator<Item = CreaseId>,
17238 cx: &mut Context<Self>,
17239 ) -> Vec<(CreaseId, Range<Anchor>)> {
17240 self.display_map
17241 .update(cx, |map, cx| map.remove_creases(ids, cx))
17242 }
17243
17244 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17245 self.display_map
17246 .update(cx, |map, cx| map.snapshot(cx))
17247 .longest_row()
17248 }
17249
17250 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17251 self.display_map
17252 .update(cx, |map, cx| map.snapshot(cx))
17253 .max_point()
17254 }
17255
17256 pub fn text(&self, cx: &App) -> String {
17257 self.buffer.read(cx).read(cx).text()
17258 }
17259
17260 pub fn is_empty(&self, cx: &App) -> bool {
17261 self.buffer.read(cx).read(cx).is_empty()
17262 }
17263
17264 pub fn text_option(&self, cx: &App) -> Option<String> {
17265 let text = self.text(cx);
17266 let text = text.trim();
17267
17268 if text.is_empty() {
17269 return None;
17270 }
17271
17272 Some(text.to_string())
17273 }
17274
17275 pub fn set_text(
17276 &mut self,
17277 text: impl Into<Arc<str>>,
17278 window: &mut Window,
17279 cx: &mut Context<Self>,
17280 ) {
17281 self.transact(window, cx, |this, _, cx| {
17282 this.buffer
17283 .read(cx)
17284 .as_singleton()
17285 .expect("you can only call set_text on editors for singleton buffers")
17286 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17287 });
17288 }
17289
17290 pub fn display_text(&self, cx: &mut App) -> String {
17291 self.display_map
17292 .update(cx, |map, cx| map.snapshot(cx))
17293 .text()
17294 }
17295
17296 fn create_minimap(
17297 &self,
17298 minimap_settings: MinimapSettings,
17299 window: &mut Window,
17300 cx: &mut Context<Self>,
17301 ) -> Option<Entity<Self>> {
17302 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17303 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17304 }
17305
17306 fn initialize_new_minimap(
17307 &self,
17308 minimap_settings: MinimapSettings,
17309 window: &mut Window,
17310 cx: &mut Context<Self>,
17311 ) -> Entity<Self> {
17312 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17313
17314 let mut minimap = Editor::new_internal(
17315 EditorMode::Minimap {
17316 parent: cx.weak_entity(),
17317 },
17318 self.buffer.clone(),
17319 self.project.clone(),
17320 Some(self.display_map.clone()),
17321 window,
17322 cx,
17323 );
17324 minimap.scroll_manager.clone_state(&self.scroll_manager);
17325 minimap.set_text_style_refinement(TextStyleRefinement {
17326 font_size: Some(MINIMAP_FONT_SIZE),
17327 font_weight: Some(MINIMAP_FONT_WEIGHT),
17328 ..Default::default()
17329 });
17330 minimap.update_minimap_configuration(minimap_settings, cx);
17331 cx.new(|_| minimap)
17332 }
17333
17334 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17335 let current_line_highlight = minimap_settings
17336 .current_line_highlight
17337 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17338 self.set_current_line_highlight(Some(current_line_highlight));
17339 }
17340
17341 pub fn minimap(&self) -> Option<&Entity<Self>> {
17342 self.minimap
17343 .as_ref()
17344 .filter(|_| self.minimap_visibility.visible())
17345 }
17346
17347 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17348 let mut wrap_guides = smallvec![];
17349
17350 if self.show_wrap_guides == Some(false) {
17351 return wrap_guides;
17352 }
17353
17354 let settings = self.buffer.read(cx).language_settings(cx);
17355 if settings.show_wrap_guides {
17356 match self.soft_wrap_mode(cx) {
17357 SoftWrap::Column(soft_wrap) => {
17358 wrap_guides.push((soft_wrap as usize, true));
17359 }
17360 SoftWrap::Bounded(soft_wrap) => {
17361 wrap_guides.push((soft_wrap as usize, true));
17362 }
17363 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17364 }
17365 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17366 }
17367
17368 wrap_guides
17369 }
17370
17371 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17372 let settings = self.buffer.read(cx).language_settings(cx);
17373 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17374 match mode {
17375 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17376 SoftWrap::None
17377 }
17378 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17379 language_settings::SoftWrap::PreferredLineLength => {
17380 SoftWrap::Column(settings.preferred_line_length)
17381 }
17382 language_settings::SoftWrap::Bounded => {
17383 SoftWrap::Bounded(settings.preferred_line_length)
17384 }
17385 }
17386 }
17387
17388 pub fn set_soft_wrap_mode(
17389 &mut self,
17390 mode: language_settings::SoftWrap,
17391
17392 cx: &mut Context<Self>,
17393 ) {
17394 self.soft_wrap_mode_override = Some(mode);
17395 cx.notify();
17396 }
17397
17398 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17399 self.hard_wrap = hard_wrap;
17400 cx.notify();
17401 }
17402
17403 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17404 self.text_style_refinement = Some(style);
17405 }
17406
17407 /// called by the Element so we know what style we were most recently rendered with.
17408 pub(crate) fn set_style(
17409 &mut self,
17410 style: EditorStyle,
17411 window: &mut Window,
17412 cx: &mut Context<Self>,
17413 ) {
17414 // We intentionally do not inform the display map about the minimap style
17415 // so that wrapping is not recalculated and stays consistent for the editor
17416 // and its linked minimap.
17417 if !self.mode.is_minimap() {
17418 let rem_size = window.rem_size();
17419 self.display_map.update(cx, |map, cx| {
17420 map.set_font(
17421 style.text.font(),
17422 style.text.font_size.to_pixels(rem_size),
17423 cx,
17424 )
17425 });
17426 }
17427 self.style = Some(style);
17428 }
17429
17430 pub fn style(&self) -> Option<&EditorStyle> {
17431 self.style.as_ref()
17432 }
17433
17434 // Called by the element. This method is not designed to be called outside of the editor
17435 // element's layout code because it does not notify when rewrapping is computed synchronously.
17436 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17437 self.display_map
17438 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17439 }
17440
17441 pub fn set_soft_wrap(&mut self) {
17442 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17443 }
17444
17445 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17446 if self.soft_wrap_mode_override.is_some() {
17447 self.soft_wrap_mode_override.take();
17448 } else {
17449 let soft_wrap = match self.soft_wrap_mode(cx) {
17450 SoftWrap::GitDiff => return,
17451 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17452 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17453 language_settings::SoftWrap::None
17454 }
17455 };
17456 self.soft_wrap_mode_override = Some(soft_wrap);
17457 }
17458 cx.notify();
17459 }
17460
17461 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17462 let Some(workspace) = self.workspace() else {
17463 return;
17464 };
17465 let fs = workspace.read(cx).app_state().fs.clone();
17466 let current_show = TabBarSettings::get_global(cx).show;
17467 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17468 setting.show = Some(!current_show);
17469 });
17470 }
17471
17472 pub fn toggle_indent_guides(
17473 &mut self,
17474 _: &ToggleIndentGuides,
17475 _: &mut Window,
17476 cx: &mut Context<Self>,
17477 ) {
17478 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17479 self.buffer
17480 .read(cx)
17481 .language_settings(cx)
17482 .indent_guides
17483 .enabled
17484 });
17485 self.show_indent_guides = Some(!currently_enabled);
17486 cx.notify();
17487 }
17488
17489 fn should_show_indent_guides(&self) -> Option<bool> {
17490 self.show_indent_guides
17491 }
17492
17493 pub fn toggle_line_numbers(
17494 &mut self,
17495 _: &ToggleLineNumbers,
17496 _: &mut Window,
17497 cx: &mut Context<Self>,
17498 ) {
17499 let mut editor_settings = EditorSettings::get_global(cx).clone();
17500 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17501 EditorSettings::override_global(editor_settings, cx);
17502 }
17503
17504 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17505 if let Some(show_line_numbers) = self.show_line_numbers {
17506 return show_line_numbers;
17507 }
17508 EditorSettings::get_global(cx).gutter.line_numbers
17509 }
17510
17511 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17512 self.use_relative_line_numbers
17513 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17514 }
17515
17516 pub fn toggle_relative_line_numbers(
17517 &mut self,
17518 _: &ToggleRelativeLineNumbers,
17519 _: &mut Window,
17520 cx: &mut Context<Self>,
17521 ) {
17522 let is_relative = self.should_use_relative_line_numbers(cx);
17523 self.set_relative_line_number(Some(!is_relative), cx)
17524 }
17525
17526 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17527 self.use_relative_line_numbers = is_relative;
17528 cx.notify();
17529 }
17530
17531 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17532 self.show_gutter = show_gutter;
17533 cx.notify();
17534 }
17535
17536 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17537 self.show_scrollbars = ScrollbarAxes {
17538 horizontal: show,
17539 vertical: show,
17540 };
17541 cx.notify();
17542 }
17543
17544 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17545 self.show_scrollbars.vertical = show;
17546 cx.notify();
17547 }
17548
17549 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17550 self.show_scrollbars.horizontal = show;
17551 cx.notify();
17552 }
17553
17554 pub fn set_minimap_visibility(
17555 &mut self,
17556 minimap_visibility: MinimapVisibility,
17557 window: &mut Window,
17558 cx: &mut Context<Self>,
17559 ) {
17560 if self.minimap_visibility != minimap_visibility {
17561 if minimap_visibility.visible() && self.minimap.is_none() {
17562 let minimap_settings = EditorSettings::get_global(cx).minimap;
17563 self.minimap =
17564 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17565 }
17566 self.minimap_visibility = minimap_visibility;
17567 cx.notify();
17568 }
17569 }
17570
17571 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17572 self.set_show_scrollbars(false, cx);
17573 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17574 }
17575
17576 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17577 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17578 }
17579
17580 /// Normally the text in full mode and auto height editors is padded on the
17581 /// left side by roughly half a character width for improved hit testing.
17582 ///
17583 /// Use this method to disable this for cases where this is not wanted (e.g.
17584 /// if you want to align the editor text with some other text above or below)
17585 /// or if you want to add this padding to single-line editors.
17586 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17587 self.offset_content = offset_content;
17588 cx.notify();
17589 }
17590
17591 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17592 self.show_line_numbers = Some(show_line_numbers);
17593 cx.notify();
17594 }
17595
17596 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17597 self.disable_expand_excerpt_buttons = true;
17598 cx.notify();
17599 }
17600
17601 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17602 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17603 cx.notify();
17604 }
17605
17606 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17607 self.show_code_actions = Some(show_code_actions);
17608 cx.notify();
17609 }
17610
17611 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17612 self.show_runnables = Some(show_runnables);
17613 cx.notify();
17614 }
17615
17616 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17617 self.show_breakpoints = Some(show_breakpoints);
17618 cx.notify();
17619 }
17620
17621 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17622 if self.display_map.read(cx).masked != masked {
17623 self.display_map.update(cx, |map, _| map.masked = masked);
17624 }
17625 cx.notify()
17626 }
17627
17628 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17629 self.show_wrap_guides = Some(show_wrap_guides);
17630 cx.notify();
17631 }
17632
17633 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17634 self.show_indent_guides = Some(show_indent_guides);
17635 cx.notify();
17636 }
17637
17638 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17639 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17640 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17641 if let Some(dir) = file.abs_path(cx).parent() {
17642 return Some(dir.to_owned());
17643 }
17644 }
17645
17646 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17647 return Some(project_path.path.to_path_buf());
17648 }
17649 }
17650
17651 None
17652 }
17653
17654 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17655 self.active_excerpt(cx)?
17656 .1
17657 .read(cx)
17658 .file()
17659 .and_then(|f| f.as_local())
17660 }
17661
17662 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17663 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17664 let buffer = buffer.read(cx);
17665 if let Some(project_path) = buffer.project_path(cx) {
17666 let project = self.project.as_ref()?.read(cx);
17667 project.absolute_path(&project_path, cx)
17668 } else {
17669 buffer
17670 .file()
17671 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17672 }
17673 })
17674 }
17675
17676 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17677 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17678 let project_path = buffer.read(cx).project_path(cx)?;
17679 let project = self.project.as_ref()?.read(cx);
17680 let entry = project.entry_for_path(&project_path, cx)?;
17681 let path = entry.path.to_path_buf();
17682 Some(path)
17683 })
17684 }
17685
17686 pub fn reveal_in_finder(
17687 &mut self,
17688 _: &RevealInFileManager,
17689 _window: &mut Window,
17690 cx: &mut Context<Self>,
17691 ) {
17692 if let Some(target) = self.target_file(cx) {
17693 cx.reveal_path(&target.abs_path(cx));
17694 }
17695 }
17696
17697 pub fn copy_path(
17698 &mut self,
17699 _: &zed_actions::workspace::CopyPath,
17700 _window: &mut Window,
17701 cx: &mut Context<Self>,
17702 ) {
17703 if let Some(path) = self.target_file_abs_path(cx) {
17704 if let Some(path) = path.to_str() {
17705 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17706 }
17707 }
17708 }
17709
17710 pub fn copy_relative_path(
17711 &mut self,
17712 _: &zed_actions::workspace::CopyRelativePath,
17713 _window: &mut Window,
17714 cx: &mut Context<Self>,
17715 ) {
17716 if let Some(path) = self.target_file_path(cx) {
17717 if let Some(path) = path.to_str() {
17718 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17719 }
17720 }
17721 }
17722
17723 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17724 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17725 buffer.read(cx).project_path(cx)
17726 } else {
17727 None
17728 }
17729 }
17730
17731 // Returns true if the editor handled a go-to-line request
17732 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17733 maybe!({
17734 let breakpoint_store = self.breakpoint_store.as_ref()?;
17735
17736 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17737 else {
17738 self.clear_row_highlights::<ActiveDebugLine>();
17739 return None;
17740 };
17741
17742 let position = active_stack_frame.position;
17743 let buffer_id = position.buffer_id?;
17744 let snapshot = self
17745 .project
17746 .as_ref()?
17747 .read(cx)
17748 .buffer_for_id(buffer_id, cx)?
17749 .read(cx)
17750 .snapshot();
17751
17752 let mut handled = false;
17753 for (id, ExcerptRange { context, .. }) in
17754 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17755 {
17756 if context.start.cmp(&position, &snapshot).is_ge()
17757 || context.end.cmp(&position, &snapshot).is_lt()
17758 {
17759 continue;
17760 }
17761 let snapshot = self.buffer.read(cx).snapshot(cx);
17762 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17763
17764 handled = true;
17765 self.clear_row_highlights::<ActiveDebugLine>();
17766
17767 self.go_to_line::<ActiveDebugLine>(
17768 multibuffer_anchor,
17769 Some(cx.theme().colors().editor_debugger_active_line_background),
17770 window,
17771 cx,
17772 );
17773
17774 cx.notify();
17775 }
17776
17777 handled.then_some(())
17778 })
17779 .is_some()
17780 }
17781
17782 pub fn copy_file_name_without_extension(
17783 &mut self,
17784 _: &CopyFileNameWithoutExtension,
17785 _: &mut Window,
17786 cx: &mut Context<Self>,
17787 ) {
17788 if let Some(file) = self.target_file(cx) {
17789 if let Some(file_stem) = file.path().file_stem() {
17790 if let Some(name) = file_stem.to_str() {
17791 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17792 }
17793 }
17794 }
17795 }
17796
17797 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17798 if let Some(file) = self.target_file(cx) {
17799 if let Some(file_name) = file.path().file_name() {
17800 if let Some(name) = file_name.to_str() {
17801 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17802 }
17803 }
17804 }
17805 }
17806
17807 pub fn toggle_git_blame(
17808 &mut self,
17809 _: &::git::Blame,
17810 window: &mut Window,
17811 cx: &mut Context<Self>,
17812 ) {
17813 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17814
17815 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17816 self.start_git_blame(true, window, cx);
17817 }
17818
17819 cx.notify();
17820 }
17821
17822 pub fn toggle_git_blame_inline(
17823 &mut self,
17824 _: &ToggleGitBlameInline,
17825 window: &mut Window,
17826 cx: &mut Context<Self>,
17827 ) {
17828 self.toggle_git_blame_inline_internal(true, window, cx);
17829 cx.notify();
17830 }
17831
17832 pub fn open_git_blame_commit(
17833 &mut self,
17834 _: &OpenGitBlameCommit,
17835 window: &mut Window,
17836 cx: &mut Context<Self>,
17837 ) {
17838 self.open_git_blame_commit_internal(window, cx);
17839 }
17840
17841 fn open_git_blame_commit_internal(
17842 &mut self,
17843 window: &mut Window,
17844 cx: &mut Context<Self>,
17845 ) -> Option<()> {
17846 let blame = self.blame.as_ref()?;
17847 let snapshot = self.snapshot(window, cx);
17848 let cursor = self.selections.newest::<Point>(cx).head();
17849 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17850 let blame_entry = blame
17851 .update(cx, |blame, cx| {
17852 blame
17853 .blame_for_rows(
17854 &[RowInfo {
17855 buffer_id: Some(buffer.remote_id()),
17856 buffer_row: Some(point.row),
17857 ..Default::default()
17858 }],
17859 cx,
17860 )
17861 .next()
17862 })
17863 .flatten()?;
17864 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17865 let repo = blame.read(cx).repository(cx)?;
17866 let workspace = self.workspace()?.downgrade();
17867 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17868 None
17869 }
17870
17871 pub fn git_blame_inline_enabled(&self) -> bool {
17872 self.git_blame_inline_enabled
17873 }
17874
17875 pub fn toggle_selection_menu(
17876 &mut self,
17877 _: &ToggleSelectionMenu,
17878 _: &mut Window,
17879 cx: &mut Context<Self>,
17880 ) {
17881 self.show_selection_menu = self
17882 .show_selection_menu
17883 .map(|show_selections_menu| !show_selections_menu)
17884 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17885
17886 cx.notify();
17887 }
17888
17889 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17890 self.show_selection_menu
17891 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17892 }
17893
17894 fn start_git_blame(
17895 &mut self,
17896 user_triggered: bool,
17897 window: &mut Window,
17898 cx: &mut Context<Self>,
17899 ) {
17900 if let Some(project) = self.project.as_ref() {
17901 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17902 return;
17903 };
17904
17905 if buffer.read(cx).file().is_none() {
17906 return;
17907 }
17908
17909 let focused = self.focus_handle(cx).contains_focused(window, cx);
17910
17911 let project = project.clone();
17912 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17913 self.blame_subscription =
17914 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17915 self.blame = Some(blame);
17916 }
17917 }
17918
17919 fn toggle_git_blame_inline_internal(
17920 &mut self,
17921 user_triggered: bool,
17922 window: &mut Window,
17923 cx: &mut Context<Self>,
17924 ) {
17925 if self.git_blame_inline_enabled {
17926 self.git_blame_inline_enabled = false;
17927 self.show_git_blame_inline = false;
17928 self.show_git_blame_inline_delay_task.take();
17929 } else {
17930 self.git_blame_inline_enabled = true;
17931 self.start_git_blame_inline(user_triggered, window, cx);
17932 }
17933
17934 cx.notify();
17935 }
17936
17937 fn start_git_blame_inline(
17938 &mut self,
17939 user_triggered: bool,
17940 window: &mut Window,
17941 cx: &mut Context<Self>,
17942 ) {
17943 self.start_git_blame(user_triggered, window, cx);
17944
17945 if ProjectSettings::get_global(cx)
17946 .git
17947 .inline_blame_delay()
17948 .is_some()
17949 {
17950 self.start_inline_blame_timer(window, cx);
17951 } else {
17952 self.show_git_blame_inline = true
17953 }
17954 }
17955
17956 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17957 self.blame.as_ref()
17958 }
17959
17960 pub fn show_git_blame_gutter(&self) -> bool {
17961 self.show_git_blame_gutter
17962 }
17963
17964 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17965 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17966 }
17967
17968 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17969 self.show_git_blame_inline
17970 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17971 && !self.newest_selection_head_on_empty_line(cx)
17972 && self.has_blame_entries(cx)
17973 }
17974
17975 fn has_blame_entries(&self, cx: &App) -> bool {
17976 self.blame()
17977 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17978 }
17979
17980 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17981 let cursor_anchor = self.selections.newest_anchor().head();
17982
17983 let snapshot = self.buffer.read(cx).snapshot(cx);
17984 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17985
17986 snapshot.line_len(buffer_row) == 0
17987 }
17988
17989 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17990 let buffer_and_selection = maybe!({
17991 let selection = self.selections.newest::<Point>(cx);
17992 let selection_range = selection.range();
17993
17994 let multi_buffer = self.buffer().read(cx);
17995 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17996 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17997
17998 let (buffer, range, _) = if selection.reversed {
17999 buffer_ranges.first()
18000 } else {
18001 buffer_ranges.last()
18002 }?;
18003
18004 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18005 ..text::ToPoint::to_point(&range.end, &buffer).row;
18006 Some((
18007 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18008 selection,
18009 ))
18010 });
18011
18012 let Some((buffer, selection)) = buffer_and_selection else {
18013 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18014 };
18015
18016 let Some(project) = self.project.as_ref() else {
18017 return Task::ready(Err(anyhow!("editor does not have project")));
18018 };
18019
18020 project.update(cx, |project, cx| {
18021 project.get_permalink_to_line(&buffer, selection, cx)
18022 })
18023 }
18024
18025 pub fn copy_permalink_to_line(
18026 &mut self,
18027 _: &CopyPermalinkToLine,
18028 window: &mut Window,
18029 cx: &mut Context<Self>,
18030 ) {
18031 let permalink_task = self.get_permalink_to_line(cx);
18032 let workspace = self.workspace();
18033
18034 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18035 Ok(permalink) => {
18036 cx.update(|_, cx| {
18037 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18038 })
18039 .ok();
18040 }
18041 Err(err) => {
18042 let message = format!("Failed to copy permalink: {err}");
18043
18044 anyhow::Result::<()>::Err(err).log_err();
18045
18046 if let Some(workspace) = workspace {
18047 workspace
18048 .update_in(cx, |workspace, _, cx| {
18049 struct CopyPermalinkToLine;
18050
18051 workspace.show_toast(
18052 Toast::new(
18053 NotificationId::unique::<CopyPermalinkToLine>(),
18054 message,
18055 ),
18056 cx,
18057 )
18058 })
18059 .ok();
18060 }
18061 }
18062 })
18063 .detach();
18064 }
18065
18066 pub fn copy_file_location(
18067 &mut self,
18068 _: &CopyFileLocation,
18069 _: &mut Window,
18070 cx: &mut Context<Self>,
18071 ) {
18072 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18073 if let Some(file) = self.target_file(cx) {
18074 if let Some(path) = file.path().to_str() {
18075 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18076 }
18077 }
18078 }
18079
18080 pub fn open_permalink_to_line(
18081 &mut self,
18082 _: &OpenPermalinkToLine,
18083 window: &mut Window,
18084 cx: &mut Context<Self>,
18085 ) {
18086 let permalink_task = self.get_permalink_to_line(cx);
18087 let workspace = self.workspace();
18088
18089 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18090 Ok(permalink) => {
18091 cx.update(|_, cx| {
18092 cx.open_url(permalink.as_ref());
18093 })
18094 .ok();
18095 }
18096 Err(err) => {
18097 let message = format!("Failed to open permalink: {err}");
18098
18099 anyhow::Result::<()>::Err(err).log_err();
18100
18101 if let Some(workspace) = workspace {
18102 workspace
18103 .update(cx, |workspace, cx| {
18104 struct OpenPermalinkToLine;
18105
18106 workspace.show_toast(
18107 Toast::new(
18108 NotificationId::unique::<OpenPermalinkToLine>(),
18109 message,
18110 ),
18111 cx,
18112 )
18113 })
18114 .ok();
18115 }
18116 }
18117 })
18118 .detach();
18119 }
18120
18121 pub fn insert_uuid_v4(
18122 &mut self,
18123 _: &InsertUuidV4,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) {
18127 self.insert_uuid(UuidVersion::V4, window, cx);
18128 }
18129
18130 pub fn insert_uuid_v7(
18131 &mut self,
18132 _: &InsertUuidV7,
18133 window: &mut Window,
18134 cx: &mut Context<Self>,
18135 ) {
18136 self.insert_uuid(UuidVersion::V7, window, cx);
18137 }
18138
18139 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18140 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18141 self.transact(window, cx, |this, window, cx| {
18142 let edits = this
18143 .selections
18144 .all::<Point>(cx)
18145 .into_iter()
18146 .map(|selection| {
18147 let uuid = match version {
18148 UuidVersion::V4 => uuid::Uuid::new_v4(),
18149 UuidVersion::V7 => uuid::Uuid::now_v7(),
18150 };
18151
18152 (selection.range(), uuid.to_string())
18153 });
18154 this.edit(edits, cx);
18155 this.refresh_inline_completion(true, false, window, cx);
18156 });
18157 }
18158
18159 pub fn open_selections_in_multibuffer(
18160 &mut self,
18161 _: &OpenSelectionsInMultibuffer,
18162 window: &mut Window,
18163 cx: &mut Context<Self>,
18164 ) {
18165 let multibuffer = self.buffer.read(cx);
18166
18167 let Some(buffer) = multibuffer.as_singleton() else {
18168 return;
18169 };
18170
18171 let Some(workspace) = self.workspace() else {
18172 return;
18173 };
18174
18175 let locations = self
18176 .selections
18177 .disjoint_anchors()
18178 .iter()
18179 .map(|range| Location {
18180 buffer: buffer.clone(),
18181 range: range.start.text_anchor..range.end.text_anchor,
18182 })
18183 .collect::<Vec<_>>();
18184
18185 let title = multibuffer.title(cx).to_string();
18186
18187 cx.spawn_in(window, async move |_, cx| {
18188 workspace.update_in(cx, |workspace, window, cx| {
18189 Self::open_locations_in_multibuffer(
18190 workspace,
18191 locations,
18192 format!("Selections for '{title}'"),
18193 false,
18194 MultibufferSelectionMode::All,
18195 window,
18196 cx,
18197 );
18198 })
18199 })
18200 .detach();
18201 }
18202
18203 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18204 /// last highlight added will be used.
18205 ///
18206 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18207 pub fn highlight_rows<T: 'static>(
18208 &mut self,
18209 range: Range<Anchor>,
18210 color: Hsla,
18211 options: RowHighlightOptions,
18212 cx: &mut Context<Self>,
18213 ) {
18214 let snapshot = self.buffer().read(cx).snapshot(cx);
18215 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18216 let ix = row_highlights.binary_search_by(|highlight| {
18217 Ordering::Equal
18218 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18219 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18220 });
18221
18222 if let Err(mut ix) = ix {
18223 let index = post_inc(&mut self.highlight_order);
18224
18225 // If this range intersects with the preceding highlight, then merge it with
18226 // the preceding highlight. Otherwise insert a new highlight.
18227 let mut merged = false;
18228 if ix > 0 {
18229 let prev_highlight = &mut row_highlights[ix - 1];
18230 if prev_highlight
18231 .range
18232 .end
18233 .cmp(&range.start, &snapshot)
18234 .is_ge()
18235 {
18236 ix -= 1;
18237 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18238 prev_highlight.range.end = range.end;
18239 }
18240 merged = true;
18241 prev_highlight.index = index;
18242 prev_highlight.color = color;
18243 prev_highlight.options = options;
18244 }
18245 }
18246
18247 if !merged {
18248 row_highlights.insert(
18249 ix,
18250 RowHighlight {
18251 range: range.clone(),
18252 index,
18253 color,
18254 options,
18255 type_id: TypeId::of::<T>(),
18256 },
18257 );
18258 }
18259
18260 // If any of the following highlights intersect with this one, merge them.
18261 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18262 let highlight = &row_highlights[ix];
18263 if next_highlight
18264 .range
18265 .start
18266 .cmp(&highlight.range.end, &snapshot)
18267 .is_le()
18268 {
18269 if next_highlight
18270 .range
18271 .end
18272 .cmp(&highlight.range.end, &snapshot)
18273 .is_gt()
18274 {
18275 row_highlights[ix].range.end = next_highlight.range.end;
18276 }
18277 row_highlights.remove(ix + 1);
18278 } else {
18279 break;
18280 }
18281 }
18282 }
18283 }
18284
18285 /// Remove any highlighted row ranges of the given type that intersect the
18286 /// given ranges.
18287 pub fn remove_highlighted_rows<T: 'static>(
18288 &mut self,
18289 ranges_to_remove: Vec<Range<Anchor>>,
18290 cx: &mut Context<Self>,
18291 ) {
18292 let snapshot = self.buffer().read(cx).snapshot(cx);
18293 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18294 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18295 row_highlights.retain(|highlight| {
18296 while let Some(range_to_remove) = ranges_to_remove.peek() {
18297 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18298 Ordering::Less | Ordering::Equal => {
18299 ranges_to_remove.next();
18300 }
18301 Ordering::Greater => {
18302 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18303 Ordering::Less | Ordering::Equal => {
18304 return false;
18305 }
18306 Ordering::Greater => break,
18307 }
18308 }
18309 }
18310 }
18311
18312 true
18313 })
18314 }
18315
18316 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18317 pub fn clear_row_highlights<T: 'static>(&mut self) {
18318 self.highlighted_rows.remove(&TypeId::of::<T>());
18319 }
18320
18321 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18322 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18323 self.highlighted_rows
18324 .get(&TypeId::of::<T>())
18325 .map_or(&[] as &[_], |vec| vec.as_slice())
18326 .iter()
18327 .map(|highlight| (highlight.range.clone(), highlight.color))
18328 }
18329
18330 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18331 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18332 /// Allows to ignore certain kinds of highlights.
18333 pub fn highlighted_display_rows(
18334 &self,
18335 window: &mut Window,
18336 cx: &mut App,
18337 ) -> BTreeMap<DisplayRow, LineHighlight> {
18338 let snapshot = self.snapshot(window, cx);
18339 let mut used_highlight_orders = HashMap::default();
18340 self.highlighted_rows
18341 .iter()
18342 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18343 .fold(
18344 BTreeMap::<DisplayRow, LineHighlight>::new(),
18345 |mut unique_rows, highlight| {
18346 let start = highlight.range.start.to_display_point(&snapshot);
18347 let end = highlight.range.end.to_display_point(&snapshot);
18348 let start_row = start.row().0;
18349 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18350 && end.column() == 0
18351 {
18352 end.row().0.saturating_sub(1)
18353 } else {
18354 end.row().0
18355 };
18356 for row in start_row..=end_row {
18357 let used_index =
18358 used_highlight_orders.entry(row).or_insert(highlight.index);
18359 if highlight.index >= *used_index {
18360 *used_index = highlight.index;
18361 unique_rows.insert(
18362 DisplayRow(row),
18363 LineHighlight {
18364 include_gutter: highlight.options.include_gutter,
18365 border: None,
18366 background: highlight.color.into(),
18367 type_id: Some(highlight.type_id),
18368 },
18369 );
18370 }
18371 }
18372 unique_rows
18373 },
18374 )
18375 }
18376
18377 pub fn highlighted_display_row_for_autoscroll(
18378 &self,
18379 snapshot: &DisplaySnapshot,
18380 ) -> Option<DisplayRow> {
18381 self.highlighted_rows
18382 .values()
18383 .flat_map(|highlighted_rows| highlighted_rows.iter())
18384 .filter_map(|highlight| {
18385 if highlight.options.autoscroll {
18386 Some(highlight.range.start.to_display_point(snapshot).row())
18387 } else {
18388 None
18389 }
18390 })
18391 .min()
18392 }
18393
18394 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18395 self.highlight_background::<SearchWithinRange>(
18396 ranges,
18397 |colors| colors.editor_document_highlight_read_background,
18398 cx,
18399 )
18400 }
18401
18402 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18403 self.breadcrumb_header = Some(new_header);
18404 }
18405
18406 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18407 self.clear_background_highlights::<SearchWithinRange>(cx);
18408 }
18409
18410 pub fn highlight_background<T: 'static>(
18411 &mut self,
18412 ranges: &[Range<Anchor>],
18413 color_fetcher: fn(&ThemeColors) -> Hsla,
18414 cx: &mut Context<Self>,
18415 ) {
18416 self.background_highlights
18417 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18418 self.scrollbar_marker_state.dirty = true;
18419 cx.notify();
18420 }
18421
18422 pub fn clear_background_highlights<T: 'static>(
18423 &mut self,
18424 cx: &mut Context<Self>,
18425 ) -> Option<BackgroundHighlight> {
18426 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18427 if !text_highlights.1.is_empty() {
18428 self.scrollbar_marker_state.dirty = true;
18429 cx.notify();
18430 }
18431 Some(text_highlights)
18432 }
18433
18434 pub fn highlight_gutter<T: 'static>(
18435 &mut self,
18436 ranges: impl Into<Vec<Range<Anchor>>>,
18437 color_fetcher: fn(&App) -> Hsla,
18438 cx: &mut Context<Self>,
18439 ) {
18440 self.gutter_highlights
18441 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18442 cx.notify();
18443 }
18444
18445 pub fn clear_gutter_highlights<T: 'static>(
18446 &mut self,
18447 cx: &mut Context<Self>,
18448 ) -> Option<GutterHighlight> {
18449 cx.notify();
18450 self.gutter_highlights.remove(&TypeId::of::<T>())
18451 }
18452
18453 pub fn insert_gutter_highlight<T: 'static>(
18454 &mut self,
18455 range: Range<Anchor>,
18456 color_fetcher: fn(&App) -> Hsla,
18457 cx: &mut Context<Self>,
18458 ) {
18459 let snapshot = self.buffer().read(cx).snapshot(cx);
18460 let mut highlights = self
18461 .gutter_highlights
18462 .remove(&TypeId::of::<T>())
18463 .map(|(_, highlights)| highlights)
18464 .unwrap_or_default();
18465 let ix = highlights.binary_search_by(|highlight| {
18466 Ordering::Equal
18467 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18468 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18469 });
18470 if let Err(ix) = ix {
18471 highlights.insert(ix, range);
18472 }
18473 self.gutter_highlights
18474 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18475 }
18476
18477 pub fn remove_gutter_highlights<T: 'static>(
18478 &mut self,
18479 ranges_to_remove: Vec<Range<Anchor>>,
18480 cx: &mut Context<Self>,
18481 ) {
18482 let snapshot = self.buffer().read(cx).snapshot(cx);
18483 let Some((color_fetcher, mut gutter_highlights)) =
18484 self.gutter_highlights.remove(&TypeId::of::<T>())
18485 else {
18486 return;
18487 };
18488 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18489 gutter_highlights.retain(|highlight| {
18490 while let Some(range_to_remove) = ranges_to_remove.peek() {
18491 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18492 Ordering::Less | Ordering::Equal => {
18493 ranges_to_remove.next();
18494 }
18495 Ordering::Greater => {
18496 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18497 Ordering::Less | Ordering::Equal => {
18498 return false;
18499 }
18500 Ordering::Greater => break,
18501 }
18502 }
18503 }
18504 }
18505
18506 true
18507 });
18508 self.gutter_highlights
18509 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18510 }
18511
18512 #[cfg(feature = "test-support")]
18513 pub fn all_text_background_highlights(
18514 &self,
18515 window: &mut Window,
18516 cx: &mut Context<Self>,
18517 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18518 let snapshot = self.snapshot(window, cx);
18519 let buffer = &snapshot.buffer_snapshot;
18520 let start = buffer.anchor_before(0);
18521 let end = buffer.anchor_after(buffer.len());
18522 let theme = cx.theme().colors();
18523 self.background_highlights_in_range(start..end, &snapshot, theme)
18524 }
18525
18526 #[cfg(feature = "test-support")]
18527 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18528 let snapshot = self.buffer().read(cx).snapshot(cx);
18529
18530 let highlights = self
18531 .background_highlights
18532 .get(&TypeId::of::<items::BufferSearchHighlights>());
18533
18534 if let Some((_color, ranges)) = highlights {
18535 ranges
18536 .iter()
18537 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18538 .collect_vec()
18539 } else {
18540 vec![]
18541 }
18542 }
18543
18544 fn document_highlights_for_position<'a>(
18545 &'a self,
18546 position: Anchor,
18547 buffer: &'a MultiBufferSnapshot,
18548 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18549 let read_highlights = self
18550 .background_highlights
18551 .get(&TypeId::of::<DocumentHighlightRead>())
18552 .map(|h| &h.1);
18553 let write_highlights = self
18554 .background_highlights
18555 .get(&TypeId::of::<DocumentHighlightWrite>())
18556 .map(|h| &h.1);
18557 let left_position = position.bias_left(buffer);
18558 let right_position = position.bias_right(buffer);
18559 read_highlights
18560 .into_iter()
18561 .chain(write_highlights)
18562 .flat_map(move |ranges| {
18563 let start_ix = match ranges.binary_search_by(|probe| {
18564 let cmp = probe.end.cmp(&left_position, buffer);
18565 if cmp.is_ge() {
18566 Ordering::Greater
18567 } else {
18568 Ordering::Less
18569 }
18570 }) {
18571 Ok(i) | Err(i) => i,
18572 };
18573
18574 ranges[start_ix..]
18575 .iter()
18576 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18577 })
18578 }
18579
18580 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18581 self.background_highlights
18582 .get(&TypeId::of::<T>())
18583 .map_or(false, |(_, highlights)| !highlights.is_empty())
18584 }
18585
18586 pub fn background_highlights_in_range(
18587 &self,
18588 search_range: Range<Anchor>,
18589 display_snapshot: &DisplaySnapshot,
18590 theme: &ThemeColors,
18591 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18592 let mut results = Vec::new();
18593 for (color_fetcher, ranges) in self.background_highlights.values() {
18594 let color = color_fetcher(theme);
18595 let start_ix = match ranges.binary_search_by(|probe| {
18596 let cmp = probe
18597 .end
18598 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18599 if cmp.is_gt() {
18600 Ordering::Greater
18601 } else {
18602 Ordering::Less
18603 }
18604 }) {
18605 Ok(i) | Err(i) => i,
18606 };
18607 for range in &ranges[start_ix..] {
18608 if range
18609 .start
18610 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18611 .is_ge()
18612 {
18613 break;
18614 }
18615
18616 let start = range.start.to_display_point(display_snapshot);
18617 let end = range.end.to_display_point(display_snapshot);
18618 results.push((start..end, color))
18619 }
18620 }
18621 results
18622 }
18623
18624 pub fn background_highlight_row_ranges<T: 'static>(
18625 &self,
18626 search_range: Range<Anchor>,
18627 display_snapshot: &DisplaySnapshot,
18628 count: usize,
18629 ) -> Vec<RangeInclusive<DisplayPoint>> {
18630 let mut results = Vec::new();
18631 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18632 return vec![];
18633 };
18634
18635 let start_ix = match ranges.binary_search_by(|probe| {
18636 let cmp = probe
18637 .end
18638 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18639 if cmp.is_gt() {
18640 Ordering::Greater
18641 } else {
18642 Ordering::Less
18643 }
18644 }) {
18645 Ok(i) | Err(i) => i,
18646 };
18647 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18648 if let (Some(start_display), Some(end_display)) = (start, end) {
18649 results.push(
18650 start_display.to_display_point(display_snapshot)
18651 ..=end_display.to_display_point(display_snapshot),
18652 );
18653 }
18654 };
18655 let mut start_row: Option<Point> = None;
18656 let mut end_row: Option<Point> = None;
18657 if ranges.len() > count {
18658 return Vec::new();
18659 }
18660 for range in &ranges[start_ix..] {
18661 if range
18662 .start
18663 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18664 .is_ge()
18665 {
18666 break;
18667 }
18668 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18669 if let Some(current_row) = &end_row {
18670 if end.row == current_row.row {
18671 continue;
18672 }
18673 }
18674 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18675 if start_row.is_none() {
18676 assert_eq!(end_row, None);
18677 start_row = Some(start);
18678 end_row = Some(end);
18679 continue;
18680 }
18681 if let Some(current_end) = end_row.as_mut() {
18682 if start.row > current_end.row + 1 {
18683 push_region(start_row, end_row);
18684 start_row = Some(start);
18685 end_row = Some(end);
18686 } else {
18687 // Merge two hunks.
18688 *current_end = end;
18689 }
18690 } else {
18691 unreachable!();
18692 }
18693 }
18694 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18695 push_region(start_row, end_row);
18696 results
18697 }
18698
18699 pub fn gutter_highlights_in_range(
18700 &self,
18701 search_range: Range<Anchor>,
18702 display_snapshot: &DisplaySnapshot,
18703 cx: &App,
18704 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18705 let mut results = Vec::new();
18706 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18707 let color = color_fetcher(cx);
18708 let start_ix = match ranges.binary_search_by(|probe| {
18709 let cmp = probe
18710 .end
18711 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18712 if cmp.is_gt() {
18713 Ordering::Greater
18714 } else {
18715 Ordering::Less
18716 }
18717 }) {
18718 Ok(i) | Err(i) => i,
18719 };
18720 for range in &ranges[start_ix..] {
18721 if range
18722 .start
18723 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18724 .is_ge()
18725 {
18726 break;
18727 }
18728
18729 let start = range.start.to_display_point(display_snapshot);
18730 let end = range.end.to_display_point(display_snapshot);
18731 results.push((start..end, color))
18732 }
18733 }
18734 results
18735 }
18736
18737 /// Get the text ranges corresponding to the redaction query
18738 pub fn redacted_ranges(
18739 &self,
18740 search_range: Range<Anchor>,
18741 display_snapshot: &DisplaySnapshot,
18742 cx: &App,
18743 ) -> Vec<Range<DisplayPoint>> {
18744 display_snapshot
18745 .buffer_snapshot
18746 .redacted_ranges(search_range, |file| {
18747 if let Some(file) = file {
18748 file.is_private()
18749 && EditorSettings::get(
18750 Some(SettingsLocation {
18751 worktree_id: file.worktree_id(cx),
18752 path: file.path().as_ref(),
18753 }),
18754 cx,
18755 )
18756 .redact_private_values
18757 } else {
18758 false
18759 }
18760 })
18761 .map(|range| {
18762 range.start.to_display_point(display_snapshot)
18763 ..range.end.to_display_point(display_snapshot)
18764 })
18765 .collect()
18766 }
18767
18768 pub fn highlight_text<T: 'static>(
18769 &mut self,
18770 ranges: Vec<Range<Anchor>>,
18771 style: HighlightStyle,
18772 cx: &mut Context<Self>,
18773 ) {
18774 self.display_map.update(cx, |map, _| {
18775 map.highlight_text(TypeId::of::<T>(), ranges, style)
18776 });
18777 cx.notify();
18778 }
18779
18780 pub(crate) fn highlight_inlays<T: 'static>(
18781 &mut self,
18782 highlights: Vec<InlayHighlight>,
18783 style: HighlightStyle,
18784 cx: &mut Context<Self>,
18785 ) {
18786 self.display_map.update(cx, |map, _| {
18787 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18788 });
18789 cx.notify();
18790 }
18791
18792 pub fn text_highlights<'a, T: 'static>(
18793 &'a self,
18794 cx: &'a App,
18795 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18796 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18797 }
18798
18799 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18800 let cleared = self
18801 .display_map
18802 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18803 if cleared {
18804 cx.notify();
18805 }
18806 }
18807
18808 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18809 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18810 && self.focus_handle.is_focused(window)
18811 }
18812
18813 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18814 self.show_cursor_when_unfocused = is_enabled;
18815 cx.notify();
18816 }
18817
18818 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18819 cx.notify();
18820 }
18821
18822 fn on_debug_session_event(
18823 &mut self,
18824 _session: Entity<Session>,
18825 event: &SessionEvent,
18826 cx: &mut Context<Self>,
18827 ) {
18828 match event {
18829 SessionEvent::InvalidateInlineValue => {
18830 self.refresh_inline_values(cx);
18831 }
18832 _ => {}
18833 }
18834 }
18835
18836 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18837 let Some(project) = self.project.clone() else {
18838 return;
18839 };
18840
18841 if !self.inline_value_cache.enabled {
18842 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18843 self.splice_inlays(&inlays, Vec::new(), cx);
18844 return;
18845 }
18846
18847 let current_execution_position = self
18848 .highlighted_rows
18849 .get(&TypeId::of::<ActiveDebugLine>())
18850 .and_then(|lines| lines.last().map(|line| line.range.start));
18851
18852 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18853 let inline_values = editor
18854 .update(cx, |editor, cx| {
18855 let Some(current_execution_position) = current_execution_position else {
18856 return Some(Task::ready(Ok(Vec::new())));
18857 };
18858
18859 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18860 let snapshot = buffer.snapshot(cx);
18861
18862 let excerpt = snapshot.excerpt_containing(
18863 current_execution_position..current_execution_position,
18864 )?;
18865
18866 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18867 })?;
18868
18869 let range =
18870 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18871
18872 project.inline_values(buffer, range, cx)
18873 })
18874 .ok()
18875 .flatten()?
18876 .await
18877 .context("refreshing debugger inlays")
18878 .log_err()?;
18879
18880 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18881
18882 for (buffer_id, inline_value) in inline_values
18883 .into_iter()
18884 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18885 {
18886 buffer_inline_values
18887 .entry(buffer_id)
18888 .or_default()
18889 .push(inline_value);
18890 }
18891
18892 editor
18893 .update(cx, |editor, cx| {
18894 let snapshot = editor.buffer.read(cx).snapshot(cx);
18895 let mut new_inlays = Vec::default();
18896
18897 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18898 let buffer_id = buffer_snapshot.remote_id();
18899 buffer_inline_values
18900 .get(&buffer_id)
18901 .into_iter()
18902 .flatten()
18903 .for_each(|hint| {
18904 let inlay = Inlay::debugger_hint(
18905 post_inc(&mut editor.next_inlay_id),
18906 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18907 hint.text(),
18908 );
18909
18910 new_inlays.push(inlay);
18911 });
18912 }
18913
18914 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18915 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18916
18917 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18918 })
18919 .ok()?;
18920 Some(())
18921 });
18922 }
18923
18924 fn on_buffer_event(
18925 &mut self,
18926 multibuffer: &Entity<MultiBuffer>,
18927 event: &multi_buffer::Event,
18928 window: &mut Window,
18929 cx: &mut Context<Self>,
18930 ) {
18931 match event {
18932 multi_buffer::Event::Edited {
18933 singleton_buffer_edited,
18934 edited_buffer,
18935 } => {
18936 self.scrollbar_marker_state.dirty = true;
18937 self.active_indent_guides_state.dirty = true;
18938 self.refresh_active_diagnostics(cx);
18939 self.refresh_code_actions(window, cx);
18940 self.refresh_selected_text_highlights(true, window, cx);
18941 refresh_matching_bracket_highlights(self, window, cx);
18942 if self.has_active_inline_completion() {
18943 self.update_visible_inline_completion(window, cx);
18944 }
18945 if let Some(project) = self.project.as_ref() {
18946 if let Some(edited_buffer) = edited_buffer {
18947 project.update(cx, |project, cx| {
18948 self.registered_buffers
18949 .entry(edited_buffer.read(cx).remote_id())
18950 .or_insert_with(|| {
18951 project
18952 .register_buffer_with_language_servers(&edited_buffer, cx)
18953 });
18954 });
18955 if edited_buffer.read(cx).file().is_some() {
18956 self.pull_diagnostics(
18957 Some(edited_buffer.read(cx).remote_id()),
18958 window,
18959 cx,
18960 );
18961 }
18962 }
18963 }
18964 cx.emit(EditorEvent::BufferEdited);
18965 cx.emit(SearchEvent::MatchesInvalidated);
18966 if *singleton_buffer_edited {
18967 if let Some(buffer) = edited_buffer {
18968 if buffer.read(cx).file().is_none() {
18969 cx.emit(EditorEvent::TitleChanged);
18970 }
18971 }
18972 if let Some(project) = &self.project {
18973 #[allow(clippy::mutable_key_type)]
18974 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18975 multibuffer
18976 .all_buffers()
18977 .into_iter()
18978 .filter_map(|buffer| {
18979 buffer.update(cx, |buffer, cx| {
18980 let language = buffer.language()?;
18981 let should_discard = project.update(cx, |project, cx| {
18982 project.is_local()
18983 && !project.has_language_servers_for(buffer, cx)
18984 });
18985 should_discard.not().then_some(language.clone())
18986 })
18987 })
18988 .collect::<HashSet<_>>()
18989 });
18990 if !languages_affected.is_empty() {
18991 self.refresh_inlay_hints(
18992 InlayHintRefreshReason::BufferEdited(languages_affected),
18993 cx,
18994 );
18995 }
18996 }
18997 }
18998
18999 let Some(project) = &self.project else { return };
19000 let (telemetry, is_via_ssh) = {
19001 let project = project.read(cx);
19002 let telemetry = project.client().telemetry().clone();
19003 let is_via_ssh = project.is_via_ssh();
19004 (telemetry, is_via_ssh)
19005 };
19006 refresh_linked_ranges(self, window, cx);
19007 telemetry.log_edit_event("editor", is_via_ssh);
19008 }
19009 multi_buffer::Event::ExcerptsAdded {
19010 buffer,
19011 predecessor,
19012 excerpts,
19013 } => {
19014 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19015 let buffer_id = buffer.read(cx).remote_id();
19016 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19017 if let Some(project) = &self.project {
19018 update_uncommitted_diff_for_buffer(
19019 cx.entity(),
19020 project,
19021 [buffer.clone()],
19022 self.buffer.clone(),
19023 cx,
19024 )
19025 .detach();
19026 }
19027 }
19028 cx.emit(EditorEvent::ExcerptsAdded {
19029 buffer: buffer.clone(),
19030 predecessor: *predecessor,
19031 excerpts: excerpts.clone(),
19032 });
19033 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19034 }
19035 multi_buffer::Event::ExcerptsRemoved {
19036 ids,
19037 removed_buffer_ids,
19038 } => {
19039 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19040 let buffer = self.buffer.read(cx);
19041 self.registered_buffers
19042 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19043 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19044 cx.emit(EditorEvent::ExcerptsRemoved {
19045 ids: ids.clone(),
19046 removed_buffer_ids: removed_buffer_ids.clone(),
19047 })
19048 }
19049 multi_buffer::Event::ExcerptsEdited {
19050 excerpt_ids,
19051 buffer_ids,
19052 } => {
19053 self.display_map.update(cx, |map, cx| {
19054 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19055 });
19056 cx.emit(EditorEvent::ExcerptsEdited {
19057 ids: excerpt_ids.clone(),
19058 })
19059 }
19060 multi_buffer::Event::ExcerptsExpanded { ids } => {
19061 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19062 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19063 }
19064 multi_buffer::Event::Reparsed(buffer_id) => {
19065 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19066 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19067
19068 cx.emit(EditorEvent::Reparsed(*buffer_id));
19069 }
19070 multi_buffer::Event::DiffHunksToggled => {
19071 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19072 }
19073 multi_buffer::Event::LanguageChanged(buffer_id) => {
19074 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19075 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19076 cx.emit(EditorEvent::Reparsed(*buffer_id));
19077 cx.notify();
19078 }
19079 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19080 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19081 multi_buffer::Event::FileHandleChanged
19082 | multi_buffer::Event::Reloaded
19083 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19084 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19085 multi_buffer::Event::DiagnosticsUpdated => {
19086 self.update_diagnostics_state(window, cx);
19087 }
19088 _ => {}
19089 };
19090 }
19091
19092 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19093 self.refresh_active_diagnostics(cx);
19094 self.refresh_inline_diagnostics(true, window, cx);
19095 self.scrollbar_marker_state.dirty = true;
19096 cx.notify();
19097 }
19098
19099 pub fn start_temporary_diff_override(&mut self) {
19100 self.load_diff_task.take();
19101 self.temporary_diff_override = true;
19102 }
19103
19104 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19105 self.temporary_diff_override = false;
19106 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19107 self.buffer.update(cx, |buffer, cx| {
19108 buffer.set_all_diff_hunks_collapsed(cx);
19109 });
19110
19111 if let Some(project) = self.project.clone() {
19112 self.load_diff_task = Some(
19113 update_uncommitted_diff_for_buffer(
19114 cx.entity(),
19115 &project,
19116 self.buffer.read(cx).all_buffers(),
19117 self.buffer.clone(),
19118 cx,
19119 )
19120 .shared(),
19121 );
19122 }
19123 }
19124
19125 fn on_display_map_changed(
19126 &mut self,
19127 _: Entity<DisplayMap>,
19128 _: &mut Window,
19129 cx: &mut Context<Self>,
19130 ) {
19131 cx.notify();
19132 }
19133
19134 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19135 let new_severity = if self.diagnostics_enabled() {
19136 EditorSettings::get_global(cx)
19137 .diagnostics_max_severity
19138 .unwrap_or(DiagnosticSeverity::Hint)
19139 } else {
19140 DiagnosticSeverity::Off
19141 };
19142 self.set_max_diagnostics_severity(new_severity, cx);
19143 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19144 self.update_edit_prediction_settings(cx);
19145 self.refresh_inline_completion(true, false, window, cx);
19146 self.refresh_inlay_hints(
19147 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19148 self.selections.newest_anchor().head(),
19149 &self.buffer.read(cx).snapshot(cx),
19150 cx,
19151 )),
19152 cx,
19153 );
19154
19155 let old_cursor_shape = self.cursor_shape;
19156
19157 {
19158 let editor_settings = EditorSettings::get_global(cx);
19159 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19160 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19161 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19162 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19163 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19164 }
19165
19166 if old_cursor_shape != self.cursor_shape {
19167 cx.emit(EditorEvent::CursorShapeChanged);
19168 }
19169
19170 let project_settings = ProjectSettings::get_global(cx);
19171 self.serialize_dirty_buffers =
19172 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19173
19174 if self.mode.is_full() {
19175 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19176 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19177 if self.show_inline_diagnostics != show_inline_diagnostics {
19178 self.show_inline_diagnostics = show_inline_diagnostics;
19179 self.refresh_inline_diagnostics(false, window, cx);
19180 }
19181
19182 if self.git_blame_inline_enabled != inline_blame_enabled {
19183 self.toggle_git_blame_inline_internal(false, window, cx);
19184 }
19185
19186 let minimap_settings = EditorSettings::get_global(cx).minimap;
19187 if self.minimap_visibility != MinimapVisibility::Disabled {
19188 if self.minimap_visibility.settings_visibility()
19189 != minimap_settings.minimap_enabled()
19190 {
19191 self.set_minimap_visibility(
19192 MinimapVisibility::for_mode(self.mode(), cx),
19193 window,
19194 cx,
19195 );
19196 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19197 minimap_entity.update(cx, |minimap_editor, cx| {
19198 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19199 })
19200 }
19201 }
19202 }
19203
19204 cx.notify();
19205 }
19206
19207 pub fn set_searchable(&mut self, searchable: bool) {
19208 self.searchable = searchable;
19209 }
19210
19211 pub fn searchable(&self) -> bool {
19212 self.searchable
19213 }
19214
19215 fn open_proposed_changes_editor(
19216 &mut self,
19217 _: &OpenProposedChangesEditor,
19218 window: &mut Window,
19219 cx: &mut Context<Self>,
19220 ) {
19221 let Some(workspace) = self.workspace() else {
19222 cx.propagate();
19223 return;
19224 };
19225
19226 let selections = self.selections.all::<usize>(cx);
19227 let multi_buffer = self.buffer.read(cx);
19228 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19229 let mut new_selections_by_buffer = HashMap::default();
19230 for selection in selections {
19231 for (buffer, range, _) in
19232 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19233 {
19234 let mut range = range.to_point(buffer);
19235 range.start.column = 0;
19236 range.end.column = buffer.line_len(range.end.row);
19237 new_selections_by_buffer
19238 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19239 .or_insert(Vec::new())
19240 .push(range)
19241 }
19242 }
19243
19244 let proposed_changes_buffers = new_selections_by_buffer
19245 .into_iter()
19246 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19247 .collect::<Vec<_>>();
19248 let proposed_changes_editor = cx.new(|cx| {
19249 ProposedChangesEditor::new(
19250 "Proposed changes",
19251 proposed_changes_buffers,
19252 self.project.clone(),
19253 window,
19254 cx,
19255 )
19256 });
19257
19258 window.defer(cx, move |window, cx| {
19259 workspace.update(cx, |workspace, cx| {
19260 workspace.active_pane().update(cx, |pane, cx| {
19261 pane.add_item(
19262 Box::new(proposed_changes_editor),
19263 true,
19264 true,
19265 None,
19266 window,
19267 cx,
19268 );
19269 });
19270 });
19271 });
19272 }
19273
19274 pub fn open_excerpts_in_split(
19275 &mut self,
19276 _: &OpenExcerptsSplit,
19277 window: &mut Window,
19278 cx: &mut Context<Self>,
19279 ) {
19280 self.open_excerpts_common(None, true, window, cx)
19281 }
19282
19283 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19284 self.open_excerpts_common(None, false, window, cx)
19285 }
19286
19287 fn open_excerpts_common(
19288 &mut self,
19289 jump_data: Option<JumpData>,
19290 split: bool,
19291 window: &mut Window,
19292 cx: &mut Context<Self>,
19293 ) {
19294 let Some(workspace) = self.workspace() else {
19295 cx.propagate();
19296 return;
19297 };
19298
19299 if self.buffer.read(cx).is_singleton() {
19300 cx.propagate();
19301 return;
19302 }
19303
19304 let mut new_selections_by_buffer = HashMap::default();
19305 match &jump_data {
19306 Some(JumpData::MultiBufferPoint {
19307 excerpt_id,
19308 position,
19309 anchor,
19310 line_offset_from_top,
19311 }) => {
19312 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19313 if let Some(buffer) = multi_buffer_snapshot
19314 .buffer_id_for_excerpt(*excerpt_id)
19315 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19316 {
19317 let buffer_snapshot = buffer.read(cx).snapshot();
19318 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19319 language::ToPoint::to_point(anchor, &buffer_snapshot)
19320 } else {
19321 buffer_snapshot.clip_point(*position, Bias::Left)
19322 };
19323 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19324 new_selections_by_buffer.insert(
19325 buffer,
19326 (
19327 vec![jump_to_offset..jump_to_offset],
19328 Some(*line_offset_from_top),
19329 ),
19330 );
19331 }
19332 }
19333 Some(JumpData::MultiBufferRow {
19334 row,
19335 line_offset_from_top,
19336 }) => {
19337 let point = MultiBufferPoint::new(row.0, 0);
19338 if let Some((buffer, buffer_point, _)) =
19339 self.buffer.read(cx).point_to_buffer_point(point, cx)
19340 {
19341 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19342 new_selections_by_buffer
19343 .entry(buffer)
19344 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19345 .0
19346 .push(buffer_offset..buffer_offset)
19347 }
19348 }
19349 None => {
19350 let selections = self.selections.all::<usize>(cx);
19351 let multi_buffer = self.buffer.read(cx);
19352 for selection in selections {
19353 for (snapshot, range, _, anchor) in multi_buffer
19354 .snapshot(cx)
19355 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19356 {
19357 if let Some(anchor) = anchor {
19358 // selection is in a deleted hunk
19359 let Some(buffer_id) = anchor.buffer_id else {
19360 continue;
19361 };
19362 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19363 continue;
19364 };
19365 let offset = text::ToOffset::to_offset(
19366 &anchor.text_anchor,
19367 &buffer_handle.read(cx).snapshot(),
19368 );
19369 let range = offset..offset;
19370 new_selections_by_buffer
19371 .entry(buffer_handle)
19372 .or_insert((Vec::new(), None))
19373 .0
19374 .push(range)
19375 } else {
19376 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19377 else {
19378 continue;
19379 };
19380 new_selections_by_buffer
19381 .entry(buffer_handle)
19382 .or_insert((Vec::new(), None))
19383 .0
19384 .push(range)
19385 }
19386 }
19387 }
19388 }
19389 }
19390
19391 new_selections_by_buffer
19392 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19393
19394 if new_selections_by_buffer.is_empty() {
19395 return;
19396 }
19397
19398 // We defer the pane interaction because we ourselves are a workspace item
19399 // and activating a new item causes the pane to call a method on us reentrantly,
19400 // which panics if we're on the stack.
19401 window.defer(cx, move |window, cx| {
19402 workspace.update(cx, |workspace, cx| {
19403 let pane = if split {
19404 workspace.adjacent_pane(window, cx)
19405 } else {
19406 workspace.active_pane().clone()
19407 };
19408
19409 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19410 let editor = buffer
19411 .read(cx)
19412 .file()
19413 .is_none()
19414 .then(|| {
19415 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19416 // so `workspace.open_project_item` will never find them, always opening a new editor.
19417 // Instead, we try to activate the existing editor in the pane first.
19418 let (editor, pane_item_index) =
19419 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19420 let editor = item.downcast::<Editor>()?;
19421 let singleton_buffer =
19422 editor.read(cx).buffer().read(cx).as_singleton()?;
19423 if singleton_buffer == buffer {
19424 Some((editor, i))
19425 } else {
19426 None
19427 }
19428 })?;
19429 pane.update(cx, |pane, cx| {
19430 pane.activate_item(pane_item_index, true, true, window, cx)
19431 });
19432 Some(editor)
19433 })
19434 .flatten()
19435 .unwrap_or_else(|| {
19436 workspace.open_project_item::<Self>(
19437 pane.clone(),
19438 buffer,
19439 true,
19440 true,
19441 window,
19442 cx,
19443 )
19444 });
19445
19446 editor.update(cx, |editor, cx| {
19447 let autoscroll = match scroll_offset {
19448 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19449 None => Autoscroll::newest(),
19450 };
19451 let nav_history = editor.nav_history.take();
19452 editor.change_selections(Some(autoscroll), window, cx, |s| {
19453 s.select_ranges(ranges);
19454 });
19455 editor.nav_history = nav_history;
19456 });
19457 }
19458 })
19459 });
19460 }
19461
19462 // For now, don't allow opening excerpts in buffers that aren't backed by
19463 // regular project files.
19464 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19465 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19466 }
19467
19468 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19469 let snapshot = self.buffer.read(cx).read(cx);
19470 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19471 Some(
19472 ranges
19473 .iter()
19474 .map(move |range| {
19475 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19476 })
19477 .collect(),
19478 )
19479 }
19480
19481 fn selection_replacement_ranges(
19482 &self,
19483 range: Range<OffsetUtf16>,
19484 cx: &mut App,
19485 ) -> Vec<Range<OffsetUtf16>> {
19486 let selections = self.selections.all::<OffsetUtf16>(cx);
19487 let newest_selection = selections
19488 .iter()
19489 .max_by_key(|selection| selection.id)
19490 .unwrap();
19491 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19492 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19493 let snapshot = self.buffer.read(cx).read(cx);
19494 selections
19495 .into_iter()
19496 .map(|mut selection| {
19497 selection.start.0 =
19498 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19499 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19500 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19501 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19502 })
19503 .collect()
19504 }
19505
19506 fn report_editor_event(
19507 &self,
19508 event_type: &'static str,
19509 file_extension: Option<String>,
19510 cx: &App,
19511 ) {
19512 if cfg!(any(test, feature = "test-support")) {
19513 return;
19514 }
19515
19516 let Some(project) = &self.project else { return };
19517
19518 // If None, we are in a file without an extension
19519 let file = self
19520 .buffer
19521 .read(cx)
19522 .as_singleton()
19523 .and_then(|b| b.read(cx).file());
19524 let file_extension = file_extension.or(file
19525 .as_ref()
19526 .and_then(|file| Path::new(file.file_name(cx)).extension())
19527 .and_then(|e| e.to_str())
19528 .map(|a| a.to_string()));
19529
19530 let vim_mode = vim_enabled(cx);
19531
19532 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19533 let copilot_enabled = edit_predictions_provider
19534 == language::language_settings::EditPredictionProvider::Copilot;
19535 let copilot_enabled_for_language = self
19536 .buffer
19537 .read(cx)
19538 .language_settings(cx)
19539 .show_edit_predictions;
19540
19541 let project = project.read(cx);
19542 telemetry::event!(
19543 event_type,
19544 file_extension,
19545 vim_mode,
19546 copilot_enabled,
19547 copilot_enabled_for_language,
19548 edit_predictions_provider,
19549 is_via_ssh = project.is_via_ssh(),
19550 );
19551 }
19552
19553 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19554 /// with each line being an array of {text, highlight} objects.
19555 fn copy_highlight_json(
19556 &mut self,
19557 _: &CopyHighlightJson,
19558 window: &mut Window,
19559 cx: &mut Context<Self>,
19560 ) {
19561 #[derive(Serialize)]
19562 struct Chunk<'a> {
19563 text: String,
19564 highlight: Option<&'a str>,
19565 }
19566
19567 let snapshot = self.buffer.read(cx).snapshot(cx);
19568 let range = self
19569 .selected_text_range(false, window, cx)
19570 .and_then(|selection| {
19571 if selection.range.is_empty() {
19572 None
19573 } else {
19574 Some(selection.range)
19575 }
19576 })
19577 .unwrap_or_else(|| 0..snapshot.len());
19578
19579 let chunks = snapshot.chunks(range, true);
19580 let mut lines = Vec::new();
19581 let mut line: VecDeque<Chunk> = VecDeque::new();
19582
19583 let Some(style) = self.style.as_ref() else {
19584 return;
19585 };
19586
19587 for chunk in chunks {
19588 let highlight = chunk
19589 .syntax_highlight_id
19590 .and_then(|id| id.name(&style.syntax));
19591 let mut chunk_lines = chunk.text.split('\n').peekable();
19592 while let Some(text) = chunk_lines.next() {
19593 let mut merged_with_last_token = false;
19594 if let Some(last_token) = line.back_mut() {
19595 if last_token.highlight == highlight {
19596 last_token.text.push_str(text);
19597 merged_with_last_token = true;
19598 }
19599 }
19600
19601 if !merged_with_last_token {
19602 line.push_back(Chunk {
19603 text: text.into(),
19604 highlight,
19605 });
19606 }
19607
19608 if chunk_lines.peek().is_some() {
19609 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19610 line.pop_front();
19611 }
19612 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19613 line.pop_back();
19614 }
19615
19616 lines.push(mem::take(&mut line));
19617 }
19618 }
19619 }
19620
19621 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19622 return;
19623 };
19624 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19625 }
19626
19627 pub fn open_context_menu(
19628 &mut self,
19629 _: &OpenContextMenu,
19630 window: &mut Window,
19631 cx: &mut Context<Self>,
19632 ) {
19633 self.request_autoscroll(Autoscroll::newest(), cx);
19634 let position = self.selections.newest_display(cx).start;
19635 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19636 }
19637
19638 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19639 &self.inlay_hint_cache
19640 }
19641
19642 pub fn replay_insert_event(
19643 &mut self,
19644 text: &str,
19645 relative_utf16_range: Option<Range<isize>>,
19646 window: &mut Window,
19647 cx: &mut Context<Self>,
19648 ) {
19649 if !self.input_enabled {
19650 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19651 return;
19652 }
19653 if let Some(relative_utf16_range) = relative_utf16_range {
19654 let selections = self.selections.all::<OffsetUtf16>(cx);
19655 self.change_selections(None, window, cx, |s| {
19656 let new_ranges = selections.into_iter().map(|range| {
19657 let start = OffsetUtf16(
19658 range
19659 .head()
19660 .0
19661 .saturating_add_signed(relative_utf16_range.start),
19662 );
19663 let end = OffsetUtf16(
19664 range
19665 .head()
19666 .0
19667 .saturating_add_signed(relative_utf16_range.end),
19668 );
19669 start..end
19670 });
19671 s.select_ranges(new_ranges);
19672 });
19673 }
19674
19675 self.handle_input(text, window, cx);
19676 }
19677
19678 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19679 let Some(provider) = self.semantics_provider.as_ref() else {
19680 return false;
19681 };
19682
19683 let mut supports = false;
19684 self.buffer().update(cx, |this, cx| {
19685 this.for_each_buffer(|buffer| {
19686 supports |= provider.supports_inlay_hints(buffer, cx);
19687 });
19688 });
19689
19690 supports
19691 }
19692
19693 pub fn is_focused(&self, window: &Window) -> bool {
19694 self.focus_handle.is_focused(window)
19695 }
19696
19697 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19698 cx.emit(EditorEvent::Focused);
19699
19700 if let Some(descendant) = self
19701 .last_focused_descendant
19702 .take()
19703 .and_then(|descendant| descendant.upgrade())
19704 {
19705 window.focus(&descendant);
19706 } else {
19707 if let Some(blame) = self.blame.as_ref() {
19708 blame.update(cx, GitBlame::focus)
19709 }
19710
19711 self.blink_manager.update(cx, BlinkManager::enable);
19712 self.show_cursor_names(window, cx);
19713 self.buffer.update(cx, |buffer, cx| {
19714 buffer.finalize_last_transaction(cx);
19715 if self.leader_id.is_none() {
19716 buffer.set_active_selections(
19717 &self.selections.disjoint_anchors(),
19718 self.selections.line_mode,
19719 self.cursor_shape,
19720 cx,
19721 );
19722 }
19723 });
19724 }
19725 }
19726
19727 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19728 cx.emit(EditorEvent::FocusedIn)
19729 }
19730
19731 fn handle_focus_out(
19732 &mut self,
19733 event: FocusOutEvent,
19734 _window: &mut Window,
19735 cx: &mut Context<Self>,
19736 ) {
19737 if event.blurred != self.focus_handle {
19738 self.last_focused_descendant = Some(event.blurred);
19739 }
19740 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19741 }
19742
19743 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19744 self.blink_manager.update(cx, BlinkManager::disable);
19745 self.buffer
19746 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19747
19748 if let Some(blame) = self.blame.as_ref() {
19749 blame.update(cx, GitBlame::blur)
19750 }
19751 if !self.hover_state.focused(window, cx) {
19752 hide_hover(self, cx);
19753 }
19754 if !self
19755 .context_menu
19756 .borrow()
19757 .as_ref()
19758 .is_some_and(|context_menu| context_menu.focused(window, cx))
19759 {
19760 self.hide_context_menu(window, cx);
19761 }
19762 self.discard_inline_completion(false, cx);
19763 cx.emit(EditorEvent::Blurred);
19764 cx.notify();
19765 }
19766
19767 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19768 let mut pending: String = window
19769 .pending_input_keystrokes()
19770 .into_iter()
19771 .flatten()
19772 .filter_map(|keystroke| {
19773 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19774 keystroke.key_char.clone()
19775 } else {
19776 None
19777 }
19778 })
19779 .collect();
19780
19781 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19782 pending = "".to_string();
19783 }
19784
19785 let existing_pending = self
19786 .text_highlights::<PendingInput>(cx)
19787 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19788 if existing_pending.is_none() && pending.is_empty() {
19789 return;
19790 }
19791 let transaction =
19792 self.transact(window, cx, |this, window, cx| {
19793 let selections = this.selections.all::<usize>(cx);
19794 let edits = selections
19795 .iter()
19796 .map(|selection| (selection.end..selection.end, pending.clone()));
19797 this.edit(edits, cx);
19798 this.change_selections(None, window, cx, |s| {
19799 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19800 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19801 }));
19802 });
19803 if let Some(existing_ranges) = existing_pending {
19804 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19805 this.edit(edits, cx);
19806 }
19807 });
19808
19809 let snapshot = self.snapshot(window, cx);
19810 let ranges = self
19811 .selections
19812 .all::<usize>(cx)
19813 .into_iter()
19814 .map(|selection| {
19815 snapshot.buffer_snapshot.anchor_after(selection.end)
19816 ..snapshot
19817 .buffer_snapshot
19818 .anchor_before(selection.end + pending.len())
19819 })
19820 .collect();
19821
19822 if pending.is_empty() {
19823 self.clear_highlights::<PendingInput>(cx);
19824 } else {
19825 self.highlight_text::<PendingInput>(
19826 ranges,
19827 HighlightStyle {
19828 underline: Some(UnderlineStyle {
19829 thickness: px(1.),
19830 color: None,
19831 wavy: false,
19832 }),
19833 ..Default::default()
19834 },
19835 cx,
19836 );
19837 }
19838
19839 self.ime_transaction = self.ime_transaction.or(transaction);
19840 if let Some(transaction) = self.ime_transaction {
19841 self.buffer.update(cx, |buffer, cx| {
19842 buffer.group_until_transaction(transaction, cx);
19843 });
19844 }
19845
19846 if self.text_highlights::<PendingInput>(cx).is_none() {
19847 self.ime_transaction.take();
19848 }
19849 }
19850
19851 pub fn register_action_renderer(
19852 &mut self,
19853 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19854 ) -> Subscription {
19855 let id = self.next_editor_action_id.post_inc();
19856 self.editor_actions
19857 .borrow_mut()
19858 .insert(id, Box::new(listener));
19859
19860 let editor_actions = self.editor_actions.clone();
19861 Subscription::new(move || {
19862 editor_actions.borrow_mut().remove(&id);
19863 })
19864 }
19865
19866 pub fn register_action<A: Action>(
19867 &mut self,
19868 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19869 ) -> Subscription {
19870 let id = self.next_editor_action_id.post_inc();
19871 let listener = Arc::new(listener);
19872 self.editor_actions.borrow_mut().insert(
19873 id,
19874 Box::new(move |_, window, _| {
19875 let listener = listener.clone();
19876 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19877 let action = action.downcast_ref().unwrap();
19878 if phase == DispatchPhase::Bubble {
19879 listener(action, window, cx)
19880 }
19881 })
19882 }),
19883 );
19884
19885 let editor_actions = self.editor_actions.clone();
19886 Subscription::new(move || {
19887 editor_actions.borrow_mut().remove(&id);
19888 })
19889 }
19890
19891 pub fn file_header_size(&self) -> u32 {
19892 FILE_HEADER_HEIGHT
19893 }
19894
19895 pub fn restore(
19896 &mut self,
19897 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19898 window: &mut Window,
19899 cx: &mut Context<Self>,
19900 ) {
19901 let workspace = self.workspace();
19902 let project = self.project.as_ref();
19903 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19904 let mut tasks = Vec::new();
19905 for (buffer_id, changes) in revert_changes {
19906 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19907 buffer.update(cx, |buffer, cx| {
19908 buffer.edit(
19909 changes
19910 .into_iter()
19911 .map(|(range, text)| (range, text.to_string())),
19912 None,
19913 cx,
19914 );
19915 });
19916
19917 if let Some(project) =
19918 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19919 {
19920 project.update(cx, |project, cx| {
19921 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19922 })
19923 }
19924 }
19925 }
19926 tasks
19927 });
19928 cx.spawn_in(window, async move |_, cx| {
19929 for (buffer, task) in save_tasks {
19930 let result = task.await;
19931 if result.is_err() {
19932 let Some(path) = buffer
19933 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19934 .ok()
19935 else {
19936 continue;
19937 };
19938 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19939 let Some(task) = cx
19940 .update_window_entity(&workspace, |workspace, window, cx| {
19941 workspace
19942 .open_path_preview(path, None, false, false, false, window, cx)
19943 })
19944 .ok()
19945 else {
19946 continue;
19947 };
19948 task.await.log_err();
19949 }
19950 }
19951 }
19952 })
19953 .detach();
19954 self.change_selections(None, window, cx, |selections| selections.refresh());
19955 }
19956
19957 pub fn to_pixel_point(
19958 &self,
19959 source: multi_buffer::Anchor,
19960 editor_snapshot: &EditorSnapshot,
19961 window: &mut Window,
19962 ) -> Option<gpui::Point<Pixels>> {
19963 let source_point = source.to_display_point(editor_snapshot);
19964 self.display_to_pixel_point(source_point, editor_snapshot, window)
19965 }
19966
19967 pub fn display_to_pixel_point(
19968 &self,
19969 source: DisplayPoint,
19970 editor_snapshot: &EditorSnapshot,
19971 window: &mut Window,
19972 ) -> Option<gpui::Point<Pixels>> {
19973 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19974 let text_layout_details = self.text_layout_details(window);
19975 let scroll_top = text_layout_details
19976 .scroll_anchor
19977 .scroll_position(editor_snapshot)
19978 .y;
19979
19980 if source.row().as_f32() < scroll_top.floor() {
19981 return None;
19982 }
19983 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19984 let source_y = line_height * (source.row().as_f32() - scroll_top);
19985 Some(gpui::Point::new(source_x, source_y))
19986 }
19987
19988 pub fn has_visible_completions_menu(&self) -> bool {
19989 !self.edit_prediction_preview_is_active()
19990 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19991 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19992 })
19993 }
19994
19995 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19996 if self.mode.is_minimap() {
19997 return;
19998 }
19999 self.addons
20000 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20001 }
20002
20003 pub fn unregister_addon<T: Addon>(&mut self) {
20004 self.addons.remove(&std::any::TypeId::of::<T>());
20005 }
20006
20007 pub fn addon<T: Addon>(&self) -> Option<&T> {
20008 let type_id = std::any::TypeId::of::<T>();
20009 self.addons
20010 .get(&type_id)
20011 .and_then(|item| item.to_any().downcast_ref::<T>())
20012 }
20013
20014 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20015 let type_id = std::any::TypeId::of::<T>();
20016 self.addons
20017 .get_mut(&type_id)
20018 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20019 }
20020
20021 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20022 let text_layout_details = self.text_layout_details(window);
20023 let style = &text_layout_details.editor_style;
20024 let font_id = window.text_system().resolve_font(&style.text.font());
20025 let font_size = style.text.font_size.to_pixels(window.rem_size());
20026 let line_height = style.text.line_height_in_pixels(window.rem_size());
20027 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20028
20029 gpui::Size::new(em_width, line_height)
20030 }
20031
20032 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20033 self.load_diff_task.clone()
20034 }
20035
20036 fn read_metadata_from_db(
20037 &mut self,
20038 item_id: u64,
20039 workspace_id: WorkspaceId,
20040 window: &mut Window,
20041 cx: &mut Context<Editor>,
20042 ) {
20043 if self.is_singleton(cx)
20044 && !self.mode.is_minimap()
20045 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20046 {
20047 let buffer_snapshot = OnceCell::new();
20048
20049 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20050 if !folds.is_empty() {
20051 let snapshot =
20052 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20053 self.fold_ranges(
20054 folds
20055 .into_iter()
20056 .map(|(start, end)| {
20057 snapshot.clip_offset(start, Bias::Left)
20058 ..snapshot.clip_offset(end, Bias::Right)
20059 })
20060 .collect(),
20061 false,
20062 window,
20063 cx,
20064 );
20065 }
20066 }
20067
20068 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20069 if !selections.is_empty() {
20070 let snapshot =
20071 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20072 // skip adding the initial selection to selection history
20073 self.selection_history.mode = SelectionHistoryMode::Skipping;
20074 self.change_selections(None, window, cx, |s| {
20075 s.select_ranges(selections.into_iter().map(|(start, end)| {
20076 snapshot.clip_offset(start, Bias::Left)
20077 ..snapshot.clip_offset(end, Bias::Right)
20078 }));
20079 });
20080 self.selection_history.mode = SelectionHistoryMode::Normal;
20081 }
20082 };
20083 }
20084
20085 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20086 }
20087}
20088
20089fn vim_enabled(cx: &App) -> bool {
20090 cx.global::<SettingsStore>()
20091 .raw_user_settings()
20092 .get("vim_mode")
20093 == Some(&serde_json::Value::Bool(true))
20094}
20095
20096fn process_completion_for_edit(
20097 completion: &Completion,
20098 intent: CompletionIntent,
20099 buffer: &Entity<Buffer>,
20100 cursor_position: &text::Anchor,
20101 cx: &mut Context<Editor>,
20102) -> CompletionEdit {
20103 let buffer = buffer.read(cx);
20104 let buffer_snapshot = buffer.snapshot();
20105 let (snippet, new_text) = if completion.is_snippet() {
20106 // Workaround for typescript language server issues so that methods don't expand within
20107 // strings and functions with type expressions. The previous point is used because the query
20108 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20109 let mut snippet_source = completion.new_text.clone();
20110 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20111 previous_point.column = previous_point.column.saturating_sub(1);
20112 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20113 if scope.prefers_label_for_snippet_in_completion() {
20114 if let Some(label) = completion.label() {
20115 if matches!(
20116 completion.kind(),
20117 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20118 ) {
20119 snippet_source = label;
20120 }
20121 }
20122 }
20123 }
20124 match Snippet::parse(&snippet_source).log_err() {
20125 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20126 None => (None, completion.new_text.clone()),
20127 }
20128 } else {
20129 (None, completion.new_text.clone())
20130 };
20131
20132 let mut range_to_replace = {
20133 let replace_range = &completion.replace_range;
20134 if let CompletionSource::Lsp {
20135 insert_range: Some(insert_range),
20136 ..
20137 } = &completion.source
20138 {
20139 debug_assert_eq!(
20140 insert_range.start, replace_range.start,
20141 "insert_range and replace_range should start at the same position"
20142 );
20143 debug_assert!(
20144 insert_range
20145 .start
20146 .cmp(&cursor_position, &buffer_snapshot)
20147 .is_le(),
20148 "insert_range should start before or at cursor position"
20149 );
20150 debug_assert!(
20151 replace_range
20152 .start
20153 .cmp(&cursor_position, &buffer_snapshot)
20154 .is_le(),
20155 "replace_range should start before or at cursor position"
20156 );
20157 debug_assert!(
20158 insert_range
20159 .end
20160 .cmp(&cursor_position, &buffer_snapshot)
20161 .is_le(),
20162 "insert_range should end before or at cursor position"
20163 );
20164
20165 let should_replace = match intent {
20166 CompletionIntent::CompleteWithInsert => false,
20167 CompletionIntent::CompleteWithReplace => true,
20168 CompletionIntent::Complete | CompletionIntent::Compose => {
20169 let insert_mode =
20170 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20171 .completions
20172 .lsp_insert_mode;
20173 match insert_mode {
20174 LspInsertMode::Insert => false,
20175 LspInsertMode::Replace => true,
20176 LspInsertMode::ReplaceSubsequence => {
20177 let mut text_to_replace = buffer.chars_for_range(
20178 buffer.anchor_before(replace_range.start)
20179 ..buffer.anchor_after(replace_range.end),
20180 );
20181 let mut current_needle = text_to_replace.next();
20182 for haystack_ch in completion.label.text.chars() {
20183 if let Some(needle_ch) = current_needle {
20184 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20185 current_needle = text_to_replace.next();
20186 }
20187 }
20188 }
20189 current_needle.is_none()
20190 }
20191 LspInsertMode::ReplaceSuffix => {
20192 if replace_range
20193 .end
20194 .cmp(&cursor_position, &buffer_snapshot)
20195 .is_gt()
20196 {
20197 let range_after_cursor = *cursor_position..replace_range.end;
20198 let text_after_cursor = buffer
20199 .text_for_range(
20200 buffer.anchor_before(range_after_cursor.start)
20201 ..buffer.anchor_after(range_after_cursor.end),
20202 )
20203 .collect::<String>()
20204 .to_ascii_lowercase();
20205 completion
20206 .label
20207 .text
20208 .to_ascii_lowercase()
20209 .ends_with(&text_after_cursor)
20210 } else {
20211 true
20212 }
20213 }
20214 }
20215 }
20216 };
20217
20218 if should_replace {
20219 replace_range.clone()
20220 } else {
20221 insert_range.clone()
20222 }
20223 } else {
20224 replace_range.clone()
20225 }
20226 };
20227
20228 if range_to_replace
20229 .end
20230 .cmp(&cursor_position, &buffer_snapshot)
20231 .is_lt()
20232 {
20233 range_to_replace.end = *cursor_position;
20234 }
20235
20236 CompletionEdit {
20237 new_text,
20238 replace_range: range_to_replace.to_offset(&buffer),
20239 snippet,
20240 }
20241}
20242
20243struct CompletionEdit {
20244 new_text: String,
20245 replace_range: Range<usize>,
20246 snippet: Option<Snippet>,
20247}
20248
20249fn insert_extra_newline_brackets(
20250 buffer: &MultiBufferSnapshot,
20251 range: Range<usize>,
20252 language: &language::LanguageScope,
20253) -> bool {
20254 let leading_whitespace_len = buffer
20255 .reversed_chars_at(range.start)
20256 .take_while(|c| c.is_whitespace() && *c != '\n')
20257 .map(|c| c.len_utf8())
20258 .sum::<usize>();
20259 let trailing_whitespace_len = buffer
20260 .chars_at(range.end)
20261 .take_while(|c| c.is_whitespace() && *c != '\n')
20262 .map(|c| c.len_utf8())
20263 .sum::<usize>();
20264 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20265
20266 language.brackets().any(|(pair, enabled)| {
20267 let pair_start = pair.start.trim_end();
20268 let pair_end = pair.end.trim_start();
20269
20270 enabled
20271 && pair.newline
20272 && buffer.contains_str_at(range.end, pair_end)
20273 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20274 })
20275}
20276
20277fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20278 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20279 [(buffer, range, _)] => (*buffer, range.clone()),
20280 _ => return false,
20281 };
20282 let pair = {
20283 let mut result: Option<BracketMatch> = None;
20284
20285 for pair in buffer
20286 .all_bracket_ranges(range.clone())
20287 .filter(move |pair| {
20288 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20289 })
20290 {
20291 let len = pair.close_range.end - pair.open_range.start;
20292
20293 if let Some(existing) = &result {
20294 let existing_len = existing.close_range.end - existing.open_range.start;
20295 if len > existing_len {
20296 continue;
20297 }
20298 }
20299
20300 result = Some(pair);
20301 }
20302
20303 result
20304 };
20305 let Some(pair) = pair else {
20306 return false;
20307 };
20308 pair.newline_only
20309 && buffer
20310 .chars_for_range(pair.open_range.end..range.start)
20311 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20312 .all(|c| c.is_whitespace() && c != '\n')
20313}
20314
20315fn update_uncommitted_diff_for_buffer(
20316 editor: Entity<Editor>,
20317 project: &Entity<Project>,
20318 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20319 buffer: Entity<MultiBuffer>,
20320 cx: &mut App,
20321) -> Task<()> {
20322 let mut tasks = Vec::new();
20323 project.update(cx, |project, cx| {
20324 for buffer in buffers {
20325 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20326 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20327 }
20328 }
20329 });
20330 cx.spawn(async move |cx| {
20331 let diffs = future::join_all(tasks).await;
20332 if editor
20333 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20334 .unwrap_or(false)
20335 {
20336 return;
20337 }
20338
20339 buffer
20340 .update(cx, |buffer, cx| {
20341 for diff in diffs.into_iter().flatten() {
20342 buffer.add_diff(diff, cx);
20343 }
20344 })
20345 .ok();
20346 })
20347}
20348
20349fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20350 let tab_size = tab_size.get() as usize;
20351 let mut width = offset;
20352
20353 for ch in text.chars() {
20354 width += if ch == '\t' {
20355 tab_size - (width % tab_size)
20356 } else {
20357 1
20358 };
20359 }
20360
20361 width - offset
20362}
20363
20364#[cfg(test)]
20365mod tests {
20366 use super::*;
20367
20368 #[test]
20369 fn test_string_size_with_expanded_tabs() {
20370 let nz = |val| NonZeroU32::new(val).unwrap();
20371 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20372 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20373 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20374 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20375 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20376 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20377 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20378 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20379 }
20380}
20381
20382/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20383struct WordBreakingTokenizer<'a> {
20384 input: &'a str,
20385}
20386
20387impl<'a> WordBreakingTokenizer<'a> {
20388 fn new(input: &'a str) -> Self {
20389 Self { input }
20390 }
20391}
20392
20393fn is_char_ideographic(ch: char) -> bool {
20394 use unicode_script::Script::*;
20395 use unicode_script::UnicodeScript;
20396 matches!(ch.script(), Han | Tangut | Yi)
20397}
20398
20399fn is_grapheme_ideographic(text: &str) -> bool {
20400 text.chars().any(is_char_ideographic)
20401}
20402
20403fn is_grapheme_whitespace(text: &str) -> bool {
20404 text.chars().any(|x| x.is_whitespace())
20405}
20406
20407fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20408 text.chars().next().map_or(false, |ch| {
20409 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20410 })
20411}
20412
20413#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20414enum WordBreakToken<'a> {
20415 Word { token: &'a str, grapheme_len: usize },
20416 InlineWhitespace { token: &'a str, grapheme_len: usize },
20417 Newline,
20418}
20419
20420impl<'a> Iterator for WordBreakingTokenizer<'a> {
20421 /// Yields a span, the count of graphemes in the token, and whether it was
20422 /// whitespace. Note that it also breaks at word boundaries.
20423 type Item = WordBreakToken<'a>;
20424
20425 fn next(&mut self) -> Option<Self::Item> {
20426 use unicode_segmentation::UnicodeSegmentation;
20427 if self.input.is_empty() {
20428 return None;
20429 }
20430
20431 let mut iter = self.input.graphemes(true).peekable();
20432 let mut offset = 0;
20433 let mut grapheme_len = 0;
20434 if let Some(first_grapheme) = iter.next() {
20435 let is_newline = first_grapheme == "\n";
20436 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20437 offset += first_grapheme.len();
20438 grapheme_len += 1;
20439 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20440 if let Some(grapheme) = iter.peek().copied() {
20441 if should_stay_with_preceding_ideograph(grapheme) {
20442 offset += grapheme.len();
20443 grapheme_len += 1;
20444 }
20445 }
20446 } else {
20447 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20448 let mut next_word_bound = words.peek().copied();
20449 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20450 next_word_bound = words.next();
20451 }
20452 while let Some(grapheme) = iter.peek().copied() {
20453 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20454 break;
20455 };
20456 if is_grapheme_whitespace(grapheme) != is_whitespace
20457 || (grapheme == "\n") != is_newline
20458 {
20459 break;
20460 };
20461 offset += grapheme.len();
20462 grapheme_len += 1;
20463 iter.next();
20464 }
20465 }
20466 let token = &self.input[..offset];
20467 self.input = &self.input[offset..];
20468 if token == "\n" {
20469 Some(WordBreakToken::Newline)
20470 } else if is_whitespace {
20471 Some(WordBreakToken::InlineWhitespace {
20472 token,
20473 grapheme_len,
20474 })
20475 } else {
20476 Some(WordBreakToken::Word {
20477 token,
20478 grapheme_len,
20479 })
20480 }
20481 } else {
20482 None
20483 }
20484 }
20485}
20486
20487#[test]
20488fn test_word_breaking_tokenizer() {
20489 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20490 ("", &[]),
20491 (" ", &[whitespace(" ", 2)]),
20492 ("Ʒ", &[word("Ʒ", 1)]),
20493 ("Ǽ", &[word("Ǽ", 1)]),
20494 ("⋑", &[word("⋑", 1)]),
20495 ("⋑⋑", &[word("⋑⋑", 2)]),
20496 (
20497 "原理,进而",
20498 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20499 ),
20500 (
20501 "hello world",
20502 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20503 ),
20504 (
20505 "hello, world",
20506 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20507 ),
20508 (
20509 " hello world",
20510 &[
20511 whitespace(" ", 2),
20512 word("hello", 5),
20513 whitespace(" ", 1),
20514 word("world", 5),
20515 ],
20516 ),
20517 (
20518 "这是什么 \n 钢笔",
20519 &[
20520 word("这", 1),
20521 word("是", 1),
20522 word("什", 1),
20523 word("么", 1),
20524 whitespace(" ", 1),
20525 newline(),
20526 whitespace(" ", 1),
20527 word("钢", 1),
20528 word("笔", 1),
20529 ],
20530 ),
20531 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20532 ];
20533
20534 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20535 WordBreakToken::Word {
20536 token,
20537 grapheme_len,
20538 }
20539 }
20540
20541 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20542 WordBreakToken::InlineWhitespace {
20543 token,
20544 grapheme_len,
20545 }
20546 }
20547
20548 fn newline() -> WordBreakToken<'static> {
20549 WordBreakToken::Newline
20550 }
20551
20552 for (input, result) in tests {
20553 assert_eq!(
20554 WordBreakingTokenizer::new(input)
20555 .collect::<Vec<_>>()
20556 .as_slice(),
20557 *result,
20558 );
20559 }
20560}
20561
20562fn wrap_with_prefix(
20563 line_prefix: String,
20564 unwrapped_text: String,
20565 wrap_column: usize,
20566 tab_size: NonZeroU32,
20567 preserve_existing_whitespace: bool,
20568) -> String {
20569 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20570 let mut wrapped_text = String::new();
20571 let mut current_line = line_prefix.clone();
20572
20573 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20574 let mut current_line_len = line_prefix_len;
20575 let mut in_whitespace = false;
20576 for token in tokenizer {
20577 let have_preceding_whitespace = in_whitespace;
20578 match token {
20579 WordBreakToken::Word {
20580 token,
20581 grapheme_len,
20582 } => {
20583 in_whitespace = false;
20584 if current_line_len + grapheme_len > wrap_column
20585 && current_line_len != line_prefix_len
20586 {
20587 wrapped_text.push_str(current_line.trim_end());
20588 wrapped_text.push('\n');
20589 current_line.truncate(line_prefix.len());
20590 current_line_len = line_prefix_len;
20591 }
20592 current_line.push_str(token);
20593 current_line_len += grapheme_len;
20594 }
20595 WordBreakToken::InlineWhitespace {
20596 mut token,
20597 mut grapheme_len,
20598 } => {
20599 in_whitespace = true;
20600 if have_preceding_whitespace && !preserve_existing_whitespace {
20601 continue;
20602 }
20603 if !preserve_existing_whitespace {
20604 token = " ";
20605 grapheme_len = 1;
20606 }
20607 if current_line_len + grapheme_len > wrap_column {
20608 wrapped_text.push_str(current_line.trim_end());
20609 wrapped_text.push('\n');
20610 current_line.truncate(line_prefix.len());
20611 current_line_len = line_prefix_len;
20612 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20613 current_line.push_str(token);
20614 current_line_len += grapheme_len;
20615 }
20616 }
20617 WordBreakToken::Newline => {
20618 in_whitespace = true;
20619 if preserve_existing_whitespace {
20620 wrapped_text.push_str(current_line.trim_end());
20621 wrapped_text.push('\n');
20622 current_line.truncate(line_prefix.len());
20623 current_line_len = line_prefix_len;
20624 } else if have_preceding_whitespace {
20625 continue;
20626 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20627 {
20628 wrapped_text.push_str(current_line.trim_end());
20629 wrapped_text.push('\n');
20630 current_line.truncate(line_prefix.len());
20631 current_line_len = line_prefix_len;
20632 } else if current_line_len != line_prefix_len {
20633 current_line.push(' ');
20634 current_line_len += 1;
20635 }
20636 }
20637 }
20638 }
20639
20640 if !current_line.is_empty() {
20641 wrapped_text.push_str(¤t_line);
20642 }
20643 wrapped_text
20644}
20645
20646#[test]
20647fn test_wrap_with_prefix() {
20648 assert_eq!(
20649 wrap_with_prefix(
20650 "# ".to_string(),
20651 "abcdefg".to_string(),
20652 4,
20653 NonZeroU32::new(4).unwrap(),
20654 false,
20655 ),
20656 "# abcdefg"
20657 );
20658 assert_eq!(
20659 wrap_with_prefix(
20660 "".to_string(),
20661 "\thello world".to_string(),
20662 8,
20663 NonZeroU32::new(4).unwrap(),
20664 false,
20665 ),
20666 "hello\nworld"
20667 );
20668 assert_eq!(
20669 wrap_with_prefix(
20670 "// ".to_string(),
20671 "xx \nyy zz aa bb cc".to_string(),
20672 12,
20673 NonZeroU32::new(4).unwrap(),
20674 false,
20675 ),
20676 "// xx yy zz\n// aa bb cc"
20677 );
20678 assert_eq!(
20679 wrap_with_prefix(
20680 String::new(),
20681 "这是什么 \n 钢笔".to_string(),
20682 3,
20683 NonZeroU32::new(4).unwrap(),
20684 false,
20685 ),
20686 "这是什\n么 钢\n笔"
20687 );
20688}
20689
20690pub trait CollaborationHub {
20691 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20692 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20693 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20694}
20695
20696impl CollaborationHub for Entity<Project> {
20697 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20698 self.read(cx).collaborators()
20699 }
20700
20701 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20702 self.read(cx).user_store().read(cx).participant_indices()
20703 }
20704
20705 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20706 let this = self.read(cx);
20707 let user_ids = this.collaborators().values().map(|c| c.user_id);
20708 this.user_store().read(cx).participant_names(user_ids, cx)
20709 }
20710}
20711
20712pub trait SemanticsProvider {
20713 fn hover(
20714 &self,
20715 buffer: &Entity<Buffer>,
20716 position: text::Anchor,
20717 cx: &mut App,
20718 ) -> Option<Task<Vec<project::Hover>>>;
20719
20720 fn inline_values(
20721 &self,
20722 buffer_handle: Entity<Buffer>,
20723 range: Range<text::Anchor>,
20724 cx: &mut App,
20725 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20726
20727 fn inlay_hints(
20728 &self,
20729 buffer_handle: Entity<Buffer>,
20730 range: Range<text::Anchor>,
20731 cx: &mut App,
20732 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20733
20734 fn resolve_inlay_hint(
20735 &self,
20736 hint: InlayHint,
20737 buffer_handle: Entity<Buffer>,
20738 server_id: LanguageServerId,
20739 cx: &mut App,
20740 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20741
20742 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20743
20744 fn document_highlights(
20745 &self,
20746 buffer: &Entity<Buffer>,
20747 position: text::Anchor,
20748 cx: &mut App,
20749 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20750
20751 fn definitions(
20752 &self,
20753 buffer: &Entity<Buffer>,
20754 position: text::Anchor,
20755 kind: GotoDefinitionKind,
20756 cx: &mut App,
20757 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20758
20759 fn range_for_rename(
20760 &self,
20761 buffer: &Entity<Buffer>,
20762 position: text::Anchor,
20763 cx: &mut App,
20764 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20765
20766 fn perform_rename(
20767 &self,
20768 buffer: &Entity<Buffer>,
20769 position: text::Anchor,
20770 new_name: String,
20771 cx: &mut App,
20772 ) -> Option<Task<Result<ProjectTransaction>>>;
20773
20774 fn pull_diagnostics_for_buffer(
20775 &self,
20776 buffer: Entity<Buffer>,
20777 cx: &mut App,
20778 ) -> Task<anyhow::Result<()>>;
20779}
20780
20781pub trait CompletionProvider {
20782 fn completions(
20783 &self,
20784 excerpt_id: ExcerptId,
20785 buffer: &Entity<Buffer>,
20786 buffer_position: text::Anchor,
20787 trigger: CompletionContext,
20788 window: &mut Window,
20789 cx: &mut Context<Editor>,
20790 ) -> Task<Result<Vec<CompletionResponse>>>;
20791
20792 fn resolve_completions(
20793 &self,
20794 _buffer: Entity<Buffer>,
20795 _completion_indices: Vec<usize>,
20796 _completions: Rc<RefCell<Box<[Completion]>>>,
20797 _cx: &mut Context<Editor>,
20798 ) -> Task<Result<bool>> {
20799 Task::ready(Ok(false))
20800 }
20801
20802 fn apply_additional_edits_for_completion(
20803 &self,
20804 _buffer: Entity<Buffer>,
20805 _completions: Rc<RefCell<Box<[Completion]>>>,
20806 _completion_index: usize,
20807 _push_to_history: bool,
20808 _cx: &mut Context<Editor>,
20809 ) -> Task<Result<Option<language::Transaction>>> {
20810 Task::ready(Ok(None))
20811 }
20812
20813 fn is_completion_trigger(
20814 &self,
20815 buffer: &Entity<Buffer>,
20816 position: language::Anchor,
20817 text: &str,
20818 trigger_in_words: bool,
20819 menu_is_open: bool,
20820 cx: &mut Context<Editor>,
20821 ) -> bool;
20822
20823 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20824
20825 fn sort_completions(&self) -> bool {
20826 true
20827 }
20828
20829 fn filter_completions(&self) -> bool {
20830 true
20831 }
20832}
20833
20834pub trait CodeActionProvider {
20835 fn id(&self) -> Arc<str>;
20836
20837 fn code_actions(
20838 &self,
20839 buffer: &Entity<Buffer>,
20840 range: Range<text::Anchor>,
20841 window: &mut Window,
20842 cx: &mut App,
20843 ) -> Task<Result<Vec<CodeAction>>>;
20844
20845 fn apply_code_action(
20846 &self,
20847 buffer_handle: Entity<Buffer>,
20848 action: CodeAction,
20849 excerpt_id: ExcerptId,
20850 push_to_history: bool,
20851 window: &mut Window,
20852 cx: &mut App,
20853 ) -> Task<Result<ProjectTransaction>>;
20854}
20855
20856impl CodeActionProvider for Entity<Project> {
20857 fn id(&self) -> Arc<str> {
20858 "project".into()
20859 }
20860
20861 fn code_actions(
20862 &self,
20863 buffer: &Entity<Buffer>,
20864 range: Range<text::Anchor>,
20865 _window: &mut Window,
20866 cx: &mut App,
20867 ) -> Task<Result<Vec<CodeAction>>> {
20868 self.update(cx, |project, cx| {
20869 let code_lens = project.code_lens(buffer, range.clone(), cx);
20870 let code_actions = project.code_actions(buffer, range, None, cx);
20871 cx.background_spawn(async move {
20872 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20873 Ok(code_lens
20874 .context("code lens fetch")?
20875 .into_iter()
20876 .chain(code_actions.context("code action fetch")?)
20877 .collect())
20878 })
20879 })
20880 }
20881
20882 fn apply_code_action(
20883 &self,
20884 buffer_handle: Entity<Buffer>,
20885 action: CodeAction,
20886 _excerpt_id: ExcerptId,
20887 push_to_history: bool,
20888 _window: &mut Window,
20889 cx: &mut App,
20890 ) -> Task<Result<ProjectTransaction>> {
20891 self.update(cx, |project, cx| {
20892 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20893 })
20894 }
20895}
20896
20897fn snippet_completions(
20898 project: &Project,
20899 buffer: &Entity<Buffer>,
20900 buffer_position: text::Anchor,
20901 cx: &mut App,
20902) -> Task<Result<CompletionResponse>> {
20903 let languages = buffer.read(cx).languages_at(buffer_position);
20904 let snippet_store = project.snippets().read(cx);
20905
20906 let scopes: Vec<_> = languages
20907 .iter()
20908 .filter_map(|language| {
20909 let language_name = language.lsp_id();
20910 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20911
20912 if snippets.is_empty() {
20913 None
20914 } else {
20915 Some((language.default_scope(), snippets))
20916 }
20917 })
20918 .collect();
20919
20920 if scopes.is_empty() {
20921 return Task::ready(Ok(CompletionResponse {
20922 completions: vec![],
20923 is_incomplete: false,
20924 }));
20925 }
20926
20927 let snapshot = buffer.read(cx).text_snapshot();
20928 let chars: String = snapshot
20929 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20930 .collect();
20931 let executor = cx.background_executor().clone();
20932
20933 cx.background_spawn(async move {
20934 let mut is_incomplete = false;
20935 let mut completions: Vec<Completion> = Vec::new();
20936 for (scope, snippets) in scopes.into_iter() {
20937 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20938 let mut last_word = chars
20939 .chars()
20940 .take_while(|c| classifier.is_word(*c))
20941 .collect::<String>();
20942 last_word = last_word.chars().rev().collect();
20943
20944 if last_word.is_empty() {
20945 return Ok(CompletionResponse {
20946 completions: vec![],
20947 is_incomplete: true,
20948 });
20949 }
20950
20951 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20952 let to_lsp = |point: &text::Anchor| {
20953 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20954 point_to_lsp(end)
20955 };
20956 let lsp_end = to_lsp(&buffer_position);
20957
20958 let candidates = snippets
20959 .iter()
20960 .enumerate()
20961 .flat_map(|(ix, snippet)| {
20962 snippet
20963 .prefix
20964 .iter()
20965 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20966 })
20967 .collect::<Vec<StringMatchCandidate>>();
20968
20969 const MAX_RESULTS: usize = 100;
20970 let mut matches = fuzzy::match_strings(
20971 &candidates,
20972 &last_word,
20973 last_word.chars().any(|c| c.is_uppercase()),
20974 MAX_RESULTS,
20975 &Default::default(),
20976 executor.clone(),
20977 )
20978 .await;
20979
20980 if matches.len() >= MAX_RESULTS {
20981 is_incomplete = true;
20982 }
20983
20984 // Remove all candidates where the query's start does not match the start of any word in the candidate
20985 if let Some(query_start) = last_word.chars().next() {
20986 matches.retain(|string_match| {
20987 split_words(&string_match.string).any(|word| {
20988 // Check that the first codepoint of the word as lowercase matches the first
20989 // codepoint of the query as lowercase
20990 word.chars()
20991 .flat_map(|codepoint| codepoint.to_lowercase())
20992 .zip(query_start.to_lowercase())
20993 .all(|(word_cp, query_cp)| word_cp == query_cp)
20994 })
20995 });
20996 }
20997
20998 let matched_strings = matches
20999 .into_iter()
21000 .map(|m| m.string)
21001 .collect::<HashSet<_>>();
21002
21003 completions.extend(snippets.iter().filter_map(|snippet| {
21004 let matching_prefix = snippet
21005 .prefix
21006 .iter()
21007 .find(|prefix| matched_strings.contains(*prefix))?;
21008 let start = as_offset - last_word.len();
21009 let start = snapshot.anchor_before(start);
21010 let range = start..buffer_position;
21011 let lsp_start = to_lsp(&start);
21012 let lsp_range = lsp::Range {
21013 start: lsp_start,
21014 end: lsp_end,
21015 };
21016 Some(Completion {
21017 replace_range: range,
21018 new_text: snippet.body.clone(),
21019 source: CompletionSource::Lsp {
21020 insert_range: None,
21021 server_id: LanguageServerId(usize::MAX),
21022 resolved: true,
21023 lsp_completion: Box::new(lsp::CompletionItem {
21024 label: snippet.prefix.first().unwrap().clone(),
21025 kind: Some(CompletionItemKind::SNIPPET),
21026 label_details: snippet.description.as_ref().map(|description| {
21027 lsp::CompletionItemLabelDetails {
21028 detail: Some(description.clone()),
21029 description: None,
21030 }
21031 }),
21032 insert_text_format: Some(InsertTextFormat::SNIPPET),
21033 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21034 lsp::InsertReplaceEdit {
21035 new_text: snippet.body.clone(),
21036 insert: lsp_range,
21037 replace: lsp_range,
21038 },
21039 )),
21040 filter_text: Some(snippet.body.clone()),
21041 sort_text: Some(char::MAX.to_string()),
21042 ..lsp::CompletionItem::default()
21043 }),
21044 lsp_defaults: None,
21045 },
21046 label: CodeLabel {
21047 text: matching_prefix.clone(),
21048 runs: Vec::new(),
21049 filter_range: 0..matching_prefix.len(),
21050 },
21051 icon_path: None,
21052 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21053 single_line: snippet.name.clone().into(),
21054 plain_text: snippet
21055 .description
21056 .clone()
21057 .map(|description| description.into()),
21058 }),
21059 insert_text_mode: None,
21060 confirm: None,
21061 })
21062 }))
21063 }
21064
21065 Ok(CompletionResponse {
21066 completions,
21067 is_incomplete,
21068 })
21069 })
21070}
21071
21072impl CompletionProvider for Entity<Project> {
21073 fn completions(
21074 &self,
21075 _excerpt_id: ExcerptId,
21076 buffer: &Entity<Buffer>,
21077 buffer_position: text::Anchor,
21078 options: CompletionContext,
21079 _window: &mut Window,
21080 cx: &mut Context<Editor>,
21081 ) -> Task<Result<Vec<CompletionResponse>>> {
21082 self.update(cx, |project, cx| {
21083 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21084 let project_completions = project.completions(buffer, buffer_position, options, cx);
21085 cx.background_spawn(async move {
21086 let mut responses = project_completions.await?;
21087 let snippets = snippets.await?;
21088 if !snippets.completions.is_empty() {
21089 responses.push(snippets);
21090 }
21091 Ok(responses)
21092 })
21093 })
21094 }
21095
21096 fn resolve_completions(
21097 &self,
21098 buffer: Entity<Buffer>,
21099 completion_indices: Vec<usize>,
21100 completions: Rc<RefCell<Box<[Completion]>>>,
21101 cx: &mut Context<Editor>,
21102 ) -> Task<Result<bool>> {
21103 self.update(cx, |project, cx| {
21104 project.lsp_store().update(cx, |lsp_store, cx| {
21105 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21106 })
21107 })
21108 }
21109
21110 fn apply_additional_edits_for_completion(
21111 &self,
21112 buffer: Entity<Buffer>,
21113 completions: Rc<RefCell<Box<[Completion]>>>,
21114 completion_index: usize,
21115 push_to_history: bool,
21116 cx: &mut Context<Editor>,
21117 ) -> Task<Result<Option<language::Transaction>>> {
21118 self.update(cx, |project, cx| {
21119 project.lsp_store().update(cx, |lsp_store, cx| {
21120 lsp_store.apply_additional_edits_for_completion(
21121 buffer,
21122 completions,
21123 completion_index,
21124 push_to_history,
21125 cx,
21126 )
21127 })
21128 })
21129 }
21130
21131 fn is_completion_trigger(
21132 &self,
21133 buffer: &Entity<Buffer>,
21134 position: language::Anchor,
21135 text: &str,
21136 trigger_in_words: bool,
21137 menu_is_open: bool,
21138 cx: &mut Context<Editor>,
21139 ) -> bool {
21140 let mut chars = text.chars();
21141 let char = if let Some(char) = chars.next() {
21142 char
21143 } else {
21144 return false;
21145 };
21146 if chars.next().is_some() {
21147 return false;
21148 }
21149
21150 let buffer = buffer.read(cx);
21151 let snapshot = buffer.snapshot();
21152 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21153 return false;
21154 }
21155 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21156 if trigger_in_words && classifier.is_word(char) {
21157 return true;
21158 }
21159
21160 buffer.completion_triggers().contains(text)
21161 }
21162}
21163
21164impl SemanticsProvider for Entity<Project> {
21165 fn hover(
21166 &self,
21167 buffer: &Entity<Buffer>,
21168 position: text::Anchor,
21169 cx: &mut App,
21170 ) -> Option<Task<Vec<project::Hover>>> {
21171 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21172 }
21173
21174 fn document_highlights(
21175 &self,
21176 buffer: &Entity<Buffer>,
21177 position: text::Anchor,
21178 cx: &mut App,
21179 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21180 Some(self.update(cx, |project, cx| {
21181 project.document_highlights(buffer, position, cx)
21182 }))
21183 }
21184
21185 fn definitions(
21186 &self,
21187 buffer: &Entity<Buffer>,
21188 position: text::Anchor,
21189 kind: GotoDefinitionKind,
21190 cx: &mut App,
21191 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21192 Some(self.update(cx, |project, cx| match kind {
21193 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21194 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21195 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21196 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21197 }))
21198 }
21199
21200 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21201 // TODO: make this work for remote projects
21202 self.update(cx, |project, cx| {
21203 if project
21204 .active_debug_session(cx)
21205 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21206 {
21207 return true;
21208 }
21209
21210 buffer.update(cx, |buffer, cx| {
21211 project.any_language_server_supports_inlay_hints(buffer, cx)
21212 })
21213 })
21214 }
21215
21216 fn inline_values(
21217 &self,
21218 buffer_handle: Entity<Buffer>,
21219
21220 range: Range<text::Anchor>,
21221 cx: &mut App,
21222 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21223 self.update(cx, |project, cx| {
21224 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21225
21226 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21227 })
21228 }
21229
21230 fn inlay_hints(
21231 &self,
21232 buffer_handle: Entity<Buffer>,
21233 range: Range<text::Anchor>,
21234 cx: &mut App,
21235 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21236 Some(self.update(cx, |project, cx| {
21237 project.inlay_hints(buffer_handle, range, cx)
21238 }))
21239 }
21240
21241 fn resolve_inlay_hint(
21242 &self,
21243 hint: InlayHint,
21244 buffer_handle: Entity<Buffer>,
21245 server_id: LanguageServerId,
21246 cx: &mut App,
21247 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21248 Some(self.update(cx, |project, cx| {
21249 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21250 }))
21251 }
21252
21253 fn range_for_rename(
21254 &self,
21255 buffer: &Entity<Buffer>,
21256 position: text::Anchor,
21257 cx: &mut App,
21258 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21259 Some(self.update(cx, |project, cx| {
21260 let buffer = buffer.clone();
21261 let task = project.prepare_rename(buffer.clone(), position, cx);
21262 cx.spawn(async move |_, cx| {
21263 Ok(match task.await? {
21264 PrepareRenameResponse::Success(range) => Some(range),
21265 PrepareRenameResponse::InvalidPosition => None,
21266 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21267 // Fallback on using TreeSitter info to determine identifier range
21268 buffer.read_with(cx, |buffer, _| {
21269 let snapshot = buffer.snapshot();
21270 let (range, kind) = snapshot.surrounding_word(position);
21271 if kind != Some(CharKind::Word) {
21272 return None;
21273 }
21274 Some(
21275 snapshot.anchor_before(range.start)
21276 ..snapshot.anchor_after(range.end),
21277 )
21278 })?
21279 }
21280 })
21281 })
21282 }))
21283 }
21284
21285 fn perform_rename(
21286 &self,
21287 buffer: &Entity<Buffer>,
21288 position: text::Anchor,
21289 new_name: String,
21290 cx: &mut App,
21291 ) -> Option<Task<Result<ProjectTransaction>>> {
21292 Some(self.update(cx, |project, cx| {
21293 project.perform_rename(buffer.clone(), position, new_name, cx)
21294 }))
21295 }
21296
21297 fn pull_diagnostics_for_buffer(
21298 &self,
21299 buffer: Entity<Buffer>,
21300 cx: &mut App,
21301 ) -> Task<anyhow::Result<()>> {
21302 let diagnostics = self.update(cx, |project, cx| {
21303 project
21304 .lsp_store()
21305 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21306 });
21307 let project = self.clone();
21308 cx.spawn(async move |cx| {
21309 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21310 project.update(cx, |project, cx| {
21311 project.lsp_store().update(cx, |lsp_store, cx| {
21312 for diagnostics_set in diagnostics {
21313 let LspPullDiagnostics::Response {
21314 server_id,
21315 uri,
21316 diagnostics,
21317 } = diagnostics_set
21318 else {
21319 continue;
21320 };
21321
21322 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21323 let disk_based_sources = adapter
21324 .as_ref()
21325 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21326 .unwrap_or(&[]);
21327 match diagnostics {
21328 PulledDiagnostics::Unchanged { result_id } => {
21329 lsp_store
21330 .merge_diagnostics(
21331 server_id,
21332 lsp::PublishDiagnosticsParams {
21333 uri: uri.clone(),
21334 diagnostics: Vec::new(),
21335 version: None,
21336 },
21337 Some(result_id),
21338 DiagnosticSourceKind::Pulled,
21339 disk_based_sources,
21340 |_, _| true,
21341 cx,
21342 )
21343 .log_err();
21344 }
21345 PulledDiagnostics::Changed {
21346 diagnostics,
21347 result_id,
21348 } => {
21349 lsp_store
21350 .merge_diagnostics(
21351 server_id,
21352 lsp::PublishDiagnosticsParams {
21353 uri: uri.clone(),
21354 diagnostics,
21355 version: None,
21356 },
21357 result_id,
21358 DiagnosticSourceKind::Pulled,
21359 disk_based_sources,
21360 |old_diagnostic, _| match old_diagnostic.source_kind {
21361 DiagnosticSourceKind::Pulled => false,
21362 DiagnosticSourceKind::Other
21363 | DiagnosticSourceKind::Pushed => true,
21364 },
21365 cx,
21366 )
21367 .log_err();
21368 }
21369 }
21370 }
21371 })
21372 })
21373 })
21374 }
21375}
21376
21377fn inlay_hint_settings(
21378 location: Anchor,
21379 snapshot: &MultiBufferSnapshot,
21380 cx: &mut Context<Editor>,
21381) -> InlayHintSettings {
21382 let file = snapshot.file_at(location);
21383 let language = snapshot.language_at(location).map(|l| l.name());
21384 language_settings(language, file, cx).inlay_hints
21385}
21386
21387fn consume_contiguous_rows(
21388 contiguous_row_selections: &mut Vec<Selection<Point>>,
21389 selection: &Selection<Point>,
21390 display_map: &DisplaySnapshot,
21391 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21392) -> (MultiBufferRow, MultiBufferRow) {
21393 contiguous_row_selections.push(selection.clone());
21394 let start_row = MultiBufferRow(selection.start.row);
21395 let mut end_row = ending_row(selection, display_map);
21396
21397 while let Some(next_selection) = selections.peek() {
21398 if next_selection.start.row <= end_row.0 {
21399 end_row = ending_row(next_selection, display_map);
21400 contiguous_row_selections.push(selections.next().unwrap().clone());
21401 } else {
21402 break;
21403 }
21404 }
21405 (start_row, end_row)
21406}
21407
21408fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21409 if next_selection.end.column > 0 || next_selection.is_empty() {
21410 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21411 } else {
21412 MultiBufferRow(next_selection.end.row)
21413 }
21414}
21415
21416impl EditorSnapshot {
21417 pub fn remote_selections_in_range<'a>(
21418 &'a self,
21419 range: &'a Range<Anchor>,
21420 collaboration_hub: &dyn CollaborationHub,
21421 cx: &'a App,
21422 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21423 let participant_names = collaboration_hub.user_names(cx);
21424 let participant_indices = collaboration_hub.user_participant_indices(cx);
21425 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21426 let collaborators_by_replica_id = collaborators_by_peer_id
21427 .values()
21428 .map(|collaborator| (collaborator.replica_id, collaborator))
21429 .collect::<HashMap<_, _>>();
21430 self.buffer_snapshot
21431 .selections_in_range(range, false)
21432 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21433 if replica_id == AGENT_REPLICA_ID {
21434 Some(RemoteSelection {
21435 replica_id,
21436 selection,
21437 cursor_shape,
21438 line_mode,
21439 collaborator_id: CollaboratorId::Agent,
21440 user_name: Some("Agent".into()),
21441 color: cx.theme().players().agent(),
21442 })
21443 } else {
21444 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21445 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21446 let user_name = participant_names.get(&collaborator.user_id).cloned();
21447 Some(RemoteSelection {
21448 replica_id,
21449 selection,
21450 cursor_shape,
21451 line_mode,
21452 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21453 user_name,
21454 color: if let Some(index) = participant_index {
21455 cx.theme().players().color_for_participant(index.0)
21456 } else {
21457 cx.theme().players().absent()
21458 },
21459 })
21460 }
21461 })
21462 }
21463
21464 pub fn hunks_for_ranges(
21465 &self,
21466 ranges: impl IntoIterator<Item = Range<Point>>,
21467 ) -> Vec<MultiBufferDiffHunk> {
21468 let mut hunks = Vec::new();
21469 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21470 HashMap::default();
21471 for query_range in ranges {
21472 let query_rows =
21473 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21474 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21475 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21476 ) {
21477 // Include deleted hunks that are adjacent to the query range, because
21478 // otherwise they would be missed.
21479 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21480 if hunk.status().is_deleted() {
21481 intersects_range |= hunk.row_range.start == query_rows.end;
21482 intersects_range |= hunk.row_range.end == query_rows.start;
21483 }
21484 if intersects_range {
21485 if !processed_buffer_rows
21486 .entry(hunk.buffer_id)
21487 .or_default()
21488 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21489 {
21490 continue;
21491 }
21492 hunks.push(hunk);
21493 }
21494 }
21495 }
21496
21497 hunks
21498 }
21499
21500 fn display_diff_hunks_for_rows<'a>(
21501 &'a self,
21502 display_rows: Range<DisplayRow>,
21503 folded_buffers: &'a HashSet<BufferId>,
21504 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21505 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21506 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21507
21508 self.buffer_snapshot
21509 .diff_hunks_in_range(buffer_start..buffer_end)
21510 .filter_map(|hunk| {
21511 if folded_buffers.contains(&hunk.buffer_id) {
21512 return None;
21513 }
21514
21515 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21516 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21517
21518 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21519 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21520
21521 let display_hunk = if hunk_display_start.column() != 0 {
21522 DisplayDiffHunk::Folded {
21523 display_row: hunk_display_start.row(),
21524 }
21525 } else {
21526 let mut end_row = hunk_display_end.row();
21527 if hunk_display_end.column() > 0 {
21528 end_row.0 += 1;
21529 }
21530 let is_created_file = hunk.is_created_file();
21531 DisplayDiffHunk::Unfolded {
21532 status: hunk.status(),
21533 diff_base_byte_range: hunk.diff_base_byte_range,
21534 display_row_range: hunk_display_start.row()..end_row,
21535 multi_buffer_range: Anchor::range_in_buffer(
21536 hunk.excerpt_id,
21537 hunk.buffer_id,
21538 hunk.buffer_range,
21539 ),
21540 is_created_file,
21541 }
21542 };
21543
21544 Some(display_hunk)
21545 })
21546 }
21547
21548 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21549 self.display_snapshot.buffer_snapshot.language_at(position)
21550 }
21551
21552 pub fn is_focused(&self) -> bool {
21553 self.is_focused
21554 }
21555
21556 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21557 self.placeholder_text.as_ref()
21558 }
21559
21560 pub fn scroll_position(&self) -> gpui::Point<f32> {
21561 self.scroll_anchor.scroll_position(&self.display_snapshot)
21562 }
21563
21564 fn gutter_dimensions(
21565 &self,
21566 font_id: FontId,
21567 font_size: Pixels,
21568 max_line_number_width: Pixels,
21569 cx: &App,
21570 ) -> Option<GutterDimensions> {
21571 if !self.show_gutter {
21572 return None;
21573 }
21574
21575 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21576 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21577
21578 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21579 matches!(
21580 ProjectSettings::get_global(cx).git.git_gutter,
21581 Some(GitGutterSetting::TrackedFiles)
21582 )
21583 });
21584 let gutter_settings = EditorSettings::get_global(cx).gutter;
21585 let show_line_numbers = self
21586 .show_line_numbers
21587 .unwrap_or(gutter_settings.line_numbers);
21588 let line_gutter_width = if show_line_numbers {
21589 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21590 let min_width_for_number_on_gutter =
21591 em_advance * gutter_settings.min_line_number_digits as f32;
21592 max_line_number_width.max(min_width_for_number_on_gutter)
21593 } else {
21594 0.0.into()
21595 };
21596
21597 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21598 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21599
21600 let git_blame_entries_width =
21601 self.git_blame_gutter_max_author_length
21602 .map(|max_author_length| {
21603 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21604 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21605
21606 /// The number of characters to dedicate to gaps and margins.
21607 const SPACING_WIDTH: usize = 4;
21608
21609 let max_char_count = max_author_length.min(renderer.max_author_length())
21610 + ::git::SHORT_SHA_LENGTH
21611 + MAX_RELATIVE_TIMESTAMP.len()
21612 + SPACING_WIDTH;
21613
21614 em_advance * max_char_count
21615 });
21616
21617 let is_singleton = self.buffer_snapshot.is_singleton();
21618
21619 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21620 left_padding += if !is_singleton {
21621 em_width * 4.0
21622 } else if show_runnables || show_breakpoints {
21623 em_width * 3.0
21624 } else if show_git_gutter && show_line_numbers {
21625 em_width * 2.0
21626 } else if show_git_gutter || show_line_numbers {
21627 em_width
21628 } else {
21629 px(0.)
21630 };
21631
21632 let shows_folds = is_singleton && gutter_settings.folds;
21633
21634 let right_padding = if shows_folds && show_line_numbers {
21635 em_width * 4.0
21636 } else if shows_folds || (!is_singleton && show_line_numbers) {
21637 em_width * 3.0
21638 } else if show_line_numbers {
21639 em_width
21640 } else {
21641 px(0.)
21642 };
21643
21644 Some(GutterDimensions {
21645 left_padding,
21646 right_padding,
21647 width: line_gutter_width + left_padding + right_padding,
21648 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21649 git_blame_entries_width,
21650 })
21651 }
21652
21653 pub fn render_crease_toggle(
21654 &self,
21655 buffer_row: MultiBufferRow,
21656 row_contains_cursor: bool,
21657 editor: Entity<Editor>,
21658 window: &mut Window,
21659 cx: &mut App,
21660 ) -> Option<AnyElement> {
21661 let folded = self.is_line_folded(buffer_row);
21662 let mut is_foldable = false;
21663
21664 if let Some(crease) = self
21665 .crease_snapshot
21666 .query_row(buffer_row, &self.buffer_snapshot)
21667 {
21668 is_foldable = true;
21669 match crease {
21670 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21671 if let Some(render_toggle) = render_toggle {
21672 let toggle_callback =
21673 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21674 if folded {
21675 editor.update(cx, |editor, cx| {
21676 editor.fold_at(buffer_row, window, cx)
21677 });
21678 } else {
21679 editor.update(cx, |editor, cx| {
21680 editor.unfold_at(buffer_row, window, cx)
21681 });
21682 }
21683 });
21684 return Some((render_toggle)(
21685 buffer_row,
21686 folded,
21687 toggle_callback,
21688 window,
21689 cx,
21690 ));
21691 }
21692 }
21693 }
21694 }
21695
21696 is_foldable |= self.starts_indent(buffer_row);
21697
21698 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21699 Some(
21700 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21701 .toggle_state(folded)
21702 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21703 if folded {
21704 this.unfold_at(buffer_row, window, cx);
21705 } else {
21706 this.fold_at(buffer_row, window, cx);
21707 }
21708 }))
21709 .into_any_element(),
21710 )
21711 } else {
21712 None
21713 }
21714 }
21715
21716 pub fn render_crease_trailer(
21717 &self,
21718 buffer_row: MultiBufferRow,
21719 window: &mut Window,
21720 cx: &mut App,
21721 ) -> Option<AnyElement> {
21722 let folded = self.is_line_folded(buffer_row);
21723 if let Crease::Inline { render_trailer, .. } = self
21724 .crease_snapshot
21725 .query_row(buffer_row, &self.buffer_snapshot)?
21726 {
21727 let render_trailer = render_trailer.as_ref()?;
21728 Some(render_trailer(buffer_row, folded, window, cx))
21729 } else {
21730 None
21731 }
21732 }
21733}
21734
21735impl Deref for EditorSnapshot {
21736 type Target = DisplaySnapshot;
21737
21738 fn deref(&self) -> &Self::Target {
21739 &self.display_snapshot
21740 }
21741}
21742
21743#[derive(Clone, Debug, PartialEq, Eq)]
21744pub enum EditorEvent {
21745 InputIgnored {
21746 text: Arc<str>,
21747 },
21748 InputHandled {
21749 utf16_range_to_replace: Option<Range<isize>>,
21750 text: Arc<str>,
21751 },
21752 ExcerptsAdded {
21753 buffer: Entity<Buffer>,
21754 predecessor: ExcerptId,
21755 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21756 },
21757 ExcerptsRemoved {
21758 ids: Vec<ExcerptId>,
21759 removed_buffer_ids: Vec<BufferId>,
21760 },
21761 BufferFoldToggled {
21762 ids: Vec<ExcerptId>,
21763 folded: bool,
21764 },
21765 ExcerptsEdited {
21766 ids: Vec<ExcerptId>,
21767 },
21768 ExcerptsExpanded {
21769 ids: Vec<ExcerptId>,
21770 },
21771 BufferEdited,
21772 Edited {
21773 transaction_id: clock::Lamport,
21774 },
21775 Reparsed(BufferId),
21776 Focused,
21777 FocusedIn,
21778 Blurred,
21779 DirtyChanged,
21780 Saved,
21781 TitleChanged,
21782 DiffBaseChanged,
21783 SelectionsChanged {
21784 local: bool,
21785 },
21786 ScrollPositionChanged {
21787 local: bool,
21788 autoscroll: bool,
21789 },
21790 Closed,
21791 TransactionUndone {
21792 transaction_id: clock::Lamport,
21793 },
21794 TransactionBegun {
21795 transaction_id: clock::Lamport,
21796 },
21797 Reloaded,
21798 CursorShapeChanged,
21799 PushedToNavHistory {
21800 anchor: Anchor,
21801 is_deactivate: bool,
21802 },
21803}
21804
21805impl EventEmitter<EditorEvent> for Editor {}
21806
21807impl Focusable for Editor {
21808 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21809 self.focus_handle.clone()
21810 }
21811}
21812
21813impl Render for Editor {
21814 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21815 let settings = ThemeSettings::get_global(cx);
21816
21817 let mut text_style = match self.mode {
21818 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21819 color: cx.theme().colors().editor_foreground,
21820 font_family: settings.ui_font.family.clone(),
21821 font_features: settings.ui_font.features.clone(),
21822 font_fallbacks: settings.ui_font.fallbacks.clone(),
21823 font_size: rems(0.875).into(),
21824 font_weight: settings.ui_font.weight,
21825 line_height: relative(settings.buffer_line_height.value()),
21826 ..Default::default()
21827 },
21828 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21829 color: cx.theme().colors().editor_foreground,
21830 font_family: settings.buffer_font.family.clone(),
21831 font_features: settings.buffer_font.features.clone(),
21832 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21833 font_size: settings.buffer_font_size(cx).into(),
21834 font_weight: settings.buffer_font.weight,
21835 line_height: relative(settings.buffer_line_height.value()),
21836 ..Default::default()
21837 },
21838 };
21839 if let Some(text_style_refinement) = &self.text_style_refinement {
21840 text_style.refine(text_style_refinement)
21841 }
21842
21843 let background = match self.mode {
21844 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21845 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21846 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21847 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21848 };
21849
21850 EditorElement::new(
21851 &cx.entity(),
21852 EditorStyle {
21853 background,
21854 local_player: cx.theme().players().local(),
21855 text: text_style,
21856 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21857 syntax: cx.theme().syntax().clone(),
21858 status: cx.theme().status().clone(),
21859 inlay_hints_style: make_inlay_hints_style(cx),
21860 inline_completion_styles: make_suggestion_styles(cx),
21861 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21862 show_underlines: !self.mode.is_minimap(),
21863 },
21864 )
21865 }
21866}
21867
21868impl EntityInputHandler for Editor {
21869 fn text_for_range(
21870 &mut self,
21871 range_utf16: Range<usize>,
21872 adjusted_range: &mut Option<Range<usize>>,
21873 _: &mut Window,
21874 cx: &mut Context<Self>,
21875 ) -> Option<String> {
21876 let snapshot = self.buffer.read(cx).read(cx);
21877 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21878 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21879 if (start.0..end.0) != range_utf16 {
21880 adjusted_range.replace(start.0..end.0);
21881 }
21882 Some(snapshot.text_for_range(start..end).collect())
21883 }
21884
21885 fn selected_text_range(
21886 &mut self,
21887 ignore_disabled_input: bool,
21888 _: &mut Window,
21889 cx: &mut Context<Self>,
21890 ) -> Option<UTF16Selection> {
21891 // Prevent the IME menu from appearing when holding down an alphabetic key
21892 // while input is disabled.
21893 if !ignore_disabled_input && !self.input_enabled {
21894 return None;
21895 }
21896
21897 let selection = self.selections.newest::<OffsetUtf16>(cx);
21898 let range = selection.range();
21899
21900 Some(UTF16Selection {
21901 range: range.start.0..range.end.0,
21902 reversed: selection.reversed,
21903 })
21904 }
21905
21906 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21907 let snapshot = self.buffer.read(cx).read(cx);
21908 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21909 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21910 }
21911
21912 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21913 self.clear_highlights::<InputComposition>(cx);
21914 self.ime_transaction.take();
21915 }
21916
21917 fn replace_text_in_range(
21918 &mut self,
21919 range_utf16: Option<Range<usize>>,
21920 text: &str,
21921 window: &mut Window,
21922 cx: &mut Context<Self>,
21923 ) {
21924 if !self.input_enabled {
21925 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21926 return;
21927 }
21928
21929 self.transact(window, cx, |this, window, cx| {
21930 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21931 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21932 Some(this.selection_replacement_ranges(range_utf16, cx))
21933 } else {
21934 this.marked_text_ranges(cx)
21935 };
21936
21937 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21938 let newest_selection_id = this.selections.newest_anchor().id;
21939 this.selections
21940 .all::<OffsetUtf16>(cx)
21941 .iter()
21942 .zip(ranges_to_replace.iter())
21943 .find_map(|(selection, range)| {
21944 if selection.id == newest_selection_id {
21945 Some(
21946 (range.start.0 as isize - selection.head().0 as isize)
21947 ..(range.end.0 as isize - selection.head().0 as isize),
21948 )
21949 } else {
21950 None
21951 }
21952 })
21953 });
21954
21955 cx.emit(EditorEvent::InputHandled {
21956 utf16_range_to_replace: range_to_replace,
21957 text: text.into(),
21958 });
21959
21960 if let Some(new_selected_ranges) = new_selected_ranges {
21961 this.change_selections(None, window, cx, |selections| {
21962 selections.select_ranges(new_selected_ranges)
21963 });
21964 this.backspace(&Default::default(), window, cx);
21965 }
21966
21967 this.handle_input(text, window, cx);
21968 });
21969
21970 if let Some(transaction) = self.ime_transaction {
21971 self.buffer.update(cx, |buffer, cx| {
21972 buffer.group_until_transaction(transaction, cx);
21973 });
21974 }
21975
21976 self.unmark_text(window, cx);
21977 }
21978
21979 fn replace_and_mark_text_in_range(
21980 &mut self,
21981 range_utf16: Option<Range<usize>>,
21982 text: &str,
21983 new_selected_range_utf16: Option<Range<usize>>,
21984 window: &mut Window,
21985 cx: &mut Context<Self>,
21986 ) {
21987 if !self.input_enabled {
21988 return;
21989 }
21990
21991 let transaction = self.transact(window, cx, |this, window, cx| {
21992 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21993 let snapshot = this.buffer.read(cx).read(cx);
21994 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21995 for marked_range in &mut marked_ranges {
21996 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21997 marked_range.start.0 += relative_range_utf16.start;
21998 marked_range.start =
21999 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22000 marked_range.end =
22001 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22002 }
22003 }
22004 Some(marked_ranges)
22005 } else if let Some(range_utf16) = range_utf16 {
22006 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22007 Some(this.selection_replacement_ranges(range_utf16, cx))
22008 } else {
22009 None
22010 };
22011
22012 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22013 let newest_selection_id = this.selections.newest_anchor().id;
22014 this.selections
22015 .all::<OffsetUtf16>(cx)
22016 .iter()
22017 .zip(ranges_to_replace.iter())
22018 .find_map(|(selection, range)| {
22019 if selection.id == newest_selection_id {
22020 Some(
22021 (range.start.0 as isize - selection.head().0 as isize)
22022 ..(range.end.0 as isize - selection.head().0 as isize),
22023 )
22024 } else {
22025 None
22026 }
22027 })
22028 });
22029
22030 cx.emit(EditorEvent::InputHandled {
22031 utf16_range_to_replace: range_to_replace,
22032 text: text.into(),
22033 });
22034
22035 if let Some(ranges) = ranges_to_replace {
22036 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22037 }
22038
22039 let marked_ranges = {
22040 let snapshot = this.buffer.read(cx).read(cx);
22041 this.selections
22042 .disjoint_anchors()
22043 .iter()
22044 .map(|selection| {
22045 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22046 })
22047 .collect::<Vec<_>>()
22048 };
22049
22050 if text.is_empty() {
22051 this.unmark_text(window, cx);
22052 } else {
22053 this.highlight_text::<InputComposition>(
22054 marked_ranges.clone(),
22055 HighlightStyle {
22056 underline: Some(UnderlineStyle {
22057 thickness: px(1.),
22058 color: None,
22059 wavy: false,
22060 }),
22061 ..Default::default()
22062 },
22063 cx,
22064 );
22065 }
22066
22067 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22068 let use_autoclose = this.use_autoclose;
22069 let use_auto_surround = this.use_auto_surround;
22070 this.set_use_autoclose(false);
22071 this.set_use_auto_surround(false);
22072 this.handle_input(text, window, cx);
22073 this.set_use_autoclose(use_autoclose);
22074 this.set_use_auto_surround(use_auto_surround);
22075
22076 if let Some(new_selected_range) = new_selected_range_utf16 {
22077 let snapshot = this.buffer.read(cx).read(cx);
22078 let new_selected_ranges = marked_ranges
22079 .into_iter()
22080 .map(|marked_range| {
22081 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22082 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22083 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22084 snapshot.clip_offset_utf16(new_start, Bias::Left)
22085 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22086 })
22087 .collect::<Vec<_>>();
22088
22089 drop(snapshot);
22090 this.change_selections(None, window, cx, |selections| {
22091 selections.select_ranges(new_selected_ranges)
22092 });
22093 }
22094 });
22095
22096 self.ime_transaction = self.ime_transaction.or(transaction);
22097 if let Some(transaction) = self.ime_transaction {
22098 self.buffer.update(cx, |buffer, cx| {
22099 buffer.group_until_transaction(transaction, cx);
22100 });
22101 }
22102
22103 if self.text_highlights::<InputComposition>(cx).is_none() {
22104 self.ime_transaction.take();
22105 }
22106 }
22107
22108 fn bounds_for_range(
22109 &mut self,
22110 range_utf16: Range<usize>,
22111 element_bounds: gpui::Bounds<Pixels>,
22112 window: &mut Window,
22113 cx: &mut Context<Self>,
22114 ) -> Option<gpui::Bounds<Pixels>> {
22115 let text_layout_details = self.text_layout_details(window);
22116 let gpui::Size {
22117 width: em_width,
22118 height: line_height,
22119 } = self.character_size(window);
22120
22121 let snapshot = self.snapshot(window, cx);
22122 let scroll_position = snapshot.scroll_position();
22123 let scroll_left = scroll_position.x * em_width;
22124
22125 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22126 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22127 + self.gutter_dimensions.width
22128 + self.gutter_dimensions.margin;
22129 let y = line_height * (start.row().as_f32() - scroll_position.y);
22130
22131 Some(Bounds {
22132 origin: element_bounds.origin + point(x, y),
22133 size: size(em_width, line_height),
22134 })
22135 }
22136
22137 fn character_index_for_point(
22138 &mut self,
22139 point: gpui::Point<Pixels>,
22140 _window: &mut Window,
22141 _cx: &mut Context<Self>,
22142 ) -> Option<usize> {
22143 let position_map = self.last_position_map.as_ref()?;
22144 if !position_map.text_hitbox.contains(&point) {
22145 return None;
22146 }
22147 let display_point = position_map.point_for_position(point).previous_valid;
22148 let anchor = position_map
22149 .snapshot
22150 .display_point_to_anchor(display_point, Bias::Left);
22151 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22152 Some(utf16_offset.0)
22153 }
22154}
22155
22156trait SelectionExt {
22157 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22158 fn spanned_rows(
22159 &self,
22160 include_end_if_at_line_start: bool,
22161 map: &DisplaySnapshot,
22162 ) -> Range<MultiBufferRow>;
22163}
22164
22165impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22166 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22167 let start = self
22168 .start
22169 .to_point(&map.buffer_snapshot)
22170 .to_display_point(map);
22171 let end = self
22172 .end
22173 .to_point(&map.buffer_snapshot)
22174 .to_display_point(map);
22175 if self.reversed {
22176 end..start
22177 } else {
22178 start..end
22179 }
22180 }
22181
22182 fn spanned_rows(
22183 &self,
22184 include_end_if_at_line_start: bool,
22185 map: &DisplaySnapshot,
22186 ) -> Range<MultiBufferRow> {
22187 let start = self.start.to_point(&map.buffer_snapshot);
22188 let mut end = self.end.to_point(&map.buffer_snapshot);
22189 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22190 end.row -= 1;
22191 }
22192
22193 let buffer_start = map.prev_line_boundary(start).0;
22194 let buffer_end = map.next_line_boundary(end).0;
22195 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22196 }
22197}
22198
22199impl<T: InvalidationRegion> InvalidationStack<T> {
22200 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22201 where
22202 S: Clone + ToOffset,
22203 {
22204 while let Some(region) = self.last() {
22205 let all_selections_inside_invalidation_ranges =
22206 if selections.len() == region.ranges().len() {
22207 selections
22208 .iter()
22209 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22210 .all(|(selection, invalidation_range)| {
22211 let head = selection.head().to_offset(buffer);
22212 invalidation_range.start <= head && invalidation_range.end >= head
22213 })
22214 } else {
22215 false
22216 };
22217
22218 if all_selections_inside_invalidation_ranges {
22219 break;
22220 } else {
22221 self.pop();
22222 }
22223 }
22224 }
22225}
22226
22227impl<T> Default for InvalidationStack<T> {
22228 fn default() -> Self {
22229 Self(Default::default())
22230 }
22231}
22232
22233impl<T> Deref for InvalidationStack<T> {
22234 type Target = Vec<T>;
22235
22236 fn deref(&self) -> &Self::Target {
22237 &self.0
22238 }
22239}
22240
22241impl<T> DerefMut for InvalidationStack<T> {
22242 fn deref_mut(&mut self) -> &mut Self::Target {
22243 &mut self.0
22244 }
22245}
22246
22247impl InvalidationRegion for SnippetState {
22248 fn ranges(&self) -> &[Range<Anchor>] {
22249 &self.ranges[self.active_index]
22250 }
22251}
22252
22253fn inline_completion_edit_text(
22254 current_snapshot: &BufferSnapshot,
22255 edits: &[(Range<Anchor>, String)],
22256 edit_preview: &EditPreview,
22257 include_deletions: bool,
22258 cx: &App,
22259) -> HighlightedText {
22260 let edits = edits
22261 .iter()
22262 .map(|(anchor, text)| {
22263 (
22264 anchor.start.text_anchor..anchor.end.text_anchor,
22265 text.clone(),
22266 )
22267 })
22268 .collect::<Vec<_>>();
22269
22270 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22271}
22272
22273pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22274 match severity {
22275 lsp::DiagnosticSeverity::ERROR => colors.error,
22276 lsp::DiagnosticSeverity::WARNING => colors.warning,
22277 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22278 lsp::DiagnosticSeverity::HINT => colors.info,
22279 _ => colors.ignored,
22280 }
22281}
22282
22283pub fn styled_runs_for_code_label<'a>(
22284 label: &'a CodeLabel,
22285 syntax_theme: &'a theme::SyntaxTheme,
22286) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22287 let fade_out = HighlightStyle {
22288 fade_out: Some(0.35),
22289 ..Default::default()
22290 };
22291
22292 let mut prev_end = label.filter_range.end;
22293 label
22294 .runs
22295 .iter()
22296 .enumerate()
22297 .flat_map(move |(ix, (range, highlight_id))| {
22298 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22299 style
22300 } else {
22301 return Default::default();
22302 };
22303 let mut muted_style = style;
22304 muted_style.highlight(fade_out);
22305
22306 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22307 if range.start >= label.filter_range.end {
22308 if range.start > prev_end {
22309 runs.push((prev_end..range.start, fade_out));
22310 }
22311 runs.push((range.clone(), muted_style));
22312 } else if range.end <= label.filter_range.end {
22313 runs.push((range.clone(), style));
22314 } else {
22315 runs.push((range.start..label.filter_range.end, style));
22316 runs.push((label.filter_range.end..range.end, muted_style));
22317 }
22318 prev_end = cmp::max(prev_end, range.end);
22319
22320 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22321 runs.push((prev_end..label.text.len(), fade_out));
22322 }
22323
22324 runs
22325 })
22326}
22327
22328pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22329 let mut prev_index = 0;
22330 let mut prev_codepoint: Option<char> = None;
22331 text.char_indices()
22332 .chain([(text.len(), '\0')])
22333 .filter_map(move |(index, codepoint)| {
22334 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22335 let is_boundary = index == text.len()
22336 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22337 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22338 if is_boundary {
22339 let chunk = &text[prev_index..index];
22340 prev_index = index;
22341 Some(chunk)
22342 } else {
22343 None
22344 }
22345 })
22346}
22347
22348pub trait RangeToAnchorExt: Sized {
22349 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22350
22351 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22352 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22353 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22354 }
22355}
22356
22357impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22358 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22359 let start_offset = self.start.to_offset(snapshot);
22360 let end_offset = self.end.to_offset(snapshot);
22361 if start_offset == end_offset {
22362 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22363 } else {
22364 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22365 }
22366 }
22367}
22368
22369pub trait RowExt {
22370 fn as_f32(&self) -> f32;
22371
22372 fn next_row(&self) -> Self;
22373
22374 fn previous_row(&self) -> Self;
22375
22376 fn minus(&self, other: Self) -> u32;
22377}
22378
22379impl RowExt for DisplayRow {
22380 fn as_f32(&self) -> f32 {
22381 self.0 as f32
22382 }
22383
22384 fn next_row(&self) -> Self {
22385 Self(self.0 + 1)
22386 }
22387
22388 fn previous_row(&self) -> Self {
22389 Self(self.0.saturating_sub(1))
22390 }
22391
22392 fn minus(&self, other: Self) -> u32 {
22393 self.0 - other.0
22394 }
22395}
22396
22397impl RowExt for MultiBufferRow {
22398 fn as_f32(&self) -> f32 {
22399 self.0 as f32
22400 }
22401
22402 fn next_row(&self) -> Self {
22403 Self(self.0 + 1)
22404 }
22405
22406 fn previous_row(&self) -> Self {
22407 Self(self.0.saturating_sub(1))
22408 }
22409
22410 fn minus(&self, other: Self) -> u32 {
22411 self.0 - other.0
22412 }
22413}
22414
22415trait RowRangeExt {
22416 type Row;
22417
22418 fn len(&self) -> usize;
22419
22420 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22421}
22422
22423impl RowRangeExt for Range<MultiBufferRow> {
22424 type Row = MultiBufferRow;
22425
22426 fn len(&self) -> usize {
22427 (self.end.0 - self.start.0) as usize
22428 }
22429
22430 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22431 (self.start.0..self.end.0).map(MultiBufferRow)
22432 }
22433}
22434
22435impl RowRangeExt for Range<DisplayRow> {
22436 type Row = DisplayRow;
22437
22438 fn len(&self) -> usize {
22439 (self.end.0 - self.start.0) as usize
22440 }
22441
22442 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22443 (self.start.0..self.end.0).map(DisplayRow)
22444 }
22445}
22446
22447/// If select range has more than one line, we
22448/// just point the cursor to range.start.
22449fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22450 if range.start.row == range.end.row {
22451 range
22452 } else {
22453 range.start..range.start
22454 }
22455}
22456pub struct KillRing(ClipboardItem);
22457impl Global for KillRing {}
22458
22459const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22460
22461enum BreakpointPromptEditAction {
22462 Log,
22463 Condition,
22464 HitCondition,
22465}
22466
22467struct BreakpointPromptEditor {
22468 pub(crate) prompt: Entity<Editor>,
22469 editor: WeakEntity<Editor>,
22470 breakpoint_anchor: Anchor,
22471 breakpoint: Breakpoint,
22472 edit_action: BreakpointPromptEditAction,
22473 block_ids: HashSet<CustomBlockId>,
22474 editor_margins: Arc<Mutex<EditorMargins>>,
22475 _subscriptions: Vec<Subscription>,
22476}
22477
22478impl BreakpointPromptEditor {
22479 const MAX_LINES: u8 = 4;
22480
22481 fn new(
22482 editor: WeakEntity<Editor>,
22483 breakpoint_anchor: Anchor,
22484 breakpoint: Breakpoint,
22485 edit_action: BreakpointPromptEditAction,
22486 window: &mut Window,
22487 cx: &mut Context<Self>,
22488 ) -> Self {
22489 let base_text = match edit_action {
22490 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22491 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22492 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22493 }
22494 .map(|msg| msg.to_string())
22495 .unwrap_or_default();
22496
22497 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22498 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22499
22500 let prompt = cx.new(|cx| {
22501 let mut prompt = Editor::new(
22502 EditorMode::AutoHeight {
22503 max_lines: Self::MAX_LINES as usize,
22504 },
22505 buffer,
22506 None,
22507 window,
22508 cx,
22509 );
22510 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22511 prompt.set_show_cursor_when_unfocused(false, cx);
22512 prompt.set_placeholder_text(
22513 match edit_action {
22514 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22515 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22516 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22517 },
22518 cx,
22519 );
22520
22521 prompt
22522 });
22523
22524 Self {
22525 prompt,
22526 editor,
22527 breakpoint_anchor,
22528 breakpoint,
22529 edit_action,
22530 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22531 block_ids: Default::default(),
22532 _subscriptions: vec![],
22533 }
22534 }
22535
22536 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22537 self.block_ids.extend(block_ids)
22538 }
22539
22540 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22541 if let Some(editor) = self.editor.upgrade() {
22542 let message = self
22543 .prompt
22544 .read(cx)
22545 .buffer
22546 .read(cx)
22547 .as_singleton()
22548 .expect("A multi buffer in breakpoint prompt isn't possible")
22549 .read(cx)
22550 .as_rope()
22551 .to_string();
22552
22553 editor.update(cx, |editor, cx| {
22554 editor.edit_breakpoint_at_anchor(
22555 self.breakpoint_anchor,
22556 self.breakpoint.clone(),
22557 match self.edit_action {
22558 BreakpointPromptEditAction::Log => {
22559 BreakpointEditAction::EditLogMessage(message.into())
22560 }
22561 BreakpointPromptEditAction::Condition => {
22562 BreakpointEditAction::EditCondition(message.into())
22563 }
22564 BreakpointPromptEditAction::HitCondition => {
22565 BreakpointEditAction::EditHitCondition(message.into())
22566 }
22567 },
22568 cx,
22569 );
22570
22571 editor.remove_blocks(self.block_ids.clone(), None, cx);
22572 cx.focus_self(window);
22573 });
22574 }
22575 }
22576
22577 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22578 self.editor
22579 .update(cx, |editor, cx| {
22580 editor.remove_blocks(self.block_ids.clone(), None, cx);
22581 window.focus(&editor.focus_handle);
22582 })
22583 .log_err();
22584 }
22585
22586 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22587 let settings = ThemeSettings::get_global(cx);
22588 let text_style = TextStyle {
22589 color: if self.prompt.read(cx).read_only(cx) {
22590 cx.theme().colors().text_disabled
22591 } else {
22592 cx.theme().colors().text
22593 },
22594 font_family: settings.buffer_font.family.clone(),
22595 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22596 font_size: settings.buffer_font_size(cx).into(),
22597 font_weight: settings.buffer_font.weight,
22598 line_height: relative(settings.buffer_line_height.value()),
22599 ..Default::default()
22600 };
22601 EditorElement::new(
22602 &self.prompt,
22603 EditorStyle {
22604 background: cx.theme().colors().editor_background,
22605 local_player: cx.theme().players().local(),
22606 text: text_style,
22607 ..Default::default()
22608 },
22609 )
22610 }
22611}
22612
22613impl Render for BreakpointPromptEditor {
22614 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22615 let editor_margins = *self.editor_margins.lock();
22616 let gutter_dimensions = editor_margins.gutter;
22617 h_flex()
22618 .key_context("Editor")
22619 .bg(cx.theme().colors().editor_background)
22620 .border_y_1()
22621 .border_color(cx.theme().status().info_border)
22622 .size_full()
22623 .py(window.line_height() / 2.5)
22624 .on_action(cx.listener(Self::confirm))
22625 .on_action(cx.listener(Self::cancel))
22626 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22627 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22628 }
22629}
22630
22631impl Focusable for BreakpointPromptEditor {
22632 fn focus_handle(&self, cx: &App) -> FocusHandle {
22633 self.prompt.focus_handle(cx)
22634 }
22635}
22636
22637fn all_edits_insertions_or_deletions(
22638 edits: &Vec<(Range<Anchor>, String)>,
22639 snapshot: &MultiBufferSnapshot,
22640) -> bool {
22641 let mut all_insertions = true;
22642 let mut all_deletions = true;
22643
22644 for (range, new_text) in edits.iter() {
22645 let range_is_empty = range.to_offset(&snapshot).is_empty();
22646 let text_is_empty = new_text.is_empty();
22647
22648 if range_is_empty != text_is_empty {
22649 if range_is_empty {
22650 all_deletions = false;
22651 } else {
22652 all_insertions = false;
22653 }
22654 } else {
22655 return false;
22656 }
22657
22658 if !all_insertions && !all_deletions {
22659 return false;
22660 }
22661 }
22662 all_insertions || all_deletions
22663}
22664
22665struct MissingEditPredictionKeybindingTooltip;
22666
22667impl Render for MissingEditPredictionKeybindingTooltip {
22668 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22669 ui::tooltip_container(window, cx, |container, _, cx| {
22670 container
22671 .flex_shrink_0()
22672 .max_w_80()
22673 .min_h(rems_from_px(124.))
22674 .justify_between()
22675 .child(
22676 v_flex()
22677 .flex_1()
22678 .text_ui_sm(cx)
22679 .child(Label::new("Conflict with Accept Keybinding"))
22680 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22681 )
22682 .child(
22683 h_flex()
22684 .pb_1()
22685 .gap_1()
22686 .items_end()
22687 .w_full()
22688 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22689 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22690 }))
22691 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22692 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22693 })),
22694 )
22695 })
22696 }
22697}
22698
22699#[derive(Debug, Clone, Copy, PartialEq)]
22700pub struct LineHighlight {
22701 pub background: Background,
22702 pub border: Option<gpui::Hsla>,
22703 pub include_gutter: bool,
22704 pub type_id: Option<TypeId>,
22705}
22706
22707fn render_diff_hunk_controls(
22708 row: u32,
22709 status: &DiffHunkStatus,
22710 hunk_range: Range<Anchor>,
22711 is_created_file: bool,
22712 line_height: Pixels,
22713 editor: &Entity<Editor>,
22714 _window: &mut Window,
22715 cx: &mut App,
22716) -> AnyElement {
22717 h_flex()
22718 .h(line_height)
22719 .mr_1()
22720 .gap_1()
22721 .px_0p5()
22722 .pb_1()
22723 .border_x_1()
22724 .border_b_1()
22725 .border_color(cx.theme().colors().border_variant)
22726 .rounded_b_lg()
22727 .bg(cx.theme().colors().editor_background)
22728 .gap_1()
22729 .block_mouse_except_scroll()
22730 .shadow_md()
22731 .child(if status.has_secondary_hunk() {
22732 Button::new(("stage", row as u64), "Stage")
22733 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22734 .tooltip({
22735 let focus_handle = editor.focus_handle(cx);
22736 move |window, cx| {
22737 Tooltip::for_action_in(
22738 "Stage Hunk",
22739 &::git::ToggleStaged,
22740 &focus_handle,
22741 window,
22742 cx,
22743 )
22744 }
22745 })
22746 .on_click({
22747 let editor = editor.clone();
22748 move |_event, _window, cx| {
22749 editor.update(cx, |editor, cx| {
22750 editor.stage_or_unstage_diff_hunks(
22751 true,
22752 vec![hunk_range.start..hunk_range.start],
22753 cx,
22754 );
22755 });
22756 }
22757 })
22758 } else {
22759 Button::new(("unstage", row as u64), "Unstage")
22760 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22761 .tooltip({
22762 let focus_handle = editor.focus_handle(cx);
22763 move |window, cx| {
22764 Tooltip::for_action_in(
22765 "Unstage Hunk",
22766 &::git::ToggleStaged,
22767 &focus_handle,
22768 window,
22769 cx,
22770 )
22771 }
22772 })
22773 .on_click({
22774 let editor = editor.clone();
22775 move |_event, _window, cx| {
22776 editor.update(cx, |editor, cx| {
22777 editor.stage_or_unstage_diff_hunks(
22778 false,
22779 vec![hunk_range.start..hunk_range.start],
22780 cx,
22781 );
22782 });
22783 }
22784 })
22785 })
22786 .child(
22787 Button::new(("restore", row as u64), "Restore")
22788 .tooltip({
22789 let focus_handle = editor.focus_handle(cx);
22790 move |window, cx| {
22791 Tooltip::for_action_in(
22792 "Restore Hunk",
22793 &::git::Restore,
22794 &focus_handle,
22795 window,
22796 cx,
22797 )
22798 }
22799 })
22800 .on_click({
22801 let editor = editor.clone();
22802 move |_event, window, cx| {
22803 editor.update(cx, |editor, cx| {
22804 let snapshot = editor.snapshot(window, cx);
22805 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22806 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22807 });
22808 }
22809 })
22810 .disabled(is_created_file),
22811 )
22812 .when(
22813 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22814 |el| {
22815 el.child(
22816 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22817 .shape(IconButtonShape::Square)
22818 .icon_size(IconSize::Small)
22819 // .disabled(!has_multiple_hunks)
22820 .tooltip({
22821 let focus_handle = editor.focus_handle(cx);
22822 move |window, cx| {
22823 Tooltip::for_action_in(
22824 "Next Hunk",
22825 &GoToHunk,
22826 &focus_handle,
22827 window,
22828 cx,
22829 )
22830 }
22831 })
22832 .on_click({
22833 let editor = editor.clone();
22834 move |_event, window, cx| {
22835 editor.update(cx, |editor, cx| {
22836 let snapshot = editor.snapshot(window, cx);
22837 let position =
22838 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22839 editor.go_to_hunk_before_or_after_position(
22840 &snapshot,
22841 position,
22842 Direction::Next,
22843 window,
22844 cx,
22845 );
22846 editor.expand_selected_diff_hunks(cx);
22847 });
22848 }
22849 }),
22850 )
22851 .child(
22852 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22853 .shape(IconButtonShape::Square)
22854 .icon_size(IconSize::Small)
22855 // .disabled(!has_multiple_hunks)
22856 .tooltip({
22857 let focus_handle = editor.focus_handle(cx);
22858 move |window, cx| {
22859 Tooltip::for_action_in(
22860 "Previous Hunk",
22861 &GoToPreviousHunk,
22862 &focus_handle,
22863 window,
22864 cx,
22865 )
22866 }
22867 })
22868 .on_click({
22869 let editor = editor.clone();
22870 move |_event, window, cx| {
22871 editor.update(cx, |editor, cx| {
22872 let snapshot = editor.snapshot(window, cx);
22873 let point =
22874 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22875 editor.go_to_hunk_before_or_after_position(
22876 &snapshot,
22877 point,
22878 Direction::Prev,
22879 window,
22880 cx,
22881 );
22882 editor.expand_selected_diff_hunks(cx);
22883 });
22884 }
22885 }),
22886 )
22887 },
22888 )
22889 .into_any_element()
22890}