1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 hide_task: Option<Task<()>>,
903 popover_bounds: Option<Bounds<Pixels>>,
904 popover_state: InlineBlamePopoverState,
905}
906
907enum SelectionDragState {
908 /// State when no drag related activity is detected.
909 None,
910 /// State when the mouse is down on a selection that is about to be dragged.
911 ReadyToDrag {
912 selection: Selection<Anchor>,
913 click_position: gpui::Point<Pixels>,
914 mouse_down_time: Instant,
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 inline_blame_popover_show_task: Option<Task<()>>,
1010 signature_help_state: SignatureHelpState,
1011 auto_signature_help: Option<bool>,
1012 find_all_references_task_sources: Vec<Anchor>,
1013 next_completion_id: CompletionId,
1014 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1015 code_actions_task: Option<Task<Result<()>>>,
1016 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1017 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1018 document_highlights_task: Option<Task<()>>,
1019 linked_editing_range_task: Option<Task<Option<()>>>,
1020 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1021 pending_rename: Option<RenameState>,
1022 searchable: bool,
1023 cursor_shape: CursorShape,
1024 current_line_highlight: Option<CurrentLineHighlight>,
1025 collapse_matches: bool,
1026 autoindent_mode: Option<AutoindentMode>,
1027 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1028 input_enabled: bool,
1029 use_modal_editing: bool,
1030 read_only: bool,
1031 leader_id: Option<CollaboratorId>,
1032 remote_id: Option<ViewId>,
1033 pub hover_state: HoverState,
1034 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1035 gutter_hovered: bool,
1036 hovered_link_state: Option<HoveredLinkState>,
1037 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1038 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1039 active_inline_completion: Option<InlineCompletionState>,
1040 /// Used to prevent flickering as the user types while the menu is open
1041 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1042 edit_prediction_settings: EditPredictionSettings,
1043 inline_completions_hidden_for_vim_mode: bool,
1044 show_inline_completions_override: Option<bool>,
1045 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1046 edit_prediction_preview: EditPredictionPreview,
1047 edit_prediction_indent_conflict: bool,
1048 edit_prediction_requires_modifier_in_indent_conflict: bool,
1049 inlay_hint_cache: InlayHintCache,
1050 next_inlay_id: usize,
1051 _subscriptions: Vec<Subscription>,
1052 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1053 gutter_dimensions: GutterDimensions,
1054 style: Option<EditorStyle>,
1055 text_style_refinement: Option<TextStyleRefinement>,
1056 next_editor_action_id: EditorActionId,
1057 editor_actions: Rc<
1058 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1059 >,
1060 use_autoclose: bool,
1061 use_auto_surround: bool,
1062 auto_replace_emoji_shortcode: bool,
1063 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1064 show_git_blame_gutter: bool,
1065 show_git_blame_inline: bool,
1066 show_git_blame_inline_delay_task: Option<Task<()>>,
1067 git_blame_inline_enabled: bool,
1068 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1069 serialize_dirty_buffers: bool,
1070 show_selection_menu: Option<bool>,
1071 blame: Option<Entity<GitBlame>>,
1072 blame_subscription: Option<Subscription>,
1073 custom_context_menu: Option<
1074 Box<
1075 dyn 'static
1076 + Fn(
1077 &mut Self,
1078 DisplayPoint,
1079 &mut Window,
1080 &mut Context<Self>,
1081 ) -> Option<Entity<ui::ContextMenu>>,
1082 >,
1083 >,
1084 last_bounds: Option<Bounds<Pixels>>,
1085 last_position_map: Option<Rc<PositionMap>>,
1086 expect_bounds_change: Option<Bounds<Pixels>>,
1087 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1088 tasks_update_task: Option<Task<()>>,
1089 breakpoint_store: Option<Entity<BreakpointStore>>,
1090 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1091 hovered_diff_hunk_row: Option<DisplayRow>,
1092 pull_diagnostics_task: Task<()>,
1093 in_project_search: bool,
1094 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1095 breadcrumb_header: Option<String>,
1096 focused_block: Option<FocusedBlock>,
1097 next_scroll_position: NextScrollCursorCenterTopBottom,
1098 addons: HashMap<TypeId, Box<dyn Addon>>,
1099 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1100 load_diff_task: Option<Shared<Task<()>>>,
1101 /// Whether we are temporarily displaying a diff other than git's
1102 temporary_diff_override: bool,
1103 selection_mark_mode: bool,
1104 toggle_fold_multiple_buffers: Task<()>,
1105 _scroll_cursor_center_top_bottom_task: Task<()>,
1106 serialize_selections: Task<()>,
1107 serialize_folds: Task<()>,
1108 mouse_cursor_hidden: bool,
1109 minimap: Option<Entity<Self>>,
1110 hide_mouse_mode: HideMouseMode,
1111 pub change_list: ChangeList,
1112 inline_value_cache: InlineValueCache,
1113 selection_drag_state: SelectionDragState,
1114 drag_and_drop_selection_enabled: bool,
1115}
1116
1117#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1118enum NextScrollCursorCenterTopBottom {
1119 #[default]
1120 Center,
1121 Top,
1122 Bottom,
1123}
1124
1125impl NextScrollCursorCenterTopBottom {
1126 fn next(&self) -> Self {
1127 match self {
1128 Self::Center => Self::Top,
1129 Self::Top => Self::Bottom,
1130 Self::Bottom => Self::Center,
1131 }
1132 }
1133}
1134
1135#[derive(Clone)]
1136pub struct EditorSnapshot {
1137 pub mode: EditorMode,
1138 show_gutter: bool,
1139 show_line_numbers: Option<bool>,
1140 show_git_diff_gutter: Option<bool>,
1141 show_code_actions: Option<bool>,
1142 show_runnables: Option<bool>,
1143 show_breakpoints: Option<bool>,
1144 git_blame_gutter_max_author_length: Option<usize>,
1145 pub display_snapshot: DisplaySnapshot,
1146 pub placeholder_text: Option<Arc<str>>,
1147 is_focused: bool,
1148 scroll_anchor: ScrollAnchor,
1149 ongoing_scroll: OngoingScroll,
1150 current_line_highlight: CurrentLineHighlight,
1151 gutter_hovered: bool,
1152}
1153
1154#[derive(Default, Debug, Clone, Copy)]
1155pub struct GutterDimensions {
1156 pub left_padding: Pixels,
1157 pub right_padding: Pixels,
1158 pub width: Pixels,
1159 pub margin: Pixels,
1160 pub git_blame_entries_width: Option<Pixels>,
1161}
1162
1163impl GutterDimensions {
1164 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1165 Self {
1166 margin: Self::default_gutter_margin(font_id, font_size, cx),
1167 ..Default::default()
1168 }
1169 }
1170
1171 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1172 -cx.text_system().descent(font_id, font_size)
1173 }
1174 /// The full width of the space taken up by the gutter.
1175 pub fn full_width(&self) -> Pixels {
1176 self.margin + self.width
1177 }
1178
1179 /// The width of the space reserved for the fold indicators,
1180 /// use alongside 'justify_end' and `gutter_width` to
1181 /// right align content with the line numbers
1182 pub fn fold_area_width(&self) -> Pixels {
1183 self.margin + self.right_padding
1184 }
1185}
1186
1187#[derive(Debug)]
1188pub struct RemoteSelection {
1189 pub replica_id: ReplicaId,
1190 pub selection: Selection<Anchor>,
1191 pub cursor_shape: CursorShape,
1192 pub collaborator_id: CollaboratorId,
1193 pub line_mode: bool,
1194 pub user_name: Option<SharedString>,
1195 pub color: PlayerColor,
1196}
1197
1198#[derive(Clone, Debug)]
1199struct SelectionHistoryEntry {
1200 selections: Arc<[Selection<Anchor>]>,
1201 select_next_state: Option<SelectNextState>,
1202 select_prev_state: Option<SelectNextState>,
1203 add_selections_state: Option<AddSelectionsState>,
1204}
1205
1206#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1207enum SelectionHistoryMode {
1208 Normal,
1209 Undoing,
1210 Redoing,
1211 Skipping,
1212}
1213
1214#[derive(Clone, PartialEq, Eq, Hash)]
1215struct HoveredCursor {
1216 replica_id: u16,
1217 selection_id: usize,
1218}
1219
1220impl Default for SelectionHistoryMode {
1221 fn default() -> Self {
1222 Self::Normal
1223 }
1224}
1225
1226#[derive(Debug)]
1227pub struct SelectionEffects {
1228 nav_history: bool,
1229 completions: bool,
1230 scroll: Option<Autoscroll>,
1231}
1232
1233impl Default for SelectionEffects {
1234 fn default() -> Self {
1235 Self {
1236 nav_history: true,
1237 completions: true,
1238 scroll: Some(Autoscroll::fit()),
1239 }
1240 }
1241}
1242impl SelectionEffects {
1243 pub fn scroll(scroll: Autoscroll) -> Self {
1244 Self {
1245 scroll: Some(scroll),
1246 ..Default::default()
1247 }
1248 }
1249
1250 pub fn no_scroll() -> Self {
1251 Self {
1252 scroll: None,
1253 ..Default::default()
1254 }
1255 }
1256
1257 pub fn completions(self, completions: bool) -> Self {
1258 Self {
1259 completions,
1260 ..self
1261 }
1262 }
1263
1264 pub fn nav_history(self, nav_history: bool) -> Self {
1265 Self {
1266 nav_history,
1267 ..self
1268 }
1269 }
1270}
1271
1272struct DeferredSelectionEffectsState {
1273 changed: bool,
1274 effects: SelectionEffects,
1275 old_cursor_position: Anchor,
1276 history_entry: SelectionHistoryEntry,
1277}
1278
1279#[derive(Default)]
1280struct SelectionHistory {
1281 #[allow(clippy::type_complexity)]
1282 selections_by_transaction:
1283 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1284 mode: SelectionHistoryMode,
1285 undo_stack: VecDeque<SelectionHistoryEntry>,
1286 redo_stack: VecDeque<SelectionHistoryEntry>,
1287}
1288
1289impl SelectionHistory {
1290 #[track_caller]
1291 fn insert_transaction(
1292 &mut self,
1293 transaction_id: TransactionId,
1294 selections: Arc<[Selection<Anchor>]>,
1295 ) {
1296 if selections.is_empty() {
1297 log::error!(
1298 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1299 std::panic::Location::caller()
1300 );
1301 return;
1302 }
1303 self.selections_by_transaction
1304 .insert(transaction_id, (selections, None));
1305 }
1306
1307 #[allow(clippy::type_complexity)]
1308 fn transaction(
1309 &self,
1310 transaction_id: TransactionId,
1311 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1312 self.selections_by_transaction.get(&transaction_id)
1313 }
1314
1315 #[allow(clippy::type_complexity)]
1316 fn transaction_mut(
1317 &mut self,
1318 transaction_id: TransactionId,
1319 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1320 self.selections_by_transaction.get_mut(&transaction_id)
1321 }
1322
1323 fn push(&mut self, entry: SelectionHistoryEntry) {
1324 if !entry.selections.is_empty() {
1325 match self.mode {
1326 SelectionHistoryMode::Normal => {
1327 self.push_undo(entry);
1328 self.redo_stack.clear();
1329 }
1330 SelectionHistoryMode::Undoing => self.push_redo(entry),
1331 SelectionHistoryMode::Redoing => self.push_undo(entry),
1332 SelectionHistoryMode::Skipping => {}
1333 }
1334 }
1335 }
1336
1337 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1338 if self
1339 .undo_stack
1340 .back()
1341 .map_or(true, |e| e.selections != entry.selections)
1342 {
1343 self.undo_stack.push_back(entry);
1344 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1345 self.undo_stack.pop_front();
1346 }
1347 }
1348 }
1349
1350 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1351 if self
1352 .redo_stack
1353 .back()
1354 .map_or(true, |e| e.selections != entry.selections)
1355 {
1356 self.redo_stack.push_back(entry);
1357 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1358 self.redo_stack.pop_front();
1359 }
1360 }
1361 }
1362}
1363
1364#[derive(Clone, Copy)]
1365pub struct RowHighlightOptions {
1366 pub autoscroll: bool,
1367 pub include_gutter: bool,
1368}
1369
1370impl Default for RowHighlightOptions {
1371 fn default() -> Self {
1372 Self {
1373 autoscroll: Default::default(),
1374 include_gutter: true,
1375 }
1376 }
1377}
1378
1379struct RowHighlight {
1380 index: usize,
1381 range: Range<Anchor>,
1382 color: Hsla,
1383 options: RowHighlightOptions,
1384 type_id: TypeId,
1385}
1386
1387#[derive(Clone, Debug)]
1388struct AddSelectionsState {
1389 groups: Vec<AddSelectionsGroup>,
1390}
1391
1392#[derive(Clone, Debug)]
1393struct AddSelectionsGroup {
1394 above: bool,
1395 stack: Vec<usize>,
1396}
1397
1398#[derive(Clone)]
1399struct SelectNextState {
1400 query: AhoCorasick,
1401 wordwise: bool,
1402 done: bool,
1403}
1404
1405impl std::fmt::Debug for SelectNextState {
1406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1407 f.debug_struct(std::any::type_name::<Self>())
1408 .field("wordwise", &self.wordwise)
1409 .field("done", &self.done)
1410 .finish()
1411 }
1412}
1413
1414#[derive(Debug)]
1415struct AutocloseRegion {
1416 selection_id: usize,
1417 range: Range<Anchor>,
1418 pair: BracketPair,
1419}
1420
1421#[derive(Debug)]
1422struct SnippetState {
1423 ranges: Vec<Vec<Range<Anchor>>>,
1424 active_index: usize,
1425 choices: Vec<Option<Vec<String>>>,
1426}
1427
1428#[doc(hidden)]
1429pub struct RenameState {
1430 pub range: Range<Anchor>,
1431 pub old_name: Arc<str>,
1432 pub editor: Entity<Editor>,
1433 block_id: CustomBlockId,
1434}
1435
1436struct InvalidationStack<T>(Vec<T>);
1437
1438struct RegisteredInlineCompletionProvider {
1439 provider: Arc<dyn InlineCompletionProviderHandle>,
1440 _subscription: Subscription,
1441}
1442
1443#[derive(Debug, PartialEq, Eq)]
1444pub struct ActiveDiagnosticGroup {
1445 pub active_range: Range<Anchor>,
1446 pub active_message: String,
1447 pub group_id: usize,
1448 pub blocks: HashSet<CustomBlockId>,
1449}
1450
1451#[derive(Debug, PartialEq, Eq)]
1452
1453pub(crate) enum ActiveDiagnostic {
1454 None,
1455 All,
1456 Group(ActiveDiagnosticGroup),
1457}
1458
1459#[derive(Serialize, Deserialize, Clone, Debug)]
1460pub struct ClipboardSelection {
1461 /// The number of bytes in this selection.
1462 pub len: usize,
1463 /// Whether this was a full-line selection.
1464 pub is_entire_line: bool,
1465 /// The indentation of the first line when this content was originally copied.
1466 pub first_line_indent: u32,
1467}
1468
1469// selections, scroll behavior, was newest selection reversed
1470type SelectSyntaxNodeHistoryState = (
1471 Box<[Selection<usize>]>,
1472 SelectSyntaxNodeScrollBehavior,
1473 bool,
1474);
1475
1476#[derive(Default)]
1477struct SelectSyntaxNodeHistory {
1478 stack: Vec<SelectSyntaxNodeHistoryState>,
1479 // disable temporarily to allow changing selections without losing the stack
1480 pub disable_clearing: bool,
1481}
1482
1483impl SelectSyntaxNodeHistory {
1484 pub fn try_clear(&mut self) {
1485 if !self.disable_clearing {
1486 self.stack.clear();
1487 }
1488 }
1489
1490 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1491 self.stack.push(selection);
1492 }
1493
1494 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1495 self.stack.pop()
1496 }
1497}
1498
1499enum SelectSyntaxNodeScrollBehavior {
1500 CursorTop,
1501 FitSelection,
1502 CursorBottom,
1503}
1504
1505#[derive(Debug)]
1506pub(crate) struct NavigationData {
1507 cursor_anchor: Anchor,
1508 cursor_position: Point,
1509 scroll_anchor: ScrollAnchor,
1510 scroll_top_row: u32,
1511}
1512
1513#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1514pub enum GotoDefinitionKind {
1515 Symbol,
1516 Declaration,
1517 Type,
1518 Implementation,
1519}
1520
1521#[derive(Debug, Clone)]
1522enum InlayHintRefreshReason {
1523 ModifiersChanged(bool),
1524 Toggle(bool),
1525 SettingsChange(InlayHintSettings),
1526 NewLinesShown,
1527 BufferEdited(HashSet<Arc<Language>>),
1528 RefreshRequested,
1529 ExcerptsRemoved(Vec<ExcerptId>),
1530}
1531
1532impl InlayHintRefreshReason {
1533 fn description(&self) -> &'static str {
1534 match self {
1535 Self::ModifiersChanged(_) => "modifiers changed",
1536 Self::Toggle(_) => "toggle",
1537 Self::SettingsChange(_) => "settings change",
1538 Self::NewLinesShown => "new lines shown",
1539 Self::BufferEdited(_) => "buffer edited",
1540 Self::RefreshRequested => "refresh requested",
1541 Self::ExcerptsRemoved(_) => "excerpts removed",
1542 }
1543 }
1544}
1545
1546pub enum FormatTarget {
1547 Buffers(HashSet<Entity<Buffer>>),
1548 Ranges(Vec<Range<MultiBufferPoint>>),
1549}
1550
1551pub(crate) struct FocusedBlock {
1552 id: BlockId,
1553 focus_handle: WeakFocusHandle,
1554}
1555
1556#[derive(Clone)]
1557enum JumpData {
1558 MultiBufferRow {
1559 row: MultiBufferRow,
1560 line_offset_from_top: u32,
1561 },
1562 MultiBufferPoint {
1563 excerpt_id: ExcerptId,
1564 position: Point,
1565 anchor: text::Anchor,
1566 line_offset_from_top: u32,
1567 },
1568}
1569
1570pub enum MultibufferSelectionMode {
1571 First,
1572 All,
1573}
1574
1575#[derive(Clone, Copy, Debug, Default)]
1576pub struct RewrapOptions {
1577 pub override_language_settings: bool,
1578 pub preserve_existing_whitespace: bool,
1579}
1580
1581impl Editor {
1582 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1583 let buffer = cx.new(|cx| Buffer::local("", cx));
1584 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1585 Self::new(
1586 EditorMode::SingleLine { auto_width: false },
1587 buffer,
1588 None,
1589 window,
1590 cx,
1591 )
1592 }
1593
1594 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1595 let buffer = cx.new(|cx| Buffer::local("", cx));
1596 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1597 Self::new(EditorMode::full(), buffer, None, window, cx)
1598 }
1599
1600 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1601 let buffer = cx.new(|cx| Buffer::local("", cx));
1602 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1603 Self::new(
1604 EditorMode::SingleLine { auto_width: true },
1605 buffer,
1606 None,
1607 window,
1608 cx,
1609 )
1610 }
1611
1612 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1613 let buffer = cx.new(|cx| Buffer::local("", cx));
1614 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1615 Self::new(
1616 EditorMode::AutoHeight { max_lines },
1617 buffer,
1618 None,
1619 window,
1620 cx,
1621 )
1622 }
1623
1624 pub fn for_buffer(
1625 buffer: Entity<Buffer>,
1626 project: Option<Entity<Project>>,
1627 window: &mut Window,
1628 cx: &mut Context<Self>,
1629 ) -> Self {
1630 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1631 Self::new(EditorMode::full(), buffer, project, window, cx)
1632 }
1633
1634 pub fn for_multibuffer(
1635 buffer: Entity<MultiBuffer>,
1636 project: Option<Entity<Project>>,
1637 window: &mut Window,
1638 cx: &mut Context<Self>,
1639 ) -> Self {
1640 Self::new(EditorMode::full(), buffer, project, window, cx)
1641 }
1642
1643 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1644 let mut clone = Self::new(
1645 self.mode.clone(),
1646 self.buffer.clone(),
1647 self.project.clone(),
1648 window,
1649 cx,
1650 );
1651 self.display_map.update(cx, |display_map, cx| {
1652 let snapshot = display_map.snapshot(cx);
1653 clone.display_map.update(cx, |display_map, cx| {
1654 display_map.set_state(&snapshot, cx);
1655 });
1656 });
1657 clone.folds_did_change(cx);
1658 clone.selections.clone_state(&self.selections);
1659 clone.scroll_manager.clone_state(&self.scroll_manager);
1660 clone.searchable = self.searchable;
1661 clone.read_only = self.read_only;
1662 clone
1663 }
1664
1665 pub fn new(
1666 mode: EditorMode,
1667 buffer: Entity<MultiBuffer>,
1668 project: Option<Entity<Project>>,
1669 window: &mut Window,
1670 cx: &mut Context<Self>,
1671 ) -> Self {
1672 Editor::new_internal(mode, buffer, project, None, window, cx)
1673 }
1674
1675 fn new_internal(
1676 mode: EditorMode,
1677 buffer: Entity<MultiBuffer>,
1678 project: Option<Entity<Project>>,
1679 display_map: Option<Entity<DisplayMap>>,
1680 window: &mut Window,
1681 cx: &mut Context<Self>,
1682 ) -> Self {
1683 debug_assert!(
1684 display_map.is_none() || mode.is_minimap(),
1685 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1686 );
1687
1688 let full_mode = mode.is_full();
1689 let diagnostics_max_severity = if full_mode {
1690 EditorSettings::get_global(cx)
1691 .diagnostics_max_severity
1692 .unwrap_or(DiagnosticSeverity::Hint)
1693 } else {
1694 DiagnosticSeverity::Off
1695 };
1696 let style = window.text_style();
1697 let font_size = style.font_size.to_pixels(window.rem_size());
1698 let editor = cx.entity().downgrade();
1699 let fold_placeholder = FoldPlaceholder {
1700 constrain_width: true,
1701 render: Arc::new(move |fold_id, fold_range, cx| {
1702 let editor = editor.clone();
1703 div()
1704 .id(fold_id)
1705 .bg(cx.theme().colors().ghost_element_background)
1706 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1707 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1708 .rounded_xs()
1709 .size_full()
1710 .cursor_pointer()
1711 .child("⋯")
1712 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1713 .on_click(move |_, _window, cx| {
1714 editor
1715 .update(cx, |editor, cx| {
1716 editor.unfold_ranges(
1717 &[fold_range.start..fold_range.end],
1718 true,
1719 false,
1720 cx,
1721 );
1722 cx.stop_propagation();
1723 })
1724 .ok();
1725 })
1726 .into_any()
1727 }),
1728 merge_adjacent: true,
1729 ..FoldPlaceholder::default()
1730 };
1731 let display_map = display_map.unwrap_or_else(|| {
1732 cx.new(|cx| {
1733 DisplayMap::new(
1734 buffer.clone(),
1735 style.font(),
1736 font_size,
1737 None,
1738 FILE_HEADER_HEIGHT,
1739 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1740 fold_placeholder,
1741 diagnostics_max_severity,
1742 cx,
1743 )
1744 })
1745 });
1746
1747 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1748
1749 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1750
1751 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1752 .then(|| language_settings::SoftWrap::None);
1753
1754 let mut project_subscriptions = Vec::new();
1755 if mode.is_full() {
1756 if let Some(project) = project.as_ref() {
1757 project_subscriptions.push(cx.subscribe_in(
1758 project,
1759 window,
1760 |editor, _, event, window, cx| match event {
1761 project::Event::RefreshCodeLens => {
1762 // we always query lens with actions, without storing them, always refreshing them
1763 }
1764 project::Event::RefreshInlayHints => {
1765 editor
1766 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1767 }
1768 project::Event::LanguageServerAdded(..)
1769 | project::Event::LanguageServerRemoved(..) => {
1770 if editor.tasks_update_task.is_none() {
1771 editor.tasks_update_task =
1772 Some(editor.refresh_runnables(window, cx));
1773 }
1774 editor.pull_diagnostics(None, window, cx);
1775 }
1776 project::Event::SnippetEdit(id, snippet_edits) => {
1777 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1778 let focus_handle = editor.focus_handle(cx);
1779 if focus_handle.is_focused(window) {
1780 let snapshot = buffer.read(cx).snapshot();
1781 for (range, snippet) in snippet_edits {
1782 let editor_range =
1783 language::range_from_lsp(*range).to_offset(&snapshot);
1784 editor
1785 .insert_snippet(
1786 &[editor_range],
1787 snippet.clone(),
1788 window,
1789 cx,
1790 )
1791 .ok();
1792 }
1793 }
1794 }
1795 }
1796 _ => {}
1797 },
1798 ));
1799 if let Some(task_inventory) = project
1800 .read(cx)
1801 .task_store()
1802 .read(cx)
1803 .task_inventory()
1804 .cloned()
1805 {
1806 project_subscriptions.push(cx.observe_in(
1807 &task_inventory,
1808 window,
1809 |editor, _, window, cx| {
1810 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1811 },
1812 ));
1813 };
1814
1815 project_subscriptions.push(cx.subscribe_in(
1816 &project.read(cx).breakpoint_store(),
1817 window,
1818 |editor, _, event, window, cx| match event {
1819 BreakpointStoreEvent::ClearDebugLines => {
1820 editor.clear_row_highlights::<ActiveDebugLine>();
1821 editor.refresh_inline_values(cx);
1822 }
1823 BreakpointStoreEvent::SetDebugLine => {
1824 if editor.go_to_active_debug_line(window, cx) {
1825 cx.stop_propagation();
1826 }
1827
1828 editor.refresh_inline_values(cx);
1829 }
1830 _ => {}
1831 },
1832 ));
1833 }
1834 }
1835
1836 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1837
1838 let inlay_hint_settings =
1839 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1840 let focus_handle = cx.focus_handle();
1841 cx.on_focus(&focus_handle, window, Self::handle_focus)
1842 .detach();
1843 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1844 .detach();
1845 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1846 .detach();
1847 cx.on_blur(&focus_handle, window, Self::handle_blur)
1848 .detach();
1849 cx.observe_pending_input(window, Self::observe_pending_input)
1850 .detach();
1851
1852 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1853 Some(false)
1854 } else {
1855 None
1856 };
1857
1858 let breakpoint_store = match (&mode, project.as_ref()) {
1859 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1860 _ => None,
1861 };
1862
1863 let mut code_action_providers = Vec::new();
1864 let mut load_uncommitted_diff = None;
1865 if let Some(project) = project.clone() {
1866 load_uncommitted_diff = Some(
1867 update_uncommitted_diff_for_buffer(
1868 cx.entity(),
1869 &project,
1870 buffer.read(cx).all_buffers(),
1871 buffer.clone(),
1872 cx,
1873 )
1874 .shared(),
1875 );
1876 code_action_providers.push(Rc::new(project) as Rc<_>);
1877 }
1878
1879 let mut editor = Self {
1880 focus_handle,
1881 show_cursor_when_unfocused: false,
1882 last_focused_descendant: None,
1883 buffer: buffer.clone(),
1884 display_map: display_map.clone(),
1885 selections,
1886 scroll_manager: ScrollManager::new(cx),
1887 columnar_selection_tail: None,
1888 columnar_display_point: None,
1889 add_selections_state: None,
1890 select_next_state: None,
1891 select_prev_state: None,
1892 selection_history: SelectionHistory::default(),
1893 defer_selection_effects: false,
1894 deferred_selection_effects_state: None,
1895 autoclose_regions: Vec::new(),
1896 snippet_stack: InvalidationStack::default(),
1897 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1898 ime_transaction: None,
1899 active_diagnostics: ActiveDiagnostic::None,
1900 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1901 inline_diagnostics_update: Task::ready(()),
1902 inline_diagnostics: Vec::new(),
1903 soft_wrap_mode_override,
1904 diagnostics_max_severity,
1905 hard_wrap: None,
1906 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1907 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1908 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1909 project,
1910 blink_manager: blink_manager.clone(),
1911 show_local_selections: true,
1912 show_scrollbars: ScrollbarAxes {
1913 horizontal: full_mode,
1914 vertical: full_mode,
1915 },
1916 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1917 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1918 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1919 show_gutter: mode.is_full(),
1920 show_line_numbers: None,
1921 use_relative_line_numbers: None,
1922 disable_expand_excerpt_buttons: false,
1923 show_git_diff_gutter: None,
1924 show_code_actions: None,
1925 show_runnables: None,
1926 show_breakpoints: None,
1927 show_wrap_guides: None,
1928 show_indent_guides,
1929 placeholder_text: None,
1930 highlight_order: 0,
1931 highlighted_rows: HashMap::default(),
1932 background_highlights: TreeMap::default(),
1933 gutter_highlights: TreeMap::default(),
1934 scrollbar_marker_state: ScrollbarMarkerState::default(),
1935 active_indent_guides_state: ActiveIndentGuidesState::default(),
1936 nav_history: None,
1937 context_menu: RefCell::new(None),
1938 context_menu_options: None,
1939 mouse_context_menu: None,
1940 completion_tasks: Vec::new(),
1941 inline_blame_popover: None,
1942 inline_blame_popover_show_task: None,
1943 signature_help_state: SignatureHelpState::default(),
1944 auto_signature_help: None,
1945 find_all_references_task_sources: Vec::new(),
1946 next_completion_id: 0,
1947 next_inlay_id: 0,
1948 code_action_providers,
1949 available_code_actions: None,
1950 code_actions_task: None,
1951 quick_selection_highlight_task: None,
1952 debounced_selection_highlight_task: None,
1953 document_highlights_task: None,
1954 linked_editing_range_task: None,
1955 pending_rename: None,
1956 searchable: true,
1957 cursor_shape: EditorSettings::get_global(cx)
1958 .cursor_shape
1959 .unwrap_or_default(),
1960 current_line_highlight: None,
1961 autoindent_mode: Some(AutoindentMode::EachLine),
1962 collapse_matches: false,
1963 workspace: None,
1964 input_enabled: true,
1965 use_modal_editing: mode.is_full(),
1966 read_only: mode.is_minimap(),
1967 use_autoclose: true,
1968 use_auto_surround: true,
1969 auto_replace_emoji_shortcode: false,
1970 jsx_tag_auto_close_enabled_in_any_buffer: false,
1971 leader_id: None,
1972 remote_id: None,
1973 hover_state: HoverState::default(),
1974 pending_mouse_down: None,
1975 hovered_link_state: None,
1976 edit_prediction_provider: None,
1977 active_inline_completion: None,
1978 stale_inline_completion_in_menu: None,
1979 edit_prediction_preview: EditPredictionPreview::Inactive {
1980 released_too_fast: false,
1981 },
1982 inline_diagnostics_enabled: mode.is_full(),
1983 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1984 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1985
1986 gutter_hovered: false,
1987 pixel_position_of_newest_cursor: None,
1988 last_bounds: None,
1989 last_position_map: None,
1990 expect_bounds_change: None,
1991 gutter_dimensions: GutterDimensions::default(),
1992 style: None,
1993 show_cursor_names: false,
1994 hovered_cursors: HashMap::default(),
1995 next_editor_action_id: EditorActionId::default(),
1996 editor_actions: Rc::default(),
1997 inline_completions_hidden_for_vim_mode: false,
1998 show_inline_completions_override: None,
1999 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2000 edit_prediction_settings: EditPredictionSettings::Disabled,
2001 edit_prediction_indent_conflict: false,
2002 edit_prediction_requires_modifier_in_indent_conflict: true,
2003 custom_context_menu: None,
2004 show_git_blame_gutter: false,
2005 show_git_blame_inline: false,
2006 show_selection_menu: None,
2007 show_git_blame_inline_delay_task: None,
2008 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2009 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2010 serialize_dirty_buffers: !mode.is_minimap()
2011 && ProjectSettings::get_global(cx)
2012 .session
2013 .restore_unsaved_buffers,
2014 blame: None,
2015 blame_subscription: None,
2016 tasks: BTreeMap::default(),
2017
2018 breakpoint_store,
2019 gutter_breakpoint_indicator: (None, None),
2020 hovered_diff_hunk_row: None,
2021 _subscriptions: vec![
2022 cx.observe(&buffer, Self::on_buffer_changed),
2023 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2024 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2025 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2026 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2027 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2028 cx.observe_window_activation(window, |editor, window, cx| {
2029 let active = window.is_window_active();
2030 editor.blink_manager.update(cx, |blink_manager, cx| {
2031 if active {
2032 blink_manager.enable(cx);
2033 } else {
2034 blink_manager.disable(cx);
2035 }
2036 });
2037 if active {
2038 editor.show_mouse_cursor();
2039 }
2040 }),
2041 ],
2042 tasks_update_task: None,
2043 pull_diagnostics_task: Task::ready(()),
2044 linked_edit_ranges: Default::default(),
2045 in_project_search: false,
2046 previous_search_ranges: None,
2047 breadcrumb_header: None,
2048 focused_block: None,
2049 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2050 addons: HashMap::default(),
2051 registered_buffers: HashMap::default(),
2052 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2053 selection_mark_mode: false,
2054 toggle_fold_multiple_buffers: Task::ready(()),
2055 serialize_selections: Task::ready(()),
2056 serialize_folds: Task::ready(()),
2057 text_style_refinement: None,
2058 load_diff_task: load_uncommitted_diff,
2059 temporary_diff_override: false,
2060 mouse_cursor_hidden: false,
2061 minimap: None,
2062 hide_mouse_mode: EditorSettings::get_global(cx)
2063 .hide_mouse
2064 .unwrap_or_default(),
2065 change_list: ChangeList::new(),
2066 mode,
2067 selection_drag_state: SelectionDragState::None,
2068 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2069 };
2070 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2071 editor
2072 ._subscriptions
2073 .push(cx.observe(breakpoints, |_, _, cx| {
2074 cx.notify();
2075 }));
2076 }
2077 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2078 editor._subscriptions.extend(project_subscriptions);
2079
2080 editor._subscriptions.push(cx.subscribe_in(
2081 &cx.entity(),
2082 window,
2083 |editor, _, e: &EditorEvent, window, cx| match e {
2084 EditorEvent::ScrollPositionChanged { local, .. } => {
2085 if *local {
2086 let new_anchor = editor.scroll_manager.anchor();
2087 let snapshot = editor.snapshot(window, cx);
2088 editor.update_restoration_data(cx, move |data| {
2089 data.scroll_position = (
2090 new_anchor.top_row(&snapshot.buffer_snapshot),
2091 new_anchor.offset,
2092 );
2093 });
2094 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2095 editor.inline_blame_popover.take();
2096 }
2097 }
2098 EditorEvent::Edited { .. } => {
2099 if !vim_enabled(cx) {
2100 let (map, selections) = editor.selections.all_adjusted_display(cx);
2101 let pop_state = editor
2102 .change_list
2103 .last()
2104 .map(|previous| {
2105 previous.len() == selections.len()
2106 && previous.iter().enumerate().all(|(ix, p)| {
2107 p.to_display_point(&map).row()
2108 == selections[ix].head().row()
2109 })
2110 })
2111 .unwrap_or(false);
2112 let new_positions = selections
2113 .into_iter()
2114 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2115 .collect();
2116 editor
2117 .change_list
2118 .push_to_change_list(pop_state, new_positions);
2119 }
2120 }
2121 _ => (),
2122 },
2123 ));
2124
2125 if let Some(dap_store) = editor
2126 .project
2127 .as_ref()
2128 .map(|project| project.read(cx).dap_store())
2129 {
2130 let weak_editor = cx.weak_entity();
2131
2132 editor
2133 ._subscriptions
2134 .push(
2135 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2136 let session_entity = cx.entity();
2137 weak_editor
2138 .update(cx, |editor, cx| {
2139 editor._subscriptions.push(
2140 cx.subscribe(&session_entity, Self::on_debug_session_event),
2141 );
2142 })
2143 .ok();
2144 }),
2145 );
2146
2147 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2148 editor
2149 ._subscriptions
2150 .push(cx.subscribe(&session, Self::on_debug_session_event));
2151 }
2152 }
2153
2154 // skip adding the initial selection to selection history
2155 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2156 editor.end_selection(window, cx);
2157 editor.selection_history.mode = SelectionHistoryMode::Normal;
2158
2159 editor.scroll_manager.show_scrollbars(window, cx);
2160 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2161
2162 if full_mode {
2163 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2164 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2165
2166 if editor.git_blame_inline_enabled {
2167 editor.start_git_blame_inline(false, window, cx);
2168 }
2169
2170 editor.go_to_active_debug_line(window, cx);
2171
2172 if let Some(buffer) = buffer.read(cx).as_singleton() {
2173 if let Some(project) = editor.project.as_ref() {
2174 let handle = project.update(cx, |project, cx| {
2175 project.register_buffer_with_language_servers(&buffer, cx)
2176 });
2177 editor
2178 .registered_buffers
2179 .insert(buffer.read(cx).remote_id(), handle);
2180 }
2181 }
2182
2183 editor.minimap =
2184 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2185 editor.pull_diagnostics(None, window, cx);
2186 }
2187
2188 editor.report_editor_event("Editor Opened", None, cx);
2189 editor
2190 }
2191
2192 pub fn deploy_mouse_context_menu(
2193 &mut self,
2194 position: gpui::Point<Pixels>,
2195 context_menu: Entity<ContextMenu>,
2196 window: &mut Window,
2197 cx: &mut Context<Self>,
2198 ) {
2199 self.mouse_context_menu = Some(MouseContextMenu::new(
2200 self,
2201 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2202 context_menu,
2203 window,
2204 cx,
2205 ));
2206 }
2207
2208 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2209 self.mouse_context_menu
2210 .as_ref()
2211 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2212 }
2213
2214 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2215 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2216 }
2217
2218 fn key_context_internal(
2219 &self,
2220 has_active_edit_prediction: bool,
2221 window: &Window,
2222 cx: &App,
2223 ) -> KeyContext {
2224 let mut key_context = KeyContext::new_with_defaults();
2225 key_context.add("Editor");
2226 let mode = match self.mode {
2227 EditorMode::SingleLine { .. } => "single_line",
2228 EditorMode::AutoHeight { .. } => "auto_height",
2229 EditorMode::Minimap { .. } => "minimap",
2230 EditorMode::Full { .. } => "full",
2231 };
2232
2233 if EditorSettings::jupyter_enabled(cx) {
2234 key_context.add("jupyter");
2235 }
2236
2237 key_context.set("mode", mode);
2238 if self.pending_rename.is_some() {
2239 key_context.add("renaming");
2240 }
2241
2242 match self.context_menu.borrow().as_ref() {
2243 Some(CodeContextMenu::Completions(_)) => {
2244 key_context.add("menu");
2245 key_context.add("showing_completions");
2246 }
2247 Some(CodeContextMenu::CodeActions(_)) => {
2248 key_context.add("menu");
2249 key_context.add("showing_code_actions")
2250 }
2251 None => {}
2252 }
2253
2254 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2255 if !self.focus_handle(cx).contains_focused(window, cx)
2256 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2257 {
2258 for addon in self.addons.values() {
2259 addon.extend_key_context(&mut key_context, cx)
2260 }
2261 }
2262
2263 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2264 if let Some(extension) = singleton_buffer
2265 .read(cx)
2266 .file()
2267 .and_then(|file| file.path().extension()?.to_str())
2268 {
2269 key_context.set("extension", extension.to_string());
2270 }
2271 } else {
2272 key_context.add("multibuffer");
2273 }
2274
2275 if has_active_edit_prediction {
2276 if self.edit_prediction_in_conflict() {
2277 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2278 } else {
2279 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2280 key_context.add("copilot_suggestion");
2281 }
2282 }
2283
2284 if self.selection_mark_mode {
2285 key_context.add("selection_mode");
2286 }
2287
2288 key_context
2289 }
2290
2291 fn show_mouse_cursor(&mut self) {
2292 self.mouse_cursor_hidden = false;
2293 }
2294
2295 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2296 self.mouse_cursor_hidden = match origin {
2297 HideMouseCursorOrigin::TypingAction => {
2298 matches!(
2299 self.hide_mouse_mode,
2300 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2301 )
2302 }
2303 HideMouseCursorOrigin::MovementAction => {
2304 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2305 }
2306 };
2307 }
2308
2309 pub fn edit_prediction_in_conflict(&self) -> bool {
2310 if !self.show_edit_predictions_in_menu() {
2311 return false;
2312 }
2313
2314 let showing_completions = self
2315 .context_menu
2316 .borrow()
2317 .as_ref()
2318 .map_or(false, |context| {
2319 matches!(context, CodeContextMenu::Completions(_))
2320 });
2321
2322 showing_completions
2323 || self.edit_prediction_requires_modifier()
2324 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2325 // bindings to insert tab characters.
2326 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2327 }
2328
2329 pub fn accept_edit_prediction_keybind(
2330 &self,
2331 accept_partial: bool,
2332 window: &Window,
2333 cx: &App,
2334 ) -> AcceptEditPredictionBinding {
2335 let key_context = self.key_context_internal(true, window, cx);
2336 let in_conflict = self.edit_prediction_in_conflict();
2337
2338 let bindings = if accept_partial {
2339 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2340 } else {
2341 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2342 };
2343
2344 // TODO: if the binding contains multiple keystrokes, display all of them, not
2345 // just the first one.
2346 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2347 !in_conflict
2348 || binding
2349 .keystrokes()
2350 .first()
2351 .map_or(false, |keystroke| keystroke.modifiers.modified())
2352 }))
2353 }
2354
2355 pub fn new_file(
2356 workspace: &mut Workspace,
2357 _: &workspace::NewFile,
2358 window: &mut Window,
2359 cx: &mut Context<Workspace>,
2360 ) {
2361 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2362 "Failed to create buffer",
2363 window,
2364 cx,
2365 |e, _, _| match e.error_code() {
2366 ErrorCode::RemoteUpgradeRequired => Some(format!(
2367 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2368 e.error_tag("required").unwrap_or("the latest version")
2369 )),
2370 _ => None,
2371 },
2372 );
2373 }
2374
2375 pub fn new_in_workspace(
2376 workspace: &mut Workspace,
2377 window: &mut Window,
2378 cx: &mut Context<Workspace>,
2379 ) -> Task<Result<Entity<Editor>>> {
2380 let project = workspace.project().clone();
2381 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2382
2383 cx.spawn_in(window, async move |workspace, cx| {
2384 let buffer = create.await?;
2385 workspace.update_in(cx, |workspace, window, cx| {
2386 let editor =
2387 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2388 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2389 editor
2390 })
2391 })
2392 }
2393
2394 fn new_file_vertical(
2395 workspace: &mut Workspace,
2396 _: &workspace::NewFileSplitVertical,
2397 window: &mut Window,
2398 cx: &mut Context<Workspace>,
2399 ) {
2400 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2401 }
2402
2403 fn new_file_horizontal(
2404 workspace: &mut Workspace,
2405 _: &workspace::NewFileSplitHorizontal,
2406 window: &mut Window,
2407 cx: &mut Context<Workspace>,
2408 ) {
2409 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2410 }
2411
2412 fn new_file_in_direction(
2413 workspace: &mut Workspace,
2414 direction: SplitDirection,
2415 window: &mut Window,
2416 cx: &mut Context<Workspace>,
2417 ) {
2418 let project = workspace.project().clone();
2419 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2420
2421 cx.spawn_in(window, async move |workspace, cx| {
2422 let buffer = create.await?;
2423 workspace.update_in(cx, move |workspace, window, cx| {
2424 workspace.split_item(
2425 direction,
2426 Box::new(
2427 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2428 ),
2429 window,
2430 cx,
2431 )
2432 })?;
2433 anyhow::Ok(())
2434 })
2435 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2436 match e.error_code() {
2437 ErrorCode::RemoteUpgradeRequired => Some(format!(
2438 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2439 e.error_tag("required").unwrap_or("the latest version")
2440 )),
2441 _ => None,
2442 }
2443 });
2444 }
2445
2446 pub fn leader_id(&self) -> Option<CollaboratorId> {
2447 self.leader_id
2448 }
2449
2450 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2451 &self.buffer
2452 }
2453
2454 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2455 self.workspace.as_ref()?.0.upgrade()
2456 }
2457
2458 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2459 self.buffer().read(cx).title(cx)
2460 }
2461
2462 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2463 let git_blame_gutter_max_author_length = self
2464 .render_git_blame_gutter(cx)
2465 .then(|| {
2466 if let Some(blame) = self.blame.as_ref() {
2467 let max_author_length =
2468 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2469 Some(max_author_length)
2470 } else {
2471 None
2472 }
2473 })
2474 .flatten();
2475
2476 EditorSnapshot {
2477 mode: self.mode.clone(),
2478 show_gutter: self.show_gutter,
2479 show_line_numbers: self.show_line_numbers,
2480 show_git_diff_gutter: self.show_git_diff_gutter,
2481 show_code_actions: self.show_code_actions,
2482 show_runnables: self.show_runnables,
2483 show_breakpoints: self.show_breakpoints,
2484 git_blame_gutter_max_author_length,
2485 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2486 scroll_anchor: self.scroll_manager.anchor(),
2487 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2488 placeholder_text: self.placeholder_text.clone(),
2489 is_focused: self.focus_handle.is_focused(window),
2490 current_line_highlight: self
2491 .current_line_highlight
2492 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2493 gutter_hovered: self.gutter_hovered,
2494 }
2495 }
2496
2497 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2498 self.buffer.read(cx).language_at(point, cx)
2499 }
2500
2501 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2502 self.buffer.read(cx).read(cx).file_at(point).cloned()
2503 }
2504
2505 pub fn active_excerpt(
2506 &self,
2507 cx: &App,
2508 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2509 self.buffer
2510 .read(cx)
2511 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2512 }
2513
2514 pub fn mode(&self) -> &EditorMode {
2515 &self.mode
2516 }
2517
2518 pub fn set_mode(&mut self, mode: EditorMode) {
2519 self.mode = mode;
2520 }
2521
2522 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2523 self.collaboration_hub.as_deref()
2524 }
2525
2526 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2527 self.collaboration_hub = Some(hub);
2528 }
2529
2530 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2531 self.in_project_search = in_project_search;
2532 }
2533
2534 pub fn set_custom_context_menu(
2535 &mut self,
2536 f: impl 'static
2537 + Fn(
2538 &mut Self,
2539 DisplayPoint,
2540 &mut Window,
2541 &mut Context<Self>,
2542 ) -> Option<Entity<ui::ContextMenu>>,
2543 ) {
2544 self.custom_context_menu = Some(Box::new(f))
2545 }
2546
2547 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2548 self.completion_provider = provider;
2549 }
2550
2551 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2552 self.semantics_provider.clone()
2553 }
2554
2555 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2556 self.semantics_provider = provider;
2557 }
2558
2559 pub fn set_edit_prediction_provider<T>(
2560 &mut self,
2561 provider: Option<Entity<T>>,
2562 window: &mut Window,
2563 cx: &mut Context<Self>,
2564 ) where
2565 T: EditPredictionProvider,
2566 {
2567 self.edit_prediction_provider =
2568 provider.map(|provider| RegisteredInlineCompletionProvider {
2569 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2570 if this.focus_handle.is_focused(window) {
2571 this.update_visible_inline_completion(window, cx);
2572 }
2573 }),
2574 provider: Arc::new(provider),
2575 });
2576 self.update_edit_prediction_settings(cx);
2577 self.refresh_inline_completion(false, false, window, cx);
2578 }
2579
2580 pub fn placeholder_text(&self) -> Option<&str> {
2581 self.placeholder_text.as_deref()
2582 }
2583
2584 pub fn set_placeholder_text(
2585 &mut self,
2586 placeholder_text: impl Into<Arc<str>>,
2587 cx: &mut Context<Self>,
2588 ) {
2589 let placeholder_text = Some(placeholder_text.into());
2590 if self.placeholder_text != placeholder_text {
2591 self.placeholder_text = placeholder_text;
2592 cx.notify();
2593 }
2594 }
2595
2596 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2597 self.cursor_shape = cursor_shape;
2598
2599 // Disrupt blink for immediate user feedback that the cursor shape has changed
2600 self.blink_manager.update(cx, BlinkManager::show_cursor);
2601
2602 cx.notify();
2603 }
2604
2605 pub fn set_current_line_highlight(
2606 &mut self,
2607 current_line_highlight: Option<CurrentLineHighlight>,
2608 ) {
2609 self.current_line_highlight = current_line_highlight;
2610 }
2611
2612 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2613 self.collapse_matches = collapse_matches;
2614 }
2615
2616 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2617 let buffers = self.buffer.read(cx).all_buffers();
2618 let Some(project) = self.project.as_ref() else {
2619 return;
2620 };
2621 project.update(cx, |project, cx| {
2622 for buffer in buffers {
2623 self.registered_buffers
2624 .entry(buffer.read(cx).remote_id())
2625 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2626 }
2627 })
2628 }
2629
2630 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2631 if self.collapse_matches {
2632 return range.start..range.start;
2633 }
2634 range.clone()
2635 }
2636
2637 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2638 if self.display_map.read(cx).clip_at_line_ends != clip {
2639 self.display_map
2640 .update(cx, |map, _| map.clip_at_line_ends = clip);
2641 }
2642 }
2643
2644 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2645 self.input_enabled = input_enabled;
2646 }
2647
2648 pub fn set_inline_completions_hidden_for_vim_mode(
2649 &mut self,
2650 hidden: bool,
2651 window: &mut Window,
2652 cx: &mut Context<Self>,
2653 ) {
2654 if hidden != self.inline_completions_hidden_for_vim_mode {
2655 self.inline_completions_hidden_for_vim_mode = hidden;
2656 if hidden {
2657 self.update_visible_inline_completion(window, cx);
2658 } else {
2659 self.refresh_inline_completion(true, false, window, cx);
2660 }
2661 }
2662 }
2663
2664 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2665 self.menu_inline_completions_policy = value;
2666 }
2667
2668 pub fn set_autoindent(&mut self, autoindent: bool) {
2669 if autoindent {
2670 self.autoindent_mode = Some(AutoindentMode::EachLine);
2671 } else {
2672 self.autoindent_mode = None;
2673 }
2674 }
2675
2676 pub fn read_only(&self, cx: &App) -> bool {
2677 self.read_only || self.buffer.read(cx).read_only()
2678 }
2679
2680 pub fn set_read_only(&mut self, read_only: bool) {
2681 self.read_only = read_only;
2682 }
2683
2684 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2685 self.use_autoclose = autoclose;
2686 }
2687
2688 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2689 self.use_auto_surround = auto_surround;
2690 }
2691
2692 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2693 self.auto_replace_emoji_shortcode = auto_replace;
2694 }
2695
2696 pub fn toggle_edit_predictions(
2697 &mut self,
2698 _: &ToggleEditPrediction,
2699 window: &mut Window,
2700 cx: &mut Context<Self>,
2701 ) {
2702 if self.show_inline_completions_override.is_some() {
2703 self.set_show_edit_predictions(None, window, cx);
2704 } else {
2705 let show_edit_predictions = !self.edit_predictions_enabled();
2706 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2707 }
2708 }
2709
2710 pub fn set_show_edit_predictions(
2711 &mut self,
2712 show_edit_predictions: Option<bool>,
2713 window: &mut Window,
2714 cx: &mut Context<Self>,
2715 ) {
2716 self.show_inline_completions_override = show_edit_predictions;
2717 self.update_edit_prediction_settings(cx);
2718
2719 if let Some(false) = show_edit_predictions {
2720 self.discard_inline_completion(false, cx);
2721 } else {
2722 self.refresh_inline_completion(false, true, window, cx);
2723 }
2724 }
2725
2726 fn inline_completions_disabled_in_scope(
2727 &self,
2728 buffer: &Entity<Buffer>,
2729 buffer_position: language::Anchor,
2730 cx: &App,
2731 ) -> bool {
2732 let snapshot = buffer.read(cx).snapshot();
2733 let settings = snapshot.settings_at(buffer_position, cx);
2734
2735 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2736 return false;
2737 };
2738
2739 scope.override_name().map_or(false, |scope_name| {
2740 settings
2741 .edit_predictions_disabled_in
2742 .iter()
2743 .any(|s| s == scope_name)
2744 })
2745 }
2746
2747 pub fn set_use_modal_editing(&mut self, to: bool) {
2748 self.use_modal_editing = to;
2749 }
2750
2751 pub fn use_modal_editing(&self) -> bool {
2752 self.use_modal_editing
2753 }
2754
2755 fn selections_did_change(
2756 &mut self,
2757 local: bool,
2758 old_cursor_position: &Anchor,
2759 effects: SelectionEffects,
2760 window: &mut Window,
2761 cx: &mut Context<Self>,
2762 ) {
2763 window.invalidate_character_coordinates();
2764
2765 // Copy selections to primary selection buffer
2766 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2767 if local {
2768 let selections = self.selections.all::<usize>(cx);
2769 let buffer_handle = self.buffer.read(cx).read(cx);
2770
2771 let mut text = String::new();
2772 for (index, selection) in selections.iter().enumerate() {
2773 let text_for_selection = buffer_handle
2774 .text_for_range(selection.start..selection.end)
2775 .collect::<String>();
2776
2777 text.push_str(&text_for_selection);
2778 if index != selections.len() - 1 {
2779 text.push('\n');
2780 }
2781 }
2782
2783 if !text.is_empty() {
2784 cx.write_to_primary(ClipboardItem::new_string(text));
2785 }
2786 }
2787
2788 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2789 self.buffer.update(cx, |buffer, cx| {
2790 buffer.set_active_selections(
2791 &self.selections.disjoint_anchors(),
2792 self.selections.line_mode,
2793 self.cursor_shape,
2794 cx,
2795 )
2796 });
2797 }
2798 let display_map = self
2799 .display_map
2800 .update(cx, |display_map, cx| display_map.snapshot(cx));
2801 let buffer = &display_map.buffer_snapshot;
2802 if self.selections.count() == 1 {
2803 self.add_selections_state = None;
2804 }
2805 self.select_next_state = None;
2806 self.select_prev_state = None;
2807 self.select_syntax_node_history.try_clear();
2808 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2809 self.snippet_stack
2810 .invalidate(&self.selections.disjoint_anchors(), buffer);
2811 self.take_rename(false, window, cx);
2812
2813 let newest_selection = self.selections.newest_anchor();
2814 let new_cursor_position = newest_selection.head();
2815 let selection_start = newest_selection.start;
2816
2817 if effects.nav_history {
2818 self.push_to_nav_history(
2819 *old_cursor_position,
2820 Some(new_cursor_position.to_point(buffer)),
2821 false,
2822 cx,
2823 );
2824 }
2825
2826 if local {
2827 if let Some(buffer_id) = new_cursor_position.buffer_id {
2828 if !self.registered_buffers.contains_key(&buffer_id) {
2829 if let Some(project) = self.project.as_ref() {
2830 project.update(cx, |project, cx| {
2831 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2832 return;
2833 };
2834 self.registered_buffers.insert(
2835 buffer_id,
2836 project.register_buffer_with_language_servers(&buffer, cx),
2837 );
2838 })
2839 }
2840 }
2841 }
2842
2843 let mut context_menu = self.context_menu.borrow_mut();
2844 let completion_menu = match context_menu.as_ref() {
2845 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2846 Some(CodeContextMenu::CodeActions(_)) => {
2847 *context_menu = None;
2848 None
2849 }
2850 None => None,
2851 };
2852 let completion_position = completion_menu.map(|menu| menu.initial_position);
2853 drop(context_menu);
2854
2855 if effects.completions {
2856 if let Some(completion_position) = completion_position {
2857 let start_offset = selection_start.to_offset(buffer);
2858 let position_matches = start_offset == completion_position.to_offset(buffer);
2859 let continue_showing = if position_matches {
2860 if self.snippet_stack.is_empty() {
2861 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2862 } else {
2863 // Snippet choices can be shown even when the cursor is in whitespace.
2864 // Dismissing the menu when actions like backspace
2865 true
2866 }
2867 } else {
2868 false
2869 };
2870
2871 if continue_showing {
2872 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2873 } else {
2874 self.hide_context_menu(window, cx);
2875 }
2876 }
2877 }
2878
2879 hide_hover(self, cx);
2880
2881 if old_cursor_position.to_display_point(&display_map).row()
2882 != new_cursor_position.to_display_point(&display_map).row()
2883 {
2884 self.available_code_actions.take();
2885 }
2886 self.refresh_code_actions(window, cx);
2887 self.refresh_document_highlights(cx);
2888 self.refresh_selected_text_highlights(false, window, cx);
2889 refresh_matching_bracket_highlights(self, window, cx);
2890 self.update_visible_inline_completion(window, cx);
2891 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2892 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2893 self.inline_blame_popover.take();
2894 if self.git_blame_inline_enabled {
2895 self.start_inline_blame_timer(window, cx);
2896 }
2897 }
2898
2899 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2900 cx.emit(EditorEvent::SelectionsChanged { local });
2901
2902 let selections = &self.selections.disjoint;
2903 if selections.len() == 1 {
2904 cx.emit(SearchEvent::ActiveMatchChanged)
2905 }
2906 if local {
2907 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2908 let inmemory_selections = selections
2909 .iter()
2910 .map(|s| {
2911 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2912 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2913 })
2914 .collect();
2915 self.update_restoration_data(cx, |data| {
2916 data.selections = inmemory_selections;
2917 });
2918
2919 if WorkspaceSettings::get(None, cx).restore_on_startup
2920 != RestoreOnStartupBehavior::None
2921 {
2922 if let Some(workspace_id) =
2923 self.workspace.as_ref().and_then(|workspace| workspace.1)
2924 {
2925 let snapshot = self.buffer().read(cx).snapshot(cx);
2926 let selections = selections.clone();
2927 let background_executor = cx.background_executor().clone();
2928 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2929 self.serialize_selections = cx.background_spawn(async move {
2930 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2931 let db_selections = selections
2932 .iter()
2933 .map(|selection| {
2934 (
2935 selection.start.to_offset(&snapshot),
2936 selection.end.to_offset(&snapshot),
2937 )
2938 })
2939 .collect();
2940
2941 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2942 .await
2943 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2944 .log_err();
2945 });
2946 }
2947 }
2948 }
2949 }
2950
2951 cx.notify();
2952 }
2953
2954 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2955 use text::ToOffset as _;
2956 use text::ToPoint as _;
2957
2958 if self.mode.is_minimap()
2959 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2960 {
2961 return;
2962 }
2963
2964 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2965 return;
2966 };
2967
2968 let snapshot = singleton.read(cx).snapshot();
2969 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2970 let display_snapshot = display_map.snapshot(cx);
2971
2972 display_snapshot
2973 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2974 .map(|fold| {
2975 fold.range.start.text_anchor.to_point(&snapshot)
2976 ..fold.range.end.text_anchor.to_point(&snapshot)
2977 })
2978 .collect()
2979 });
2980 self.update_restoration_data(cx, |data| {
2981 data.folds = inmemory_folds;
2982 });
2983
2984 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2985 return;
2986 };
2987 let background_executor = cx.background_executor().clone();
2988 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2989 let db_folds = self.display_map.update(cx, |display_map, cx| {
2990 display_map
2991 .snapshot(cx)
2992 .folds_in_range(0..snapshot.len())
2993 .map(|fold| {
2994 (
2995 fold.range.start.text_anchor.to_offset(&snapshot),
2996 fold.range.end.text_anchor.to_offset(&snapshot),
2997 )
2998 })
2999 .collect()
3000 });
3001 self.serialize_folds = cx.background_spawn(async move {
3002 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3003 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3004 .await
3005 .with_context(|| {
3006 format!(
3007 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3008 )
3009 })
3010 .log_err();
3011 });
3012 }
3013
3014 pub fn sync_selections(
3015 &mut self,
3016 other: Entity<Editor>,
3017 cx: &mut Context<Self>,
3018 ) -> gpui::Subscription {
3019 let other_selections = other.read(cx).selections.disjoint.to_vec();
3020 self.selections.change_with(cx, |selections| {
3021 selections.select_anchors(other_selections);
3022 });
3023
3024 let other_subscription =
3025 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3026 EditorEvent::SelectionsChanged { local: true } => {
3027 let other_selections = other.read(cx).selections.disjoint.to_vec();
3028 if other_selections.is_empty() {
3029 return;
3030 }
3031 this.selections.change_with(cx, |selections| {
3032 selections.select_anchors(other_selections);
3033 });
3034 }
3035 _ => {}
3036 });
3037
3038 let this_subscription =
3039 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3040 EditorEvent::SelectionsChanged { local: true } => {
3041 let these_selections = this.selections.disjoint.to_vec();
3042 if these_selections.is_empty() {
3043 return;
3044 }
3045 other.update(cx, |other_editor, cx| {
3046 other_editor.selections.change_with(cx, |selections| {
3047 selections.select_anchors(these_selections);
3048 })
3049 });
3050 }
3051 _ => {}
3052 });
3053
3054 Subscription::join(other_subscription, this_subscription)
3055 }
3056
3057 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3058 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3059 /// effects of selection change occur at the end of the transaction.
3060 pub fn change_selections<R>(
3061 &mut self,
3062 effects: impl Into<SelectionEffects>,
3063 window: &mut Window,
3064 cx: &mut Context<Self>,
3065 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3066 ) -> R {
3067 let effects = effects.into();
3068 if let Some(state) = &mut self.deferred_selection_effects_state {
3069 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3070 state.effects.completions = effects.completions;
3071 state.effects.nav_history |= effects.nav_history;
3072 let (changed, result) = self.selections.change_with(cx, change);
3073 state.changed |= changed;
3074 return result;
3075 }
3076 let mut state = DeferredSelectionEffectsState {
3077 changed: false,
3078 effects,
3079 old_cursor_position: self.selections.newest_anchor().head(),
3080 history_entry: SelectionHistoryEntry {
3081 selections: self.selections.disjoint_anchors(),
3082 select_next_state: self.select_next_state.clone(),
3083 select_prev_state: self.select_prev_state.clone(),
3084 add_selections_state: self.add_selections_state.clone(),
3085 },
3086 };
3087 let (changed, result) = self.selections.change_with(cx, change);
3088 state.changed = state.changed || changed;
3089 if self.defer_selection_effects {
3090 self.deferred_selection_effects_state = Some(state);
3091 } else {
3092 self.apply_selection_effects(state, window, cx);
3093 }
3094 result
3095 }
3096
3097 /// Defers the effects of selection change, so that the effects of multiple calls to
3098 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3099 /// to selection history and the state of popovers based on selection position aren't
3100 /// erroneously updated.
3101 pub fn with_selection_effects_deferred<R>(
3102 &mut self,
3103 window: &mut Window,
3104 cx: &mut Context<Self>,
3105 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3106 ) -> R {
3107 let already_deferred = self.defer_selection_effects;
3108 self.defer_selection_effects = true;
3109 let result = update(self, window, cx);
3110 if !already_deferred {
3111 self.defer_selection_effects = false;
3112 if let Some(state) = self.deferred_selection_effects_state.take() {
3113 self.apply_selection_effects(state, window, cx);
3114 }
3115 }
3116 result
3117 }
3118
3119 fn apply_selection_effects(
3120 &mut self,
3121 state: DeferredSelectionEffectsState,
3122 window: &mut Window,
3123 cx: &mut Context<Self>,
3124 ) {
3125 if state.changed {
3126 self.selection_history.push(state.history_entry);
3127
3128 if let Some(autoscroll) = state.effects.scroll {
3129 self.request_autoscroll(autoscroll, cx);
3130 }
3131
3132 let old_cursor_position = &state.old_cursor_position;
3133
3134 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3135
3136 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3137 self.show_signature_help(&ShowSignatureHelp, window, cx);
3138 }
3139 }
3140 }
3141
3142 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3143 where
3144 I: IntoIterator<Item = (Range<S>, T)>,
3145 S: ToOffset,
3146 T: Into<Arc<str>>,
3147 {
3148 if self.read_only(cx) {
3149 return;
3150 }
3151
3152 self.buffer
3153 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3154 }
3155
3156 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3157 where
3158 I: IntoIterator<Item = (Range<S>, T)>,
3159 S: ToOffset,
3160 T: Into<Arc<str>>,
3161 {
3162 if self.read_only(cx) {
3163 return;
3164 }
3165
3166 self.buffer.update(cx, |buffer, cx| {
3167 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3168 });
3169 }
3170
3171 pub fn edit_with_block_indent<I, S, T>(
3172 &mut self,
3173 edits: I,
3174 original_indent_columns: Vec<Option<u32>>,
3175 cx: &mut Context<Self>,
3176 ) where
3177 I: IntoIterator<Item = (Range<S>, T)>,
3178 S: ToOffset,
3179 T: Into<Arc<str>>,
3180 {
3181 if self.read_only(cx) {
3182 return;
3183 }
3184
3185 self.buffer.update(cx, |buffer, cx| {
3186 buffer.edit(
3187 edits,
3188 Some(AutoindentMode::Block {
3189 original_indent_columns,
3190 }),
3191 cx,
3192 )
3193 });
3194 }
3195
3196 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3197 self.hide_context_menu(window, cx);
3198
3199 match phase {
3200 SelectPhase::Begin {
3201 position,
3202 add,
3203 click_count,
3204 } => self.begin_selection(position, add, click_count, window, cx),
3205 SelectPhase::BeginColumnar {
3206 position,
3207 goal_column,
3208 reset,
3209 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3210 SelectPhase::Extend {
3211 position,
3212 click_count,
3213 } => self.extend_selection(position, click_count, window, cx),
3214 SelectPhase::Update {
3215 position,
3216 goal_column,
3217 scroll_delta,
3218 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3219 SelectPhase::End => self.end_selection(window, cx),
3220 }
3221 }
3222
3223 fn extend_selection(
3224 &mut self,
3225 position: DisplayPoint,
3226 click_count: usize,
3227 window: &mut Window,
3228 cx: &mut Context<Self>,
3229 ) {
3230 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3231 let tail = self.selections.newest::<usize>(cx).tail();
3232 self.begin_selection(position, false, click_count, window, cx);
3233
3234 let position = position.to_offset(&display_map, Bias::Left);
3235 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3236
3237 let mut pending_selection = self
3238 .selections
3239 .pending_anchor()
3240 .expect("extend_selection not called with pending selection");
3241 if position >= tail {
3242 pending_selection.start = tail_anchor;
3243 } else {
3244 pending_selection.end = tail_anchor;
3245 pending_selection.reversed = true;
3246 }
3247
3248 let mut pending_mode = self.selections.pending_mode().unwrap();
3249 match &mut pending_mode {
3250 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3251 _ => {}
3252 }
3253
3254 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3255 SelectionEffects::scroll(Autoscroll::fit())
3256 } else {
3257 SelectionEffects::no_scroll()
3258 };
3259
3260 self.change_selections(effects, window, cx, |s| {
3261 s.set_pending(pending_selection, pending_mode)
3262 });
3263 }
3264
3265 fn begin_selection(
3266 &mut self,
3267 position: DisplayPoint,
3268 add: bool,
3269 click_count: usize,
3270 window: &mut Window,
3271 cx: &mut Context<Self>,
3272 ) {
3273 if !self.focus_handle.is_focused(window) {
3274 self.last_focused_descendant = None;
3275 window.focus(&self.focus_handle);
3276 }
3277
3278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3279 let buffer = &display_map.buffer_snapshot;
3280 let position = display_map.clip_point(position, Bias::Left);
3281
3282 let start;
3283 let end;
3284 let mode;
3285 let mut auto_scroll;
3286 match click_count {
3287 1 => {
3288 start = buffer.anchor_before(position.to_point(&display_map));
3289 end = start;
3290 mode = SelectMode::Character;
3291 auto_scroll = true;
3292 }
3293 2 => {
3294 let range = movement::surrounding_word(&display_map, position);
3295 start = buffer.anchor_before(range.start.to_point(&display_map));
3296 end = buffer.anchor_before(range.end.to_point(&display_map));
3297 mode = SelectMode::Word(start..end);
3298 auto_scroll = true;
3299 }
3300 3 => {
3301 let position = display_map
3302 .clip_point(position, Bias::Left)
3303 .to_point(&display_map);
3304 let line_start = display_map.prev_line_boundary(position).0;
3305 let next_line_start = buffer.clip_point(
3306 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3307 Bias::Left,
3308 );
3309 start = buffer.anchor_before(line_start);
3310 end = buffer.anchor_before(next_line_start);
3311 mode = SelectMode::Line(start..end);
3312 auto_scroll = true;
3313 }
3314 _ => {
3315 start = buffer.anchor_before(0);
3316 end = buffer.anchor_before(buffer.len());
3317 mode = SelectMode::All;
3318 auto_scroll = false;
3319 }
3320 }
3321 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3322
3323 let point_to_delete: Option<usize> = {
3324 let selected_points: Vec<Selection<Point>> =
3325 self.selections.disjoint_in_range(start..end, cx);
3326
3327 if !add || click_count > 1 {
3328 None
3329 } else if !selected_points.is_empty() {
3330 Some(selected_points[0].id)
3331 } else {
3332 let clicked_point_already_selected =
3333 self.selections.disjoint.iter().find(|selection| {
3334 selection.start.to_point(buffer) == start.to_point(buffer)
3335 || selection.end.to_point(buffer) == end.to_point(buffer)
3336 });
3337
3338 clicked_point_already_selected.map(|selection| selection.id)
3339 }
3340 };
3341
3342 let selections_count = self.selections.count();
3343
3344 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3345 if let Some(point_to_delete) = point_to_delete {
3346 s.delete(point_to_delete);
3347
3348 if selections_count == 1 {
3349 s.set_pending_anchor_range(start..end, mode);
3350 }
3351 } else {
3352 if !add {
3353 s.clear_disjoint();
3354 }
3355
3356 s.set_pending_anchor_range(start..end, mode);
3357 }
3358 });
3359 }
3360
3361 fn begin_columnar_selection(
3362 &mut self,
3363 position: DisplayPoint,
3364 goal_column: u32,
3365 reset: bool,
3366 window: &mut Window,
3367 cx: &mut Context<Self>,
3368 ) {
3369 if !self.focus_handle.is_focused(window) {
3370 self.last_focused_descendant = None;
3371 window.focus(&self.focus_handle);
3372 }
3373
3374 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3375
3376 if reset {
3377 let pointer_position = display_map
3378 .buffer_snapshot
3379 .anchor_before(position.to_point(&display_map));
3380
3381 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3382 s.clear_disjoint();
3383 s.set_pending_anchor_range(
3384 pointer_position..pointer_position,
3385 SelectMode::Character,
3386 );
3387 });
3388 if position.column() != goal_column {
3389 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3390 } else {
3391 self.columnar_display_point = None;
3392 }
3393 }
3394
3395 let tail = self.selections.newest::<Point>(cx).tail();
3396 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3397
3398 if !reset {
3399 self.columnar_display_point = None;
3400 self.select_columns(
3401 tail.to_display_point(&display_map),
3402 position,
3403 goal_column,
3404 &display_map,
3405 window,
3406 cx,
3407 );
3408 }
3409 }
3410
3411 fn update_selection(
3412 &mut self,
3413 position: DisplayPoint,
3414 goal_column: u32,
3415 scroll_delta: gpui::Point<f32>,
3416 window: &mut Window,
3417 cx: &mut Context<Self>,
3418 ) {
3419 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3420
3421 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3422 let tail = self
3423 .columnar_display_point
3424 .unwrap_or_else(|| tail.to_display_point(&display_map));
3425 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3426 } else if let Some(mut pending) = self.selections.pending_anchor() {
3427 let buffer = self.buffer.read(cx).snapshot(cx);
3428 let head;
3429 let tail;
3430 let mode = self.selections.pending_mode().unwrap();
3431 match &mode {
3432 SelectMode::Character => {
3433 head = position.to_point(&display_map);
3434 tail = pending.tail().to_point(&buffer);
3435 }
3436 SelectMode::Word(original_range) => {
3437 let original_display_range = original_range.start.to_display_point(&display_map)
3438 ..original_range.end.to_display_point(&display_map);
3439 let original_buffer_range = original_display_range.start.to_point(&display_map)
3440 ..original_display_range.end.to_point(&display_map);
3441 if movement::is_inside_word(&display_map, position)
3442 || original_display_range.contains(&position)
3443 {
3444 let word_range = movement::surrounding_word(&display_map, position);
3445 if word_range.start < original_display_range.start {
3446 head = word_range.start.to_point(&display_map);
3447 } else {
3448 head = word_range.end.to_point(&display_map);
3449 }
3450 } else {
3451 head = position.to_point(&display_map);
3452 }
3453
3454 if head <= original_buffer_range.start {
3455 tail = original_buffer_range.end;
3456 } else {
3457 tail = original_buffer_range.start;
3458 }
3459 }
3460 SelectMode::Line(original_range) => {
3461 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3462
3463 let position = display_map
3464 .clip_point(position, Bias::Left)
3465 .to_point(&display_map);
3466 let line_start = display_map.prev_line_boundary(position).0;
3467 let next_line_start = buffer.clip_point(
3468 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3469 Bias::Left,
3470 );
3471
3472 if line_start < original_range.start {
3473 head = line_start
3474 } else {
3475 head = next_line_start
3476 }
3477
3478 if head <= original_range.start {
3479 tail = original_range.end;
3480 } else {
3481 tail = original_range.start;
3482 }
3483 }
3484 SelectMode::All => {
3485 return;
3486 }
3487 };
3488
3489 if head < tail {
3490 pending.start = buffer.anchor_before(head);
3491 pending.end = buffer.anchor_before(tail);
3492 pending.reversed = true;
3493 } else {
3494 pending.start = buffer.anchor_before(tail);
3495 pending.end = buffer.anchor_before(head);
3496 pending.reversed = false;
3497 }
3498
3499 self.change_selections(None, window, cx, |s| {
3500 s.set_pending(pending, mode);
3501 });
3502 } else {
3503 log::error!("update_selection dispatched with no pending selection");
3504 return;
3505 }
3506
3507 self.apply_scroll_delta(scroll_delta, window, cx);
3508 cx.notify();
3509 }
3510
3511 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3512 self.columnar_selection_tail.take();
3513 if self.selections.pending_anchor().is_some() {
3514 let selections = self.selections.all::<usize>(cx);
3515 self.change_selections(None, window, cx, |s| {
3516 s.select(selections);
3517 s.clear_pending();
3518 });
3519 }
3520 }
3521
3522 fn select_columns(
3523 &mut self,
3524 tail: DisplayPoint,
3525 head: DisplayPoint,
3526 goal_column: u32,
3527 display_map: &DisplaySnapshot,
3528 window: &mut Window,
3529 cx: &mut Context<Self>,
3530 ) {
3531 let start_row = cmp::min(tail.row(), head.row());
3532 let end_row = cmp::max(tail.row(), head.row());
3533 let start_column = cmp::min(tail.column(), goal_column);
3534 let end_column = cmp::max(tail.column(), goal_column);
3535 let reversed = start_column < tail.column();
3536
3537 let selection_ranges = (start_row.0..=end_row.0)
3538 .map(DisplayRow)
3539 .filter_map(|row| {
3540 if !display_map.is_block_line(row) {
3541 let start = display_map
3542 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3543 .to_point(display_map);
3544 let end = display_map
3545 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3546 .to_point(display_map);
3547 if reversed {
3548 Some(end..start)
3549 } else {
3550 Some(start..end)
3551 }
3552 } else {
3553 None
3554 }
3555 })
3556 .collect::<Vec<_>>();
3557
3558 let mut non_empty_ranges = selection_ranges
3559 .iter()
3560 .filter(|selection_range| selection_range.start != selection_range.end)
3561 .peekable();
3562
3563 let ranges = if non_empty_ranges.peek().is_some() {
3564 non_empty_ranges.cloned().collect()
3565 } else {
3566 selection_ranges
3567 };
3568
3569 self.change_selections(None, window, cx, |s| {
3570 s.select_ranges(ranges);
3571 });
3572 cx.notify();
3573 }
3574
3575 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3576 self.selections
3577 .all_adjusted(cx)
3578 .iter()
3579 .any(|selection| !selection.is_empty())
3580 }
3581
3582 pub fn has_pending_nonempty_selection(&self) -> bool {
3583 let pending_nonempty_selection = match self.selections.pending_anchor() {
3584 Some(Selection { start, end, .. }) => start != end,
3585 None => false,
3586 };
3587
3588 pending_nonempty_selection
3589 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3590 }
3591
3592 pub fn has_pending_selection(&self) -> bool {
3593 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3594 }
3595
3596 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3597 self.selection_mark_mode = false;
3598 self.selection_drag_state = SelectionDragState::None;
3599
3600 if self.clear_expanded_diff_hunks(cx) {
3601 cx.notify();
3602 return;
3603 }
3604 if self.dismiss_menus_and_popups(true, window, cx) {
3605 return;
3606 }
3607
3608 if self.mode.is_full()
3609 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3610 {
3611 return;
3612 }
3613
3614 cx.propagate();
3615 }
3616
3617 pub fn dismiss_menus_and_popups(
3618 &mut self,
3619 is_user_requested: bool,
3620 window: &mut Window,
3621 cx: &mut Context<Self>,
3622 ) -> bool {
3623 if self.take_rename(false, window, cx).is_some() {
3624 return true;
3625 }
3626
3627 if hide_hover(self, cx) {
3628 return true;
3629 }
3630
3631 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3632 return true;
3633 }
3634
3635 if self.hide_context_menu(window, cx).is_some() {
3636 return true;
3637 }
3638
3639 if self.mouse_context_menu.take().is_some() {
3640 return true;
3641 }
3642
3643 if is_user_requested && self.discard_inline_completion(true, cx) {
3644 return true;
3645 }
3646
3647 if self.snippet_stack.pop().is_some() {
3648 return true;
3649 }
3650
3651 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3652 self.dismiss_diagnostics(cx);
3653 return true;
3654 }
3655
3656 false
3657 }
3658
3659 fn linked_editing_ranges_for(
3660 &self,
3661 selection: Range<text::Anchor>,
3662 cx: &App,
3663 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3664 if self.linked_edit_ranges.is_empty() {
3665 return None;
3666 }
3667 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3668 selection.end.buffer_id.and_then(|end_buffer_id| {
3669 if selection.start.buffer_id != Some(end_buffer_id) {
3670 return None;
3671 }
3672 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3673 let snapshot = buffer.read(cx).snapshot();
3674 self.linked_edit_ranges
3675 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3676 .map(|ranges| (ranges, snapshot, buffer))
3677 })?;
3678 use text::ToOffset as TO;
3679 // find offset from the start of current range to current cursor position
3680 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3681
3682 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3683 let start_difference = start_offset - start_byte_offset;
3684 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3685 let end_difference = end_offset - start_byte_offset;
3686 // Current range has associated linked ranges.
3687 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3688 for range in linked_ranges.iter() {
3689 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3690 let end_offset = start_offset + end_difference;
3691 let start_offset = start_offset + start_difference;
3692 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3693 continue;
3694 }
3695 if self.selections.disjoint_anchor_ranges().any(|s| {
3696 if s.start.buffer_id != selection.start.buffer_id
3697 || s.end.buffer_id != selection.end.buffer_id
3698 {
3699 return false;
3700 }
3701 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3702 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3703 }) {
3704 continue;
3705 }
3706 let start = buffer_snapshot.anchor_after(start_offset);
3707 let end = buffer_snapshot.anchor_after(end_offset);
3708 linked_edits
3709 .entry(buffer.clone())
3710 .or_default()
3711 .push(start..end);
3712 }
3713 Some(linked_edits)
3714 }
3715
3716 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3717 let text: Arc<str> = text.into();
3718
3719 if self.read_only(cx) {
3720 return;
3721 }
3722
3723 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3724
3725 let selections = self.selections.all_adjusted(cx);
3726 let mut bracket_inserted = false;
3727 let mut edits = Vec::new();
3728 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3729 let mut new_selections = Vec::with_capacity(selections.len());
3730 let mut new_autoclose_regions = Vec::new();
3731 let snapshot = self.buffer.read(cx).read(cx);
3732 let mut clear_linked_edit_ranges = false;
3733
3734 for (selection, autoclose_region) in
3735 self.selections_with_autoclose_regions(selections, &snapshot)
3736 {
3737 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3738 // Determine if the inserted text matches the opening or closing
3739 // bracket of any of this language's bracket pairs.
3740 let mut bracket_pair = None;
3741 let mut is_bracket_pair_start = false;
3742 let mut is_bracket_pair_end = false;
3743 if !text.is_empty() {
3744 let mut bracket_pair_matching_end = None;
3745 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3746 // and they are removing the character that triggered IME popup.
3747 for (pair, enabled) in scope.brackets() {
3748 if !pair.close && !pair.surround {
3749 continue;
3750 }
3751
3752 if enabled && pair.start.ends_with(text.as_ref()) {
3753 let prefix_len = pair.start.len() - text.len();
3754 let preceding_text_matches_prefix = prefix_len == 0
3755 || (selection.start.column >= (prefix_len as u32)
3756 && snapshot.contains_str_at(
3757 Point::new(
3758 selection.start.row,
3759 selection.start.column - (prefix_len as u32),
3760 ),
3761 &pair.start[..prefix_len],
3762 ));
3763 if preceding_text_matches_prefix {
3764 bracket_pair = Some(pair.clone());
3765 is_bracket_pair_start = true;
3766 break;
3767 }
3768 }
3769 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3770 {
3771 // take first bracket pair matching end, but don't break in case a later bracket
3772 // pair matches start
3773 bracket_pair_matching_end = Some(pair.clone());
3774 }
3775 }
3776 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3777 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3778 is_bracket_pair_end = true;
3779 }
3780 }
3781
3782 if let Some(bracket_pair) = bracket_pair {
3783 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3784 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3785 let auto_surround =
3786 self.use_auto_surround && snapshot_settings.use_auto_surround;
3787 if selection.is_empty() {
3788 if is_bracket_pair_start {
3789 // If the inserted text is a suffix of an opening bracket and the
3790 // selection is preceded by the rest of the opening bracket, then
3791 // insert the closing bracket.
3792 let following_text_allows_autoclose = snapshot
3793 .chars_at(selection.start)
3794 .next()
3795 .map_or(true, |c| scope.should_autoclose_before(c));
3796
3797 let preceding_text_allows_autoclose = selection.start.column == 0
3798 || snapshot.reversed_chars_at(selection.start).next().map_or(
3799 true,
3800 |c| {
3801 bracket_pair.start != bracket_pair.end
3802 || !snapshot
3803 .char_classifier_at(selection.start)
3804 .is_word(c)
3805 },
3806 );
3807
3808 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3809 && bracket_pair.start.len() == 1
3810 {
3811 let target = bracket_pair.start.chars().next().unwrap();
3812 let current_line_count = snapshot
3813 .reversed_chars_at(selection.start)
3814 .take_while(|&c| c != '\n')
3815 .filter(|&c| c == target)
3816 .count();
3817 current_line_count % 2 == 1
3818 } else {
3819 false
3820 };
3821
3822 if autoclose
3823 && bracket_pair.close
3824 && following_text_allows_autoclose
3825 && preceding_text_allows_autoclose
3826 && !is_closing_quote
3827 {
3828 let anchor = snapshot.anchor_before(selection.end);
3829 new_selections.push((selection.map(|_| anchor), text.len()));
3830 new_autoclose_regions.push((
3831 anchor,
3832 text.len(),
3833 selection.id,
3834 bracket_pair.clone(),
3835 ));
3836 edits.push((
3837 selection.range(),
3838 format!("{}{}", text, bracket_pair.end).into(),
3839 ));
3840 bracket_inserted = true;
3841 continue;
3842 }
3843 }
3844
3845 if let Some(region) = autoclose_region {
3846 // If the selection is followed by an auto-inserted closing bracket,
3847 // then don't insert that closing bracket again; just move the selection
3848 // past the closing bracket.
3849 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3850 && text.as_ref() == region.pair.end.as_str();
3851 if should_skip {
3852 let anchor = snapshot.anchor_after(selection.end);
3853 new_selections
3854 .push((selection.map(|_| anchor), region.pair.end.len()));
3855 continue;
3856 }
3857 }
3858
3859 let always_treat_brackets_as_autoclosed = snapshot
3860 .language_settings_at(selection.start, cx)
3861 .always_treat_brackets_as_autoclosed;
3862 if always_treat_brackets_as_autoclosed
3863 && is_bracket_pair_end
3864 && snapshot.contains_str_at(selection.end, text.as_ref())
3865 {
3866 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3867 // and the inserted text is a closing bracket and the selection is followed
3868 // by the closing bracket then move the selection past the closing bracket.
3869 let anchor = snapshot.anchor_after(selection.end);
3870 new_selections.push((selection.map(|_| anchor), text.len()));
3871 continue;
3872 }
3873 }
3874 // If an opening bracket is 1 character long and is typed while
3875 // text is selected, then surround that text with the bracket pair.
3876 else if auto_surround
3877 && bracket_pair.surround
3878 && is_bracket_pair_start
3879 && bracket_pair.start.chars().count() == 1
3880 {
3881 edits.push((selection.start..selection.start, text.clone()));
3882 edits.push((
3883 selection.end..selection.end,
3884 bracket_pair.end.as_str().into(),
3885 ));
3886 bracket_inserted = true;
3887 new_selections.push((
3888 Selection {
3889 id: selection.id,
3890 start: snapshot.anchor_after(selection.start),
3891 end: snapshot.anchor_before(selection.end),
3892 reversed: selection.reversed,
3893 goal: selection.goal,
3894 },
3895 0,
3896 ));
3897 continue;
3898 }
3899 }
3900 }
3901
3902 if self.auto_replace_emoji_shortcode
3903 && selection.is_empty()
3904 && text.as_ref().ends_with(':')
3905 {
3906 if let Some(possible_emoji_short_code) =
3907 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3908 {
3909 if !possible_emoji_short_code.is_empty() {
3910 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3911 let emoji_shortcode_start = Point::new(
3912 selection.start.row,
3913 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3914 );
3915
3916 // Remove shortcode from buffer
3917 edits.push((
3918 emoji_shortcode_start..selection.start,
3919 "".to_string().into(),
3920 ));
3921 new_selections.push((
3922 Selection {
3923 id: selection.id,
3924 start: snapshot.anchor_after(emoji_shortcode_start),
3925 end: snapshot.anchor_before(selection.start),
3926 reversed: selection.reversed,
3927 goal: selection.goal,
3928 },
3929 0,
3930 ));
3931
3932 // Insert emoji
3933 let selection_start_anchor = snapshot.anchor_after(selection.start);
3934 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3935 edits.push((selection.start..selection.end, emoji.to_string().into()));
3936
3937 continue;
3938 }
3939 }
3940 }
3941 }
3942
3943 // If not handling any auto-close operation, then just replace the selected
3944 // text with the given input and move the selection to the end of the
3945 // newly inserted text.
3946 let anchor = snapshot.anchor_after(selection.end);
3947 if !self.linked_edit_ranges.is_empty() {
3948 let start_anchor = snapshot.anchor_before(selection.start);
3949
3950 let is_word_char = text.chars().next().map_or(true, |char| {
3951 let classifier = snapshot
3952 .char_classifier_at(start_anchor.to_offset(&snapshot))
3953 .ignore_punctuation(true);
3954 classifier.is_word(char)
3955 });
3956
3957 if is_word_char {
3958 if let Some(ranges) = self
3959 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3960 {
3961 for (buffer, edits) in ranges {
3962 linked_edits
3963 .entry(buffer.clone())
3964 .or_default()
3965 .extend(edits.into_iter().map(|range| (range, text.clone())));
3966 }
3967 }
3968 } else {
3969 clear_linked_edit_ranges = true;
3970 }
3971 }
3972
3973 new_selections.push((selection.map(|_| anchor), 0));
3974 edits.push((selection.start..selection.end, text.clone()));
3975 }
3976
3977 drop(snapshot);
3978
3979 self.transact(window, cx, |this, window, cx| {
3980 if clear_linked_edit_ranges {
3981 this.linked_edit_ranges.clear();
3982 }
3983 let initial_buffer_versions =
3984 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3985
3986 this.buffer.update(cx, |buffer, cx| {
3987 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3988 });
3989 for (buffer, edits) in linked_edits {
3990 buffer.update(cx, |buffer, cx| {
3991 let snapshot = buffer.snapshot();
3992 let edits = edits
3993 .into_iter()
3994 .map(|(range, text)| {
3995 use text::ToPoint as TP;
3996 let end_point = TP::to_point(&range.end, &snapshot);
3997 let start_point = TP::to_point(&range.start, &snapshot);
3998 (start_point..end_point, text)
3999 })
4000 .sorted_by_key(|(range, _)| range.start);
4001 buffer.edit(edits, None, cx);
4002 })
4003 }
4004 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4005 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4006 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4007 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4008 .zip(new_selection_deltas)
4009 .map(|(selection, delta)| Selection {
4010 id: selection.id,
4011 start: selection.start + delta,
4012 end: selection.end + delta,
4013 reversed: selection.reversed,
4014 goal: SelectionGoal::None,
4015 })
4016 .collect::<Vec<_>>();
4017
4018 let mut i = 0;
4019 for (position, delta, selection_id, pair) in new_autoclose_regions {
4020 let position = position.to_offset(&map.buffer_snapshot) + delta;
4021 let start = map.buffer_snapshot.anchor_before(position);
4022 let end = map.buffer_snapshot.anchor_after(position);
4023 while let Some(existing_state) = this.autoclose_regions.get(i) {
4024 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4025 Ordering::Less => i += 1,
4026 Ordering::Greater => break,
4027 Ordering::Equal => {
4028 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4029 Ordering::Less => i += 1,
4030 Ordering::Equal => break,
4031 Ordering::Greater => break,
4032 }
4033 }
4034 }
4035 }
4036 this.autoclose_regions.insert(
4037 i,
4038 AutocloseRegion {
4039 selection_id,
4040 range: start..end,
4041 pair,
4042 },
4043 );
4044 }
4045
4046 let had_active_inline_completion = this.has_active_inline_completion();
4047 this.change_selections(
4048 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4049 window,
4050 cx,
4051 |s| s.select(new_selections),
4052 );
4053
4054 if !bracket_inserted {
4055 if let Some(on_type_format_task) =
4056 this.trigger_on_type_formatting(text.to_string(), window, cx)
4057 {
4058 on_type_format_task.detach_and_log_err(cx);
4059 }
4060 }
4061
4062 let editor_settings = EditorSettings::get_global(cx);
4063 if bracket_inserted
4064 && (editor_settings.auto_signature_help
4065 || editor_settings.show_signature_help_after_edits)
4066 {
4067 this.show_signature_help(&ShowSignatureHelp, window, cx);
4068 }
4069
4070 let trigger_in_words =
4071 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4072 if this.hard_wrap.is_some() {
4073 let latest: Range<Point> = this.selections.newest(cx).range();
4074 if latest.is_empty()
4075 && this
4076 .buffer()
4077 .read(cx)
4078 .snapshot(cx)
4079 .line_len(MultiBufferRow(latest.start.row))
4080 == latest.start.column
4081 {
4082 this.rewrap_impl(
4083 RewrapOptions {
4084 override_language_settings: true,
4085 preserve_existing_whitespace: true,
4086 },
4087 cx,
4088 )
4089 }
4090 }
4091 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4092 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4093 this.refresh_inline_completion(true, false, window, cx);
4094 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4095 });
4096 }
4097
4098 fn find_possible_emoji_shortcode_at_position(
4099 snapshot: &MultiBufferSnapshot,
4100 position: Point,
4101 ) -> Option<String> {
4102 let mut chars = Vec::new();
4103 let mut found_colon = false;
4104 for char in snapshot.reversed_chars_at(position).take(100) {
4105 // Found a possible emoji shortcode in the middle of the buffer
4106 if found_colon {
4107 if char.is_whitespace() {
4108 chars.reverse();
4109 return Some(chars.iter().collect());
4110 }
4111 // If the previous character is not a whitespace, we are in the middle of a word
4112 // and we only want to complete the shortcode if the word is made up of other emojis
4113 let mut containing_word = String::new();
4114 for ch in snapshot
4115 .reversed_chars_at(position)
4116 .skip(chars.len() + 1)
4117 .take(100)
4118 {
4119 if ch.is_whitespace() {
4120 break;
4121 }
4122 containing_word.push(ch);
4123 }
4124 let containing_word = containing_word.chars().rev().collect::<String>();
4125 if util::word_consists_of_emojis(containing_word.as_str()) {
4126 chars.reverse();
4127 return Some(chars.iter().collect());
4128 }
4129 }
4130
4131 if char.is_whitespace() || !char.is_ascii() {
4132 return None;
4133 }
4134 if char == ':' {
4135 found_colon = true;
4136 } else {
4137 chars.push(char);
4138 }
4139 }
4140 // Found a possible emoji shortcode at the beginning of the buffer
4141 chars.reverse();
4142 Some(chars.iter().collect())
4143 }
4144
4145 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4146 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4147 self.transact(window, cx, |this, window, cx| {
4148 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4149 let selections = this.selections.all::<usize>(cx);
4150 let multi_buffer = this.buffer.read(cx);
4151 let buffer = multi_buffer.snapshot(cx);
4152 selections
4153 .iter()
4154 .map(|selection| {
4155 let start_point = selection.start.to_point(&buffer);
4156 let mut existing_indent =
4157 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4158 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4159 let start = selection.start;
4160 let end = selection.end;
4161 let selection_is_empty = start == end;
4162 let language_scope = buffer.language_scope_at(start);
4163 let (
4164 comment_delimiter,
4165 doc_delimiter,
4166 insert_extra_newline,
4167 indent_on_newline,
4168 indent_on_extra_newline,
4169 ) = if let Some(language) = &language_scope {
4170 let mut insert_extra_newline =
4171 insert_extra_newline_brackets(&buffer, start..end, language)
4172 || insert_extra_newline_tree_sitter(&buffer, start..end);
4173
4174 // Comment extension on newline is allowed only for cursor selections
4175 let comment_delimiter = maybe!({
4176 if !selection_is_empty {
4177 return None;
4178 }
4179
4180 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4181 return None;
4182 }
4183
4184 let delimiters = language.line_comment_prefixes();
4185 let max_len_of_delimiter =
4186 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4187 let (snapshot, range) =
4188 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4189
4190 let num_of_whitespaces = snapshot
4191 .chars_for_range(range.clone())
4192 .take_while(|c| c.is_whitespace())
4193 .count();
4194 let comment_candidate = snapshot
4195 .chars_for_range(range)
4196 .skip(num_of_whitespaces)
4197 .take(max_len_of_delimiter)
4198 .collect::<String>();
4199 let (delimiter, trimmed_len) = delimiters
4200 .iter()
4201 .filter_map(|delimiter| {
4202 let prefix = delimiter.trim_end();
4203 if comment_candidate.starts_with(prefix) {
4204 Some((delimiter, prefix.len()))
4205 } else {
4206 None
4207 }
4208 })
4209 .max_by_key(|(_, len)| *len)?;
4210
4211 let cursor_is_placed_after_comment_marker =
4212 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4213 if cursor_is_placed_after_comment_marker {
4214 Some(delimiter.clone())
4215 } else {
4216 None
4217 }
4218 });
4219
4220 let mut indent_on_newline = IndentSize::spaces(0);
4221 let mut indent_on_extra_newline = IndentSize::spaces(0);
4222
4223 let doc_delimiter = maybe!({
4224 if !selection_is_empty {
4225 return None;
4226 }
4227
4228 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4229 return None;
4230 }
4231
4232 let DocumentationConfig {
4233 start: start_tag,
4234 end: end_tag,
4235 prefix: delimiter,
4236 tab_size: len,
4237 } = language.documentation()?;
4238
4239 let is_within_block_comment = buffer
4240 .language_scope_at(start_point)
4241 .is_some_and(|scope| scope.override_name() == Some("comment"));
4242 if !is_within_block_comment {
4243 return None;
4244 }
4245
4246 let (snapshot, range) =
4247 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4248
4249 let num_of_whitespaces = snapshot
4250 .chars_for_range(range.clone())
4251 .take_while(|c| c.is_whitespace())
4252 .count();
4253
4254 // 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.
4255 let column = start_point.column;
4256 let cursor_is_after_start_tag = {
4257 let start_tag_len = start_tag.len();
4258 let start_tag_line = snapshot
4259 .chars_for_range(range.clone())
4260 .skip(num_of_whitespaces)
4261 .take(start_tag_len)
4262 .collect::<String>();
4263 if start_tag_line.starts_with(start_tag.as_ref()) {
4264 num_of_whitespaces + start_tag_len <= column as usize
4265 } else {
4266 false
4267 }
4268 };
4269
4270 let cursor_is_after_delimiter = {
4271 let delimiter_trim = delimiter.trim_end();
4272 let delimiter_line = snapshot
4273 .chars_for_range(range.clone())
4274 .skip(num_of_whitespaces)
4275 .take(delimiter_trim.len())
4276 .collect::<String>();
4277 if delimiter_line.starts_with(delimiter_trim) {
4278 num_of_whitespaces + delimiter_trim.len() <= column as usize
4279 } else {
4280 false
4281 }
4282 };
4283
4284 let cursor_is_before_end_tag_if_exists = {
4285 let mut char_position = 0u32;
4286 let mut end_tag_offset = None;
4287
4288 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4289 if let Some(byte_pos) = chunk.find(&**end_tag) {
4290 let chars_before_match =
4291 chunk[..byte_pos].chars().count() as u32;
4292 end_tag_offset =
4293 Some(char_position + chars_before_match);
4294 break 'outer;
4295 }
4296 char_position += chunk.chars().count() as u32;
4297 }
4298
4299 if let Some(end_tag_offset) = end_tag_offset {
4300 let cursor_is_before_end_tag = column <= end_tag_offset;
4301 if cursor_is_after_start_tag {
4302 if cursor_is_before_end_tag {
4303 insert_extra_newline = true;
4304 }
4305 let cursor_is_at_start_of_end_tag =
4306 column == end_tag_offset;
4307 if cursor_is_at_start_of_end_tag {
4308 indent_on_extra_newline.len = (*len).into();
4309 }
4310 }
4311 cursor_is_before_end_tag
4312 } else {
4313 true
4314 }
4315 };
4316
4317 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4318 && cursor_is_before_end_tag_if_exists
4319 {
4320 if cursor_is_after_start_tag {
4321 indent_on_newline.len = (*len).into();
4322 }
4323 Some(delimiter.clone())
4324 } else {
4325 None
4326 }
4327 });
4328
4329 (
4330 comment_delimiter,
4331 doc_delimiter,
4332 insert_extra_newline,
4333 indent_on_newline,
4334 indent_on_extra_newline,
4335 )
4336 } else {
4337 (
4338 None,
4339 None,
4340 false,
4341 IndentSize::default(),
4342 IndentSize::default(),
4343 )
4344 };
4345
4346 let prevent_auto_indent = doc_delimiter.is_some();
4347 let delimiter = comment_delimiter.or(doc_delimiter);
4348
4349 let capacity_for_delimiter =
4350 delimiter.as_deref().map(str::len).unwrap_or_default();
4351 let mut new_text = String::with_capacity(
4352 1 + capacity_for_delimiter
4353 + existing_indent.len as usize
4354 + indent_on_newline.len as usize
4355 + indent_on_extra_newline.len as usize,
4356 );
4357 new_text.push('\n');
4358 new_text.extend(existing_indent.chars());
4359 new_text.extend(indent_on_newline.chars());
4360
4361 if let Some(delimiter) = &delimiter {
4362 new_text.push_str(delimiter);
4363 }
4364
4365 if insert_extra_newline {
4366 new_text.push('\n');
4367 new_text.extend(existing_indent.chars());
4368 new_text.extend(indent_on_extra_newline.chars());
4369 }
4370
4371 let anchor = buffer.anchor_after(end);
4372 let new_selection = selection.map(|_| anchor);
4373 (
4374 ((start..end, new_text), prevent_auto_indent),
4375 (insert_extra_newline, new_selection),
4376 )
4377 })
4378 .unzip()
4379 };
4380
4381 let mut auto_indent_edits = Vec::new();
4382 let mut edits = Vec::new();
4383 for (edit, prevent_auto_indent) in edits_with_flags {
4384 if prevent_auto_indent {
4385 edits.push(edit);
4386 } else {
4387 auto_indent_edits.push(edit);
4388 }
4389 }
4390 if !edits.is_empty() {
4391 this.edit(edits, cx);
4392 }
4393 if !auto_indent_edits.is_empty() {
4394 this.edit_with_autoindent(auto_indent_edits, cx);
4395 }
4396
4397 let buffer = this.buffer.read(cx).snapshot(cx);
4398 let new_selections = selection_info
4399 .into_iter()
4400 .map(|(extra_newline_inserted, new_selection)| {
4401 let mut cursor = new_selection.end.to_point(&buffer);
4402 if extra_newline_inserted {
4403 cursor.row -= 1;
4404 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4405 }
4406 new_selection.map(|_| cursor)
4407 })
4408 .collect();
4409
4410 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4411 s.select(new_selections)
4412 });
4413 this.refresh_inline_completion(true, false, window, cx);
4414 });
4415 }
4416
4417 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4418 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4419
4420 let buffer = self.buffer.read(cx);
4421 let snapshot = buffer.snapshot(cx);
4422
4423 let mut edits = Vec::new();
4424 let mut rows = Vec::new();
4425
4426 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4427 let cursor = selection.head();
4428 let row = cursor.row;
4429
4430 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4431
4432 let newline = "\n".to_string();
4433 edits.push((start_of_line..start_of_line, newline));
4434
4435 rows.push(row + rows_inserted as u32);
4436 }
4437
4438 self.transact(window, cx, |editor, window, cx| {
4439 editor.edit(edits, cx);
4440
4441 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4442 let mut index = 0;
4443 s.move_cursors_with(|map, _, _| {
4444 let row = rows[index];
4445 index += 1;
4446
4447 let point = Point::new(row, 0);
4448 let boundary = map.next_line_boundary(point).1;
4449 let clipped = map.clip_point(boundary, Bias::Left);
4450
4451 (clipped, SelectionGoal::None)
4452 });
4453 });
4454
4455 let mut indent_edits = Vec::new();
4456 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4457 for row in rows {
4458 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4459 for (row, indent) in indents {
4460 if indent.len == 0 {
4461 continue;
4462 }
4463
4464 let text = match indent.kind {
4465 IndentKind::Space => " ".repeat(indent.len as usize),
4466 IndentKind::Tab => "\t".repeat(indent.len as usize),
4467 };
4468 let point = Point::new(row.0, 0);
4469 indent_edits.push((point..point, text));
4470 }
4471 }
4472 editor.edit(indent_edits, cx);
4473 });
4474 }
4475
4476 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4477 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4478
4479 let buffer = self.buffer.read(cx);
4480 let snapshot = buffer.snapshot(cx);
4481
4482 let mut edits = Vec::new();
4483 let mut rows = Vec::new();
4484 let mut rows_inserted = 0;
4485
4486 for selection in self.selections.all_adjusted(cx) {
4487 let cursor = selection.head();
4488 let row = cursor.row;
4489
4490 let point = Point::new(row + 1, 0);
4491 let start_of_line = snapshot.clip_point(point, Bias::Left);
4492
4493 let newline = "\n".to_string();
4494 edits.push((start_of_line..start_of_line, newline));
4495
4496 rows_inserted += 1;
4497 rows.push(row + rows_inserted);
4498 }
4499
4500 self.transact(window, cx, |editor, window, cx| {
4501 editor.edit(edits, cx);
4502
4503 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4504 let mut index = 0;
4505 s.move_cursors_with(|map, _, _| {
4506 let row = rows[index];
4507 index += 1;
4508
4509 let point = Point::new(row, 0);
4510 let boundary = map.next_line_boundary(point).1;
4511 let clipped = map.clip_point(boundary, Bias::Left);
4512
4513 (clipped, SelectionGoal::None)
4514 });
4515 });
4516
4517 let mut indent_edits = Vec::new();
4518 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4519 for row in rows {
4520 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4521 for (row, indent) in indents {
4522 if indent.len == 0 {
4523 continue;
4524 }
4525
4526 let text = match indent.kind {
4527 IndentKind::Space => " ".repeat(indent.len as usize),
4528 IndentKind::Tab => "\t".repeat(indent.len as usize),
4529 };
4530 let point = Point::new(row.0, 0);
4531 indent_edits.push((point..point, text));
4532 }
4533 }
4534 editor.edit(indent_edits, cx);
4535 });
4536 }
4537
4538 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4539 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4540 original_indent_columns: Vec::new(),
4541 });
4542 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4543 }
4544
4545 fn insert_with_autoindent_mode(
4546 &mut self,
4547 text: &str,
4548 autoindent_mode: Option<AutoindentMode>,
4549 window: &mut Window,
4550 cx: &mut Context<Self>,
4551 ) {
4552 if self.read_only(cx) {
4553 return;
4554 }
4555
4556 let text: Arc<str> = text.into();
4557 self.transact(window, cx, |this, window, cx| {
4558 let old_selections = this.selections.all_adjusted(cx);
4559 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4560 let anchors = {
4561 let snapshot = buffer.read(cx);
4562 old_selections
4563 .iter()
4564 .map(|s| {
4565 let anchor = snapshot.anchor_after(s.head());
4566 s.map(|_| anchor)
4567 })
4568 .collect::<Vec<_>>()
4569 };
4570 buffer.edit(
4571 old_selections
4572 .iter()
4573 .map(|s| (s.start..s.end, text.clone())),
4574 autoindent_mode,
4575 cx,
4576 );
4577 anchors
4578 });
4579
4580 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4581 s.select_anchors(selection_anchors);
4582 });
4583
4584 cx.notify();
4585 });
4586 }
4587
4588 fn trigger_completion_on_input(
4589 &mut self,
4590 text: &str,
4591 trigger_in_words: bool,
4592 window: &mut Window,
4593 cx: &mut Context<Self>,
4594 ) {
4595 let completions_source = self
4596 .context_menu
4597 .borrow()
4598 .as_ref()
4599 .and_then(|menu| match menu {
4600 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4601 CodeContextMenu::CodeActions(_) => None,
4602 });
4603
4604 match completions_source {
4605 Some(CompletionsMenuSource::Words) => {
4606 self.show_word_completions(&ShowWordCompletions, window, cx)
4607 }
4608 Some(CompletionsMenuSource::Normal)
4609 | Some(CompletionsMenuSource::SnippetChoices)
4610 | None
4611 if self.is_completion_trigger(
4612 text,
4613 trigger_in_words,
4614 completions_source.is_some(),
4615 cx,
4616 ) =>
4617 {
4618 self.show_completions(
4619 &ShowCompletions {
4620 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4621 },
4622 window,
4623 cx,
4624 )
4625 }
4626 _ => {
4627 self.hide_context_menu(window, cx);
4628 }
4629 }
4630 }
4631
4632 fn is_completion_trigger(
4633 &self,
4634 text: &str,
4635 trigger_in_words: bool,
4636 menu_is_open: bool,
4637 cx: &mut Context<Self>,
4638 ) -> bool {
4639 let position = self.selections.newest_anchor().head();
4640 let multibuffer = self.buffer.read(cx);
4641 let Some(buffer) = position
4642 .buffer_id
4643 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4644 else {
4645 return false;
4646 };
4647
4648 if let Some(completion_provider) = &self.completion_provider {
4649 completion_provider.is_completion_trigger(
4650 &buffer,
4651 position.text_anchor,
4652 text,
4653 trigger_in_words,
4654 menu_is_open,
4655 cx,
4656 )
4657 } else {
4658 false
4659 }
4660 }
4661
4662 /// If any empty selections is touching the start of its innermost containing autoclose
4663 /// region, expand it to select the brackets.
4664 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4665 let selections = self.selections.all::<usize>(cx);
4666 let buffer = self.buffer.read(cx).read(cx);
4667 let new_selections = self
4668 .selections_with_autoclose_regions(selections, &buffer)
4669 .map(|(mut selection, region)| {
4670 if !selection.is_empty() {
4671 return selection;
4672 }
4673
4674 if let Some(region) = region {
4675 let mut range = region.range.to_offset(&buffer);
4676 if selection.start == range.start && range.start >= region.pair.start.len() {
4677 range.start -= region.pair.start.len();
4678 if buffer.contains_str_at(range.start, ®ion.pair.start)
4679 && buffer.contains_str_at(range.end, ®ion.pair.end)
4680 {
4681 range.end += region.pair.end.len();
4682 selection.start = range.start;
4683 selection.end = range.end;
4684
4685 return selection;
4686 }
4687 }
4688 }
4689
4690 let always_treat_brackets_as_autoclosed = buffer
4691 .language_settings_at(selection.start, cx)
4692 .always_treat_brackets_as_autoclosed;
4693
4694 if !always_treat_brackets_as_autoclosed {
4695 return selection;
4696 }
4697
4698 if let Some(scope) = buffer.language_scope_at(selection.start) {
4699 for (pair, enabled) in scope.brackets() {
4700 if !enabled || !pair.close {
4701 continue;
4702 }
4703
4704 if buffer.contains_str_at(selection.start, &pair.end) {
4705 let pair_start_len = pair.start.len();
4706 if buffer.contains_str_at(
4707 selection.start.saturating_sub(pair_start_len),
4708 &pair.start,
4709 ) {
4710 selection.start -= pair_start_len;
4711 selection.end += pair.end.len();
4712
4713 return selection;
4714 }
4715 }
4716 }
4717 }
4718
4719 selection
4720 })
4721 .collect();
4722
4723 drop(buffer);
4724 self.change_selections(None, window, cx, |selections| {
4725 selections.select(new_selections)
4726 });
4727 }
4728
4729 /// Iterate the given selections, and for each one, find the smallest surrounding
4730 /// autoclose region. This uses the ordering of the selections and the autoclose
4731 /// regions to avoid repeated comparisons.
4732 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4733 &'a self,
4734 selections: impl IntoIterator<Item = Selection<D>>,
4735 buffer: &'a MultiBufferSnapshot,
4736 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4737 let mut i = 0;
4738 let mut regions = self.autoclose_regions.as_slice();
4739 selections.into_iter().map(move |selection| {
4740 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4741
4742 let mut enclosing = None;
4743 while let Some(pair_state) = regions.get(i) {
4744 if pair_state.range.end.to_offset(buffer) < range.start {
4745 regions = ®ions[i + 1..];
4746 i = 0;
4747 } else if pair_state.range.start.to_offset(buffer) > range.end {
4748 break;
4749 } else {
4750 if pair_state.selection_id == selection.id {
4751 enclosing = Some(pair_state);
4752 }
4753 i += 1;
4754 }
4755 }
4756
4757 (selection, enclosing)
4758 })
4759 }
4760
4761 /// Remove any autoclose regions that no longer contain their selection.
4762 fn invalidate_autoclose_regions(
4763 &mut self,
4764 mut selections: &[Selection<Anchor>],
4765 buffer: &MultiBufferSnapshot,
4766 ) {
4767 self.autoclose_regions.retain(|state| {
4768 let mut i = 0;
4769 while let Some(selection) = selections.get(i) {
4770 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4771 selections = &selections[1..];
4772 continue;
4773 }
4774 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4775 break;
4776 }
4777 if selection.id == state.selection_id {
4778 return true;
4779 } else {
4780 i += 1;
4781 }
4782 }
4783 false
4784 });
4785 }
4786
4787 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4788 let offset = position.to_offset(buffer);
4789 let (word_range, kind) = buffer.surrounding_word(offset, true);
4790 if offset > word_range.start && kind == Some(CharKind::Word) {
4791 Some(
4792 buffer
4793 .text_for_range(word_range.start..offset)
4794 .collect::<String>(),
4795 )
4796 } else {
4797 None
4798 }
4799 }
4800
4801 pub fn toggle_inline_values(
4802 &mut self,
4803 _: &ToggleInlineValues,
4804 _: &mut Window,
4805 cx: &mut Context<Self>,
4806 ) {
4807 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4808
4809 self.refresh_inline_values(cx);
4810 }
4811
4812 pub fn toggle_inlay_hints(
4813 &mut self,
4814 _: &ToggleInlayHints,
4815 _: &mut Window,
4816 cx: &mut Context<Self>,
4817 ) {
4818 self.refresh_inlay_hints(
4819 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4820 cx,
4821 );
4822 }
4823
4824 pub fn inlay_hints_enabled(&self) -> bool {
4825 self.inlay_hint_cache.enabled
4826 }
4827
4828 pub fn inline_values_enabled(&self) -> bool {
4829 self.inline_value_cache.enabled
4830 }
4831
4832 #[cfg(any(test, feature = "test-support"))]
4833 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4834 self.display_map
4835 .read(cx)
4836 .current_inlays()
4837 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4838 .cloned()
4839 .collect()
4840 }
4841
4842 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4843 if self.semantics_provider.is_none() || !self.mode.is_full() {
4844 return;
4845 }
4846
4847 let reason_description = reason.description();
4848 let ignore_debounce = matches!(
4849 reason,
4850 InlayHintRefreshReason::SettingsChange(_)
4851 | InlayHintRefreshReason::Toggle(_)
4852 | InlayHintRefreshReason::ExcerptsRemoved(_)
4853 | InlayHintRefreshReason::ModifiersChanged(_)
4854 );
4855 let (invalidate_cache, required_languages) = match reason {
4856 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4857 match self.inlay_hint_cache.modifiers_override(enabled) {
4858 Some(enabled) => {
4859 if enabled {
4860 (InvalidationStrategy::RefreshRequested, None)
4861 } else {
4862 self.splice_inlays(
4863 &self
4864 .visible_inlay_hints(cx)
4865 .iter()
4866 .map(|inlay| inlay.id)
4867 .collect::<Vec<InlayId>>(),
4868 Vec::new(),
4869 cx,
4870 );
4871 return;
4872 }
4873 }
4874 None => return,
4875 }
4876 }
4877 InlayHintRefreshReason::Toggle(enabled) => {
4878 if self.inlay_hint_cache.toggle(enabled) {
4879 if enabled {
4880 (InvalidationStrategy::RefreshRequested, None)
4881 } else {
4882 self.splice_inlays(
4883 &self
4884 .visible_inlay_hints(cx)
4885 .iter()
4886 .map(|inlay| inlay.id)
4887 .collect::<Vec<InlayId>>(),
4888 Vec::new(),
4889 cx,
4890 );
4891 return;
4892 }
4893 } else {
4894 return;
4895 }
4896 }
4897 InlayHintRefreshReason::SettingsChange(new_settings) => {
4898 match self.inlay_hint_cache.update_settings(
4899 &self.buffer,
4900 new_settings,
4901 self.visible_inlay_hints(cx),
4902 cx,
4903 ) {
4904 ControlFlow::Break(Some(InlaySplice {
4905 to_remove,
4906 to_insert,
4907 })) => {
4908 self.splice_inlays(&to_remove, to_insert, cx);
4909 return;
4910 }
4911 ControlFlow::Break(None) => return,
4912 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4913 }
4914 }
4915 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4916 if let Some(InlaySplice {
4917 to_remove,
4918 to_insert,
4919 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4920 {
4921 self.splice_inlays(&to_remove, to_insert, cx);
4922 }
4923 self.display_map.update(cx, |display_map, _| {
4924 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4925 });
4926 return;
4927 }
4928 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4929 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4930 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4931 }
4932 InlayHintRefreshReason::RefreshRequested => {
4933 (InvalidationStrategy::RefreshRequested, None)
4934 }
4935 };
4936
4937 if let Some(InlaySplice {
4938 to_remove,
4939 to_insert,
4940 }) = self.inlay_hint_cache.spawn_hint_refresh(
4941 reason_description,
4942 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4943 invalidate_cache,
4944 ignore_debounce,
4945 cx,
4946 ) {
4947 self.splice_inlays(&to_remove, to_insert, cx);
4948 }
4949 }
4950
4951 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4952 self.display_map
4953 .read(cx)
4954 .current_inlays()
4955 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4956 .cloned()
4957 .collect()
4958 }
4959
4960 pub fn excerpts_for_inlay_hints_query(
4961 &self,
4962 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4963 cx: &mut Context<Editor>,
4964 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4965 let Some(project) = self.project.as_ref() else {
4966 return HashMap::default();
4967 };
4968 let project = project.read(cx);
4969 let multi_buffer = self.buffer().read(cx);
4970 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4971 let multi_buffer_visible_start = self
4972 .scroll_manager
4973 .anchor()
4974 .anchor
4975 .to_point(&multi_buffer_snapshot);
4976 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4977 multi_buffer_visible_start
4978 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4979 Bias::Left,
4980 );
4981 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4982 multi_buffer_snapshot
4983 .range_to_buffer_ranges(multi_buffer_visible_range)
4984 .into_iter()
4985 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4986 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4987 let buffer_file = project::File::from_dyn(buffer.file())?;
4988 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4989 let worktree_entry = buffer_worktree
4990 .read(cx)
4991 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4992 if worktree_entry.is_ignored {
4993 return None;
4994 }
4995
4996 let language = buffer.language()?;
4997 if let Some(restrict_to_languages) = restrict_to_languages {
4998 if !restrict_to_languages.contains(language) {
4999 return None;
5000 }
5001 }
5002 Some((
5003 excerpt_id,
5004 (
5005 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5006 buffer.version().clone(),
5007 excerpt_visible_range,
5008 ),
5009 ))
5010 })
5011 .collect()
5012 }
5013
5014 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5015 TextLayoutDetails {
5016 text_system: window.text_system().clone(),
5017 editor_style: self.style.clone().unwrap(),
5018 rem_size: window.rem_size(),
5019 scroll_anchor: self.scroll_manager.anchor(),
5020 visible_rows: self.visible_line_count(),
5021 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5022 }
5023 }
5024
5025 pub fn splice_inlays(
5026 &self,
5027 to_remove: &[InlayId],
5028 to_insert: Vec<Inlay>,
5029 cx: &mut Context<Self>,
5030 ) {
5031 self.display_map.update(cx, |display_map, cx| {
5032 display_map.splice_inlays(to_remove, to_insert, cx)
5033 });
5034 cx.notify();
5035 }
5036
5037 fn trigger_on_type_formatting(
5038 &self,
5039 input: String,
5040 window: &mut Window,
5041 cx: &mut Context<Self>,
5042 ) -> Option<Task<Result<()>>> {
5043 if input.len() != 1 {
5044 return None;
5045 }
5046
5047 let project = self.project.as_ref()?;
5048 let position = self.selections.newest_anchor().head();
5049 let (buffer, buffer_position) = self
5050 .buffer
5051 .read(cx)
5052 .text_anchor_for_position(position, cx)?;
5053
5054 let settings = language_settings::language_settings(
5055 buffer
5056 .read(cx)
5057 .language_at(buffer_position)
5058 .map(|l| l.name()),
5059 buffer.read(cx).file(),
5060 cx,
5061 );
5062 if !settings.use_on_type_format {
5063 return None;
5064 }
5065
5066 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5067 // hence we do LSP request & edit on host side only — add formats to host's history.
5068 let push_to_lsp_host_history = true;
5069 // If this is not the host, append its history with new edits.
5070 let push_to_client_history = project.read(cx).is_via_collab();
5071
5072 let on_type_formatting = project.update(cx, |project, cx| {
5073 project.on_type_format(
5074 buffer.clone(),
5075 buffer_position,
5076 input,
5077 push_to_lsp_host_history,
5078 cx,
5079 )
5080 });
5081 Some(cx.spawn_in(window, async move |editor, cx| {
5082 if let Some(transaction) = on_type_formatting.await? {
5083 if push_to_client_history {
5084 buffer
5085 .update(cx, |buffer, _| {
5086 buffer.push_transaction(transaction, Instant::now());
5087 buffer.finalize_last_transaction();
5088 })
5089 .ok();
5090 }
5091 editor.update(cx, |editor, cx| {
5092 editor.refresh_document_highlights(cx);
5093 })?;
5094 }
5095 Ok(())
5096 }))
5097 }
5098
5099 pub fn show_word_completions(
5100 &mut self,
5101 _: &ShowWordCompletions,
5102 window: &mut Window,
5103 cx: &mut Context<Self>,
5104 ) {
5105 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5106 }
5107
5108 pub fn show_completions(
5109 &mut self,
5110 options: &ShowCompletions,
5111 window: &mut Window,
5112 cx: &mut Context<Self>,
5113 ) {
5114 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5115 }
5116
5117 fn open_or_update_completions_menu(
5118 &mut self,
5119 requested_source: Option<CompletionsMenuSource>,
5120 trigger: Option<&str>,
5121 window: &mut Window,
5122 cx: &mut Context<Self>,
5123 ) {
5124 if self.pending_rename.is_some() {
5125 return;
5126 }
5127
5128 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5129
5130 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5131 // inserted and selected. To handle that case, the start of the selection is used so that
5132 // the menu starts with all choices.
5133 let position = self
5134 .selections
5135 .newest_anchor()
5136 .start
5137 .bias_right(&multibuffer_snapshot);
5138 if position.diff_base_anchor.is_some() {
5139 return;
5140 }
5141 let (buffer, buffer_position) =
5142 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5143 output
5144 } else {
5145 return;
5146 };
5147 let buffer_snapshot = buffer.read(cx).snapshot();
5148
5149 let query: Option<Arc<String>> =
5150 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5151
5152 drop(multibuffer_snapshot);
5153
5154 let provider = match requested_source {
5155 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5156 Some(CompletionsMenuSource::Words) => None,
5157 Some(CompletionsMenuSource::SnippetChoices) => {
5158 log::error!("bug: SnippetChoices requested_source is not handled");
5159 None
5160 }
5161 };
5162
5163 let sort_completions = provider
5164 .as_ref()
5165 .map_or(false, |provider| provider.sort_completions());
5166
5167 let filter_completions = provider
5168 .as_ref()
5169 .map_or(true, |provider| provider.filter_completions());
5170
5171 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5172 if filter_completions {
5173 menu.filter(query.clone(), provider.clone(), window, cx);
5174 }
5175 // When `is_incomplete` is false, no need to re-query completions when the current query
5176 // is a suffix of the initial query.
5177 if !menu.is_incomplete {
5178 // If the new query is a suffix of the old query (typing more characters) and
5179 // the previous result was complete, the existing completions can be filtered.
5180 //
5181 // Note that this is always true for snippet completions.
5182 let query_matches = match (&menu.initial_query, &query) {
5183 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5184 (None, _) => true,
5185 _ => false,
5186 };
5187 if query_matches {
5188 let position_matches = if menu.initial_position == position {
5189 true
5190 } else {
5191 let snapshot = self.buffer.read(cx).read(cx);
5192 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5193 };
5194 if position_matches {
5195 return;
5196 }
5197 }
5198 }
5199 };
5200
5201 let trigger_kind = match trigger {
5202 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5203 CompletionTriggerKind::TRIGGER_CHARACTER
5204 }
5205 _ => CompletionTriggerKind::INVOKED,
5206 };
5207 let completion_context = CompletionContext {
5208 trigger_character: trigger.and_then(|trigger| {
5209 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5210 Some(String::from(trigger))
5211 } else {
5212 None
5213 }
5214 }),
5215 trigger_kind,
5216 };
5217
5218 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5219 buffer_snapshot.surrounding_word(buffer_position)
5220 {
5221 let word_to_exclude = buffer_snapshot
5222 .text_for_range(word_range.clone())
5223 .collect::<String>();
5224 (
5225 buffer_snapshot.anchor_before(word_range.start)
5226 ..buffer_snapshot.anchor_after(buffer_position),
5227 Some(word_to_exclude),
5228 )
5229 } else {
5230 (buffer_position..buffer_position, None)
5231 };
5232
5233 let language = buffer_snapshot
5234 .language_at(buffer_position)
5235 .map(|language| language.name());
5236
5237 let completion_settings =
5238 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5239
5240 let show_completion_documentation = buffer_snapshot
5241 .settings_at(buffer_position, cx)
5242 .show_completion_documentation;
5243
5244 // The document can be large, so stay in reasonable bounds when searching for words,
5245 // otherwise completion pop-up might be slow to appear.
5246 const WORD_LOOKUP_ROWS: u32 = 5_000;
5247 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5248 let min_word_search = buffer_snapshot.clip_point(
5249 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5250 Bias::Left,
5251 );
5252 let max_word_search = buffer_snapshot.clip_point(
5253 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5254 Bias::Right,
5255 );
5256 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5257 ..buffer_snapshot.point_to_offset(max_word_search);
5258
5259 let skip_digits = query
5260 .as_ref()
5261 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5262
5263 let (mut words, provider_responses) = match &provider {
5264 Some(provider) => {
5265 let provider_responses = provider.completions(
5266 position.excerpt_id,
5267 &buffer,
5268 buffer_position,
5269 completion_context,
5270 window,
5271 cx,
5272 );
5273
5274 let words = match completion_settings.words {
5275 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5276 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5277 .background_spawn(async move {
5278 buffer_snapshot.words_in_range(WordsQuery {
5279 fuzzy_contents: None,
5280 range: word_search_range,
5281 skip_digits,
5282 })
5283 }),
5284 };
5285
5286 (words, provider_responses)
5287 }
5288 None => (
5289 cx.background_spawn(async move {
5290 buffer_snapshot.words_in_range(WordsQuery {
5291 fuzzy_contents: None,
5292 range: word_search_range,
5293 skip_digits,
5294 })
5295 }),
5296 Task::ready(Ok(Vec::new())),
5297 ),
5298 };
5299
5300 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5301
5302 let id = post_inc(&mut self.next_completion_id);
5303 let task = cx.spawn_in(window, async move |editor, cx| {
5304 let Ok(()) = editor.update(cx, |this, _| {
5305 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5306 }) else {
5307 return;
5308 };
5309
5310 // TODO: Ideally completions from different sources would be selectively re-queried, so
5311 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5312 let mut completions = Vec::new();
5313 let mut is_incomplete = false;
5314 if let Some(provider_responses) = provider_responses.await.log_err() {
5315 if !provider_responses.is_empty() {
5316 for response in provider_responses {
5317 completions.extend(response.completions);
5318 is_incomplete = is_incomplete || response.is_incomplete;
5319 }
5320 if completion_settings.words == WordsCompletionMode::Fallback {
5321 words = Task::ready(BTreeMap::default());
5322 }
5323 }
5324 }
5325
5326 let mut words = words.await;
5327 if let Some(word_to_exclude) = &word_to_exclude {
5328 words.remove(word_to_exclude);
5329 }
5330 for lsp_completion in &completions {
5331 words.remove(&lsp_completion.new_text);
5332 }
5333 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5334 replace_range: word_replace_range.clone(),
5335 new_text: word.clone(),
5336 label: CodeLabel::plain(word, None),
5337 icon_path: None,
5338 documentation: None,
5339 source: CompletionSource::BufferWord {
5340 word_range,
5341 resolved: false,
5342 },
5343 insert_text_mode: Some(InsertTextMode::AS_IS),
5344 confirm: None,
5345 }));
5346
5347 let menu = if completions.is_empty() {
5348 None
5349 } else {
5350 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5351 let languages = editor
5352 .workspace
5353 .as_ref()
5354 .and_then(|(workspace, _)| workspace.upgrade())
5355 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5356 let menu = CompletionsMenu::new(
5357 id,
5358 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5359 sort_completions,
5360 show_completion_documentation,
5361 position,
5362 query.clone(),
5363 is_incomplete,
5364 buffer.clone(),
5365 completions.into(),
5366 snippet_sort_order,
5367 languages,
5368 language,
5369 cx,
5370 );
5371
5372 let query = if filter_completions { query } else { None };
5373 let matches_task = if let Some(query) = query {
5374 menu.do_async_filtering(query, cx)
5375 } else {
5376 Task::ready(menu.unfiltered_matches())
5377 };
5378 (menu, matches_task)
5379 }) else {
5380 return;
5381 };
5382
5383 let matches = matches_task.await;
5384
5385 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5386 // Newer menu already set, so exit.
5387 match editor.context_menu.borrow().as_ref() {
5388 Some(CodeContextMenu::Completions(prev_menu)) => {
5389 if prev_menu.id > id {
5390 return;
5391 }
5392 }
5393 _ => {}
5394 };
5395
5396 // Only valid to take prev_menu because it the new menu is immediately set
5397 // below, or the menu is hidden.
5398 match editor.context_menu.borrow_mut().take() {
5399 Some(CodeContextMenu::Completions(prev_menu)) => {
5400 let position_matches =
5401 if prev_menu.initial_position == menu.initial_position {
5402 true
5403 } else {
5404 let snapshot = editor.buffer.read(cx).read(cx);
5405 prev_menu.initial_position.to_offset(&snapshot)
5406 == menu.initial_position.to_offset(&snapshot)
5407 };
5408 if position_matches {
5409 // Preserve markdown cache before `set_filter_results` because it will
5410 // try to populate the documentation cache.
5411 menu.preserve_markdown_cache(prev_menu);
5412 }
5413 }
5414 _ => {}
5415 };
5416
5417 menu.set_filter_results(matches, provider, window, cx);
5418 }) else {
5419 return;
5420 };
5421
5422 menu.visible().then_some(menu)
5423 };
5424
5425 editor
5426 .update_in(cx, |editor, window, cx| {
5427 if editor.focus_handle.is_focused(window) {
5428 if let Some(menu) = menu {
5429 *editor.context_menu.borrow_mut() =
5430 Some(CodeContextMenu::Completions(menu));
5431
5432 crate::hover_popover::hide_hover(editor, cx);
5433 if editor.show_edit_predictions_in_menu() {
5434 editor.update_visible_inline_completion(window, cx);
5435 } else {
5436 editor.discard_inline_completion(false, cx);
5437 }
5438
5439 cx.notify();
5440 return;
5441 }
5442 }
5443
5444 if editor.completion_tasks.len() <= 1 {
5445 // If there are no more completion tasks and the last menu was empty, we should hide it.
5446 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5447 // If it was already hidden and we don't show inline completions in the menu, we should
5448 // also show the inline-completion when available.
5449 if was_hidden && editor.show_edit_predictions_in_menu() {
5450 editor.update_visible_inline_completion(window, cx);
5451 }
5452 }
5453 })
5454 .ok();
5455 });
5456
5457 self.completion_tasks.push((id, task));
5458 }
5459
5460 #[cfg(feature = "test-support")]
5461 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5462 let menu = self.context_menu.borrow();
5463 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5464 let completions = menu.completions.borrow();
5465 Some(completions.to_vec())
5466 } else {
5467 None
5468 }
5469 }
5470
5471 pub fn with_completions_menu_matching_id<R>(
5472 &self,
5473 id: CompletionId,
5474 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5475 ) -> R {
5476 let mut context_menu = self.context_menu.borrow_mut();
5477 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5478 return f(None);
5479 };
5480 if completions_menu.id != id {
5481 return f(None);
5482 }
5483 f(Some(completions_menu))
5484 }
5485
5486 pub fn confirm_completion(
5487 &mut self,
5488 action: &ConfirmCompletion,
5489 window: &mut Window,
5490 cx: &mut Context<Self>,
5491 ) -> Option<Task<Result<()>>> {
5492 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5493 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5494 }
5495
5496 pub fn confirm_completion_insert(
5497 &mut self,
5498 _: &ConfirmCompletionInsert,
5499 window: &mut Window,
5500 cx: &mut Context<Self>,
5501 ) -> Option<Task<Result<()>>> {
5502 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5503 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5504 }
5505
5506 pub fn confirm_completion_replace(
5507 &mut self,
5508 _: &ConfirmCompletionReplace,
5509 window: &mut Window,
5510 cx: &mut Context<Self>,
5511 ) -> Option<Task<Result<()>>> {
5512 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5513 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5514 }
5515
5516 pub fn compose_completion(
5517 &mut self,
5518 action: &ComposeCompletion,
5519 window: &mut Window,
5520 cx: &mut Context<Self>,
5521 ) -> Option<Task<Result<()>>> {
5522 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5523 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5524 }
5525
5526 fn do_completion(
5527 &mut self,
5528 item_ix: Option<usize>,
5529 intent: CompletionIntent,
5530 window: &mut Window,
5531 cx: &mut Context<Editor>,
5532 ) -> Option<Task<Result<()>>> {
5533 use language::ToOffset as _;
5534
5535 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5536 else {
5537 return None;
5538 };
5539
5540 let candidate_id = {
5541 let entries = completions_menu.entries.borrow();
5542 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5543 if self.show_edit_predictions_in_menu() {
5544 self.discard_inline_completion(true, cx);
5545 }
5546 mat.candidate_id
5547 };
5548
5549 let completion = completions_menu
5550 .completions
5551 .borrow()
5552 .get(candidate_id)?
5553 .clone();
5554 cx.stop_propagation();
5555
5556 let buffer_handle = completions_menu.buffer.clone();
5557
5558 let CompletionEdit {
5559 new_text,
5560 snippet,
5561 replace_range,
5562 } = process_completion_for_edit(
5563 &completion,
5564 intent,
5565 &buffer_handle,
5566 &completions_menu.initial_position.text_anchor,
5567 cx,
5568 );
5569
5570 let buffer = buffer_handle.read(cx);
5571 let snapshot = self.buffer.read(cx).snapshot(cx);
5572 let newest_anchor = self.selections.newest_anchor();
5573 let replace_range_multibuffer = {
5574 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5575 let multibuffer_anchor = snapshot
5576 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5577 .unwrap()
5578 ..snapshot
5579 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5580 .unwrap();
5581 multibuffer_anchor.start.to_offset(&snapshot)
5582 ..multibuffer_anchor.end.to_offset(&snapshot)
5583 };
5584 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5585 return None;
5586 }
5587
5588 let old_text = buffer
5589 .text_for_range(replace_range.clone())
5590 .collect::<String>();
5591 let lookbehind = newest_anchor
5592 .start
5593 .text_anchor
5594 .to_offset(buffer)
5595 .saturating_sub(replace_range.start);
5596 let lookahead = replace_range
5597 .end
5598 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5599 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5600 let suffix = &old_text[lookbehind.min(old_text.len())..];
5601
5602 let selections = self.selections.all::<usize>(cx);
5603 let mut ranges = Vec::new();
5604 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5605
5606 for selection in &selections {
5607 let range = if selection.id == newest_anchor.id {
5608 replace_range_multibuffer.clone()
5609 } else {
5610 let mut range = selection.range();
5611
5612 // if prefix is present, don't duplicate it
5613 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5614 range.start = range.start.saturating_sub(lookbehind);
5615
5616 // if suffix is also present, mimic the newest cursor and replace it
5617 if selection.id != newest_anchor.id
5618 && snapshot.contains_str_at(range.end, suffix)
5619 {
5620 range.end += lookahead;
5621 }
5622 }
5623 range
5624 };
5625
5626 ranges.push(range.clone());
5627
5628 if !self.linked_edit_ranges.is_empty() {
5629 let start_anchor = snapshot.anchor_before(range.start);
5630 let end_anchor = snapshot.anchor_after(range.end);
5631 if let Some(ranges) = self
5632 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5633 {
5634 for (buffer, edits) in ranges {
5635 linked_edits
5636 .entry(buffer.clone())
5637 .or_default()
5638 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5639 }
5640 }
5641 }
5642 }
5643
5644 let common_prefix_len = old_text
5645 .chars()
5646 .zip(new_text.chars())
5647 .take_while(|(a, b)| a == b)
5648 .map(|(a, _)| a.len_utf8())
5649 .sum::<usize>();
5650
5651 cx.emit(EditorEvent::InputHandled {
5652 utf16_range_to_replace: None,
5653 text: new_text[common_prefix_len..].into(),
5654 });
5655
5656 self.transact(window, cx, |this, window, cx| {
5657 if let Some(mut snippet) = snippet {
5658 snippet.text = new_text.to_string();
5659 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5660 } else {
5661 this.buffer.update(cx, |buffer, cx| {
5662 let auto_indent = match completion.insert_text_mode {
5663 Some(InsertTextMode::AS_IS) => None,
5664 _ => this.autoindent_mode.clone(),
5665 };
5666 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5667 buffer.edit(edits, auto_indent, cx);
5668 });
5669 }
5670 for (buffer, edits) in linked_edits {
5671 buffer.update(cx, |buffer, cx| {
5672 let snapshot = buffer.snapshot();
5673 let edits = edits
5674 .into_iter()
5675 .map(|(range, text)| {
5676 use text::ToPoint as TP;
5677 let end_point = TP::to_point(&range.end, &snapshot);
5678 let start_point = TP::to_point(&range.start, &snapshot);
5679 (start_point..end_point, text)
5680 })
5681 .sorted_by_key(|(range, _)| range.start);
5682 buffer.edit(edits, None, cx);
5683 })
5684 }
5685
5686 this.refresh_inline_completion(true, false, window, cx);
5687 });
5688
5689 let show_new_completions_on_confirm = completion
5690 .confirm
5691 .as_ref()
5692 .map_or(false, |confirm| confirm(intent, window, cx));
5693 if show_new_completions_on_confirm {
5694 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5695 }
5696
5697 let provider = self.completion_provider.as_ref()?;
5698 drop(completion);
5699 let apply_edits = provider.apply_additional_edits_for_completion(
5700 buffer_handle,
5701 completions_menu.completions.clone(),
5702 candidate_id,
5703 true,
5704 cx,
5705 );
5706
5707 let editor_settings = EditorSettings::get_global(cx);
5708 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5709 // After the code completion is finished, users often want to know what signatures are needed.
5710 // so we should automatically call signature_help
5711 self.show_signature_help(&ShowSignatureHelp, window, cx);
5712 }
5713
5714 Some(cx.foreground_executor().spawn(async move {
5715 apply_edits.await?;
5716 Ok(())
5717 }))
5718 }
5719
5720 pub fn toggle_code_actions(
5721 &mut self,
5722 action: &ToggleCodeActions,
5723 window: &mut Window,
5724 cx: &mut Context<Self>,
5725 ) {
5726 let quick_launch = action.quick_launch;
5727 let mut context_menu = self.context_menu.borrow_mut();
5728 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5729 if code_actions.deployed_from == action.deployed_from {
5730 // Toggle if we're selecting the same one
5731 *context_menu = None;
5732 cx.notify();
5733 return;
5734 } else {
5735 // Otherwise, clear it and start a new one
5736 *context_menu = None;
5737 cx.notify();
5738 }
5739 }
5740 drop(context_menu);
5741 let snapshot = self.snapshot(window, cx);
5742 let deployed_from = action.deployed_from.clone();
5743 let action = action.clone();
5744 self.completion_tasks.clear();
5745 self.discard_inline_completion(false, cx);
5746
5747 let multibuffer_point = match &action.deployed_from {
5748 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5749 DisplayPoint::new(*row, 0).to_point(&snapshot)
5750 }
5751 _ => self.selections.newest::<Point>(cx).head(),
5752 };
5753 let Some((buffer, buffer_row)) = snapshot
5754 .buffer_snapshot
5755 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5756 .and_then(|(buffer_snapshot, range)| {
5757 self.buffer()
5758 .read(cx)
5759 .buffer(buffer_snapshot.remote_id())
5760 .map(|buffer| (buffer, range.start.row))
5761 })
5762 else {
5763 return;
5764 };
5765 let buffer_id = buffer.read(cx).remote_id();
5766 let tasks = self
5767 .tasks
5768 .get(&(buffer_id, buffer_row))
5769 .map(|t| Arc::new(t.to_owned()));
5770
5771 if !self.focus_handle.is_focused(window) {
5772 return;
5773 }
5774 let project = self.project.clone();
5775
5776 let code_actions_task = match deployed_from {
5777 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5778 _ => self.code_actions(buffer_row, window, cx),
5779 };
5780
5781 let runnable_task = match deployed_from {
5782 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5783 _ => {
5784 let mut task_context_task = Task::ready(None);
5785 if let Some(tasks) = &tasks {
5786 if let Some(project) = project {
5787 task_context_task =
5788 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5789 }
5790 }
5791
5792 cx.spawn_in(window, {
5793 let buffer = buffer.clone();
5794 async move |editor, cx| {
5795 let task_context = task_context_task.await;
5796
5797 let resolved_tasks =
5798 tasks
5799 .zip(task_context.clone())
5800 .map(|(tasks, task_context)| ResolvedTasks {
5801 templates: tasks.resolve(&task_context).collect(),
5802 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5803 multibuffer_point.row,
5804 tasks.column,
5805 )),
5806 });
5807 let debug_scenarios = editor
5808 .update(cx, |editor, cx| {
5809 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5810 })?
5811 .await;
5812 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5813 }
5814 })
5815 }
5816 };
5817
5818 cx.spawn_in(window, async move |editor, cx| {
5819 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5820 let code_actions = code_actions_task.await;
5821 let spawn_straight_away = quick_launch
5822 && resolved_tasks
5823 .as_ref()
5824 .map_or(false, |tasks| tasks.templates.len() == 1)
5825 && code_actions
5826 .as_ref()
5827 .map_or(true, |actions| actions.is_empty())
5828 && debug_scenarios.is_empty();
5829
5830 editor.update_in(cx, |editor, window, cx| {
5831 crate::hover_popover::hide_hover(editor, cx);
5832 *editor.context_menu.borrow_mut() =
5833 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5834 buffer,
5835 actions: CodeActionContents::new(
5836 resolved_tasks,
5837 code_actions,
5838 debug_scenarios,
5839 task_context.unwrap_or_default(),
5840 ),
5841 selected_item: Default::default(),
5842 scroll_handle: UniformListScrollHandle::default(),
5843 deployed_from,
5844 }));
5845 if spawn_straight_away {
5846 if let Some(task) = editor.confirm_code_action(
5847 &ConfirmCodeAction { item_ix: Some(0) },
5848 window,
5849 cx,
5850 ) {
5851 cx.notify();
5852 return task;
5853 }
5854 }
5855
5856 Task::ready(Ok(()))
5857 })
5858 })
5859 .detach_and_log_err(cx);
5860 }
5861
5862 fn debug_scenarios(
5863 &mut self,
5864 resolved_tasks: &Option<ResolvedTasks>,
5865 buffer: &Entity<Buffer>,
5866 cx: &mut App,
5867 ) -> Task<Vec<task::DebugScenario>> {
5868 if cx.has_flag::<DebuggerFeatureFlag>() {
5869 maybe!({
5870 let project = self.project.as_ref()?;
5871 let dap_store = project.read(cx).dap_store();
5872 let mut scenarios = vec![];
5873 let resolved_tasks = resolved_tasks.as_ref()?;
5874 let buffer = buffer.read(cx);
5875 let language = buffer.language()?;
5876 let file = buffer.file();
5877 let debug_adapter = language_settings(language.name().into(), file, cx)
5878 .debuggers
5879 .first()
5880 .map(SharedString::from)
5881 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5882
5883 dap_store.update(cx, |dap_store, cx| {
5884 for (_, task) in &resolved_tasks.templates {
5885 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5886 task.original_task().clone(),
5887 debug_adapter.clone().into(),
5888 task.display_label().to_owned().into(),
5889 cx,
5890 );
5891 scenarios.push(maybe_scenario);
5892 }
5893 });
5894 Some(cx.background_spawn(async move {
5895 let scenarios = futures::future::join_all(scenarios)
5896 .await
5897 .into_iter()
5898 .flatten()
5899 .collect::<Vec<_>>();
5900 scenarios
5901 }))
5902 })
5903 .unwrap_or_else(|| Task::ready(vec![]))
5904 } else {
5905 Task::ready(vec![])
5906 }
5907 }
5908
5909 fn code_actions(
5910 &mut self,
5911 buffer_row: u32,
5912 window: &mut Window,
5913 cx: &mut Context<Self>,
5914 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5915 let mut task = self.code_actions_task.take();
5916 cx.spawn_in(window, async move |editor, cx| {
5917 while let Some(prev_task) = task {
5918 prev_task.await.log_err();
5919 task = editor
5920 .update(cx, |this, _| this.code_actions_task.take())
5921 .ok()?;
5922 }
5923
5924 editor
5925 .update(cx, |editor, cx| {
5926 editor
5927 .available_code_actions
5928 .clone()
5929 .and_then(|(location, code_actions)| {
5930 let snapshot = location.buffer.read(cx).snapshot();
5931 let point_range = location.range.to_point(&snapshot);
5932 let point_range = point_range.start.row..=point_range.end.row;
5933 if point_range.contains(&buffer_row) {
5934 Some(code_actions)
5935 } else {
5936 None
5937 }
5938 })
5939 })
5940 .ok()
5941 .flatten()
5942 })
5943 }
5944
5945 pub fn confirm_code_action(
5946 &mut self,
5947 action: &ConfirmCodeAction,
5948 window: &mut Window,
5949 cx: &mut Context<Self>,
5950 ) -> Option<Task<Result<()>>> {
5951 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5952
5953 let actions_menu =
5954 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5955 menu
5956 } else {
5957 return None;
5958 };
5959
5960 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5961 let action = actions_menu.actions.get(action_ix)?;
5962 let title = action.label();
5963 let buffer = actions_menu.buffer;
5964 let workspace = self.workspace()?;
5965
5966 match action {
5967 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5968 workspace.update(cx, |workspace, cx| {
5969 workspace.schedule_resolved_task(
5970 task_source_kind,
5971 resolved_task,
5972 false,
5973 window,
5974 cx,
5975 );
5976
5977 Some(Task::ready(Ok(())))
5978 })
5979 }
5980 CodeActionsItem::CodeAction {
5981 excerpt_id,
5982 action,
5983 provider,
5984 } => {
5985 let apply_code_action =
5986 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5987 let workspace = workspace.downgrade();
5988 Some(cx.spawn_in(window, async move |editor, cx| {
5989 let project_transaction = apply_code_action.await?;
5990 Self::open_project_transaction(
5991 &editor,
5992 workspace,
5993 project_transaction,
5994 title,
5995 cx,
5996 )
5997 .await
5998 }))
5999 }
6000 CodeActionsItem::DebugScenario(scenario) => {
6001 let context = actions_menu.actions.context.clone();
6002
6003 workspace.update(cx, |workspace, cx| {
6004 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6005 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6006 });
6007 Some(Task::ready(Ok(())))
6008 }
6009 }
6010 }
6011
6012 pub async fn open_project_transaction(
6013 this: &WeakEntity<Editor>,
6014 workspace: WeakEntity<Workspace>,
6015 transaction: ProjectTransaction,
6016 title: String,
6017 cx: &mut AsyncWindowContext,
6018 ) -> Result<()> {
6019 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6020 cx.update(|_, cx| {
6021 entries.sort_unstable_by_key(|(buffer, _)| {
6022 buffer.read(cx).file().map(|f| f.path().clone())
6023 });
6024 })?;
6025
6026 // If the project transaction's edits are all contained within this editor, then
6027 // avoid opening a new editor to display them.
6028
6029 if let Some((buffer, transaction)) = entries.first() {
6030 if entries.len() == 1 {
6031 let excerpt = this.update(cx, |editor, cx| {
6032 editor
6033 .buffer()
6034 .read(cx)
6035 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6036 })?;
6037 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6038 if excerpted_buffer == *buffer {
6039 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6040 let excerpt_range = excerpt_range.to_offset(buffer);
6041 buffer
6042 .edited_ranges_for_transaction::<usize>(transaction)
6043 .all(|range| {
6044 excerpt_range.start <= range.start
6045 && excerpt_range.end >= range.end
6046 })
6047 })?;
6048
6049 if all_edits_within_excerpt {
6050 return Ok(());
6051 }
6052 }
6053 }
6054 }
6055 } else {
6056 return Ok(());
6057 }
6058
6059 let mut ranges_to_highlight = Vec::new();
6060 let excerpt_buffer = cx.new(|cx| {
6061 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6062 for (buffer_handle, transaction) in &entries {
6063 let edited_ranges = buffer_handle
6064 .read(cx)
6065 .edited_ranges_for_transaction::<Point>(transaction)
6066 .collect::<Vec<_>>();
6067 let (ranges, _) = multibuffer.set_excerpts_for_path(
6068 PathKey::for_buffer(buffer_handle, cx),
6069 buffer_handle.clone(),
6070 edited_ranges,
6071 DEFAULT_MULTIBUFFER_CONTEXT,
6072 cx,
6073 );
6074
6075 ranges_to_highlight.extend(ranges);
6076 }
6077 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6078 multibuffer
6079 })?;
6080
6081 workspace.update_in(cx, |workspace, window, cx| {
6082 let project = workspace.project().clone();
6083 let editor =
6084 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6085 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6086 editor.update(cx, |editor, cx| {
6087 editor.highlight_background::<Self>(
6088 &ranges_to_highlight,
6089 |theme| theme.editor_highlighted_line_background,
6090 cx,
6091 );
6092 });
6093 })?;
6094
6095 Ok(())
6096 }
6097
6098 pub fn clear_code_action_providers(&mut self) {
6099 self.code_action_providers.clear();
6100 self.available_code_actions.take();
6101 }
6102
6103 pub fn add_code_action_provider(
6104 &mut self,
6105 provider: Rc<dyn CodeActionProvider>,
6106 window: &mut Window,
6107 cx: &mut Context<Self>,
6108 ) {
6109 if self
6110 .code_action_providers
6111 .iter()
6112 .any(|existing_provider| existing_provider.id() == provider.id())
6113 {
6114 return;
6115 }
6116
6117 self.code_action_providers.push(provider);
6118 self.refresh_code_actions(window, cx);
6119 }
6120
6121 pub fn remove_code_action_provider(
6122 &mut self,
6123 id: Arc<str>,
6124 window: &mut Window,
6125 cx: &mut Context<Self>,
6126 ) {
6127 self.code_action_providers
6128 .retain(|provider| provider.id() != id);
6129 self.refresh_code_actions(window, cx);
6130 }
6131
6132 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6133 !self.code_action_providers.is_empty()
6134 && EditorSettings::get_global(cx).toolbar.code_actions
6135 }
6136
6137 pub fn has_available_code_actions(&self) -> bool {
6138 self.available_code_actions
6139 .as_ref()
6140 .is_some_and(|(_, actions)| !actions.is_empty())
6141 }
6142
6143 fn render_inline_code_actions(
6144 &self,
6145 icon_size: ui::IconSize,
6146 display_row: DisplayRow,
6147 is_active: bool,
6148 cx: &mut Context<Self>,
6149 ) -> AnyElement {
6150 let show_tooltip = !self.context_menu_visible();
6151 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6152 .icon_size(icon_size)
6153 .shape(ui::IconButtonShape::Square)
6154 .style(ButtonStyle::Transparent)
6155 .icon_color(ui::Color::Hidden)
6156 .toggle_state(is_active)
6157 .when(show_tooltip, |this| {
6158 this.tooltip({
6159 let focus_handle = self.focus_handle.clone();
6160 move |window, cx| {
6161 Tooltip::for_action_in(
6162 "Toggle Code Actions",
6163 &ToggleCodeActions {
6164 deployed_from: None,
6165 quick_launch: false,
6166 },
6167 &focus_handle,
6168 window,
6169 cx,
6170 )
6171 }
6172 })
6173 })
6174 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6175 window.focus(&editor.focus_handle(cx));
6176 editor.toggle_code_actions(
6177 &crate::actions::ToggleCodeActions {
6178 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6179 display_row,
6180 )),
6181 quick_launch: false,
6182 },
6183 window,
6184 cx,
6185 );
6186 }))
6187 .into_any_element()
6188 }
6189
6190 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6191 &self.context_menu
6192 }
6193
6194 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6195 let newest_selection = self.selections.newest_anchor().clone();
6196 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6197 let buffer = self.buffer.read(cx);
6198 if newest_selection.head().diff_base_anchor.is_some() {
6199 return None;
6200 }
6201 let (start_buffer, start) =
6202 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6203 let (end_buffer, end) =
6204 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6205 if start_buffer != end_buffer {
6206 return None;
6207 }
6208
6209 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6210 cx.background_executor()
6211 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6212 .await;
6213
6214 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6215 let providers = this.code_action_providers.clone();
6216 let tasks = this
6217 .code_action_providers
6218 .iter()
6219 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6220 .collect::<Vec<_>>();
6221 (providers, tasks)
6222 })?;
6223
6224 let mut actions = Vec::new();
6225 for (provider, provider_actions) in
6226 providers.into_iter().zip(future::join_all(tasks).await)
6227 {
6228 if let Some(provider_actions) = provider_actions.log_err() {
6229 actions.extend(provider_actions.into_iter().map(|action| {
6230 AvailableCodeAction {
6231 excerpt_id: newest_selection.start.excerpt_id,
6232 action,
6233 provider: provider.clone(),
6234 }
6235 }));
6236 }
6237 }
6238
6239 this.update(cx, |this, cx| {
6240 this.available_code_actions = if actions.is_empty() {
6241 None
6242 } else {
6243 Some((
6244 Location {
6245 buffer: start_buffer,
6246 range: start..end,
6247 },
6248 actions.into(),
6249 ))
6250 };
6251 cx.notify();
6252 })
6253 }));
6254 None
6255 }
6256
6257 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6258 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6259 self.show_git_blame_inline = false;
6260
6261 self.show_git_blame_inline_delay_task =
6262 Some(cx.spawn_in(window, async move |this, cx| {
6263 cx.background_executor().timer(delay).await;
6264
6265 this.update(cx, |this, cx| {
6266 this.show_git_blame_inline = true;
6267 cx.notify();
6268 })
6269 .log_err();
6270 }));
6271 }
6272 }
6273
6274 fn show_blame_popover(
6275 &mut self,
6276 blame_entry: &BlameEntry,
6277 position: gpui::Point<Pixels>,
6278 cx: &mut Context<Self>,
6279 ) {
6280 if let Some(state) = &mut self.inline_blame_popover {
6281 state.hide_task.take();
6282 } else {
6283 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6284 let blame_entry = blame_entry.clone();
6285 let show_task = cx.spawn(async move |editor, cx| {
6286 cx.background_executor()
6287 .timer(std::time::Duration::from_millis(delay))
6288 .await;
6289 editor
6290 .update(cx, |editor, cx| {
6291 editor.inline_blame_popover_show_task.take();
6292 let Some(blame) = editor.blame.as_ref() else {
6293 return;
6294 };
6295 let blame = blame.read(cx);
6296 let details = blame.details_for_entry(&blame_entry);
6297 let markdown = cx.new(|cx| {
6298 Markdown::new(
6299 details
6300 .as_ref()
6301 .map(|message| message.message.clone())
6302 .unwrap_or_default(),
6303 None,
6304 None,
6305 cx,
6306 )
6307 });
6308 editor.inline_blame_popover = Some(InlineBlamePopover {
6309 position,
6310 hide_task: None,
6311 popover_bounds: None,
6312 popover_state: InlineBlamePopoverState {
6313 scroll_handle: ScrollHandle::new(),
6314 commit_message: details,
6315 markdown,
6316 },
6317 });
6318 cx.notify();
6319 })
6320 .ok();
6321 });
6322 self.inline_blame_popover_show_task = Some(show_task);
6323 }
6324 }
6325
6326 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6327 self.inline_blame_popover_show_task.take();
6328 if let Some(state) = &mut self.inline_blame_popover {
6329 let hide_task = cx.spawn(async move |editor, cx| {
6330 cx.background_executor()
6331 .timer(std::time::Duration::from_millis(100))
6332 .await;
6333 editor
6334 .update(cx, |editor, cx| {
6335 editor.inline_blame_popover.take();
6336 cx.notify();
6337 })
6338 .ok();
6339 });
6340 state.hide_task = Some(hide_task);
6341 }
6342 }
6343
6344 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6345 if self.pending_rename.is_some() {
6346 return None;
6347 }
6348
6349 let provider = self.semantics_provider.clone()?;
6350 let buffer = self.buffer.read(cx);
6351 let newest_selection = self.selections.newest_anchor().clone();
6352 let cursor_position = newest_selection.head();
6353 let (cursor_buffer, cursor_buffer_position) =
6354 buffer.text_anchor_for_position(cursor_position, cx)?;
6355 let (tail_buffer, tail_buffer_position) =
6356 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6357 if cursor_buffer != tail_buffer {
6358 return None;
6359 }
6360
6361 let snapshot = cursor_buffer.read(cx).snapshot();
6362 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6363 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6364 if start_word_range != end_word_range {
6365 self.document_highlights_task.take();
6366 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6367 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6368 return None;
6369 }
6370
6371 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6372 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6373 cx.background_executor()
6374 .timer(Duration::from_millis(debounce))
6375 .await;
6376
6377 let highlights = if let Some(highlights) = cx
6378 .update(|cx| {
6379 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6380 })
6381 .ok()
6382 .flatten()
6383 {
6384 highlights.await.log_err()
6385 } else {
6386 None
6387 };
6388
6389 if let Some(highlights) = highlights {
6390 this.update(cx, |this, cx| {
6391 if this.pending_rename.is_some() {
6392 return;
6393 }
6394
6395 let buffer_id = cursor_position.buffer_id;
6396 let buffer = this.buffer.read(cx);
6397 if !buffer
6398 .text_anchor_for_position(cursor_position, cx)
6399 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6400 {
6401 return;
6402 }
6403
6404 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6405 let mut write_ranges = Vec::new();
6406 let mut read_ranges = Vec::new();
6407 for highlight in highlights {
6408 for (excerpt_id, excerpt_range) in
6409 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6410 {
6411 let start = highlight
6412 .range
6413 .start
6414 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6415 let end = highlight
6416 .range
6417 .end
6418 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6419 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6420 continue;
6421 }
6422
6423 let range = Anchor {
6424 buffer_id,
6425 excerpt_id,
6426 text_anchor: start,
6427 diff_base_anchor: None,
6428 }..Anchor {
6429 buffer_id,
6430 excerpt_id,
6431 text_anchor: end,
6432 diff_base_anchor: None,
6433 };
6434 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6435 write_ranges.push(range);
6436 } else {
6437 read_ranges.push(range);
6438 }
6439 }
6440 }
6441
6442 this.highlight_background::<DocumentHighlightRead>(
6443 &read_ranges,
6444 |theme| theme.editor_document_highlight_read_background,
6445 cx,
6446 );
6447 this.highlight_background::<DocumentHighlightWrite>(
6448 &write_ranges,
6449 |theme| theme.editor_document_highlight_write_background,
6450 cx,
6451 );
6452 cx.notify();
6453 })
6454 .log_err();
6455 }
6456 }));
6457 None
6458 }
6459
6460 fn prepare_highlight_query_from_selection(
6461 &mut self,
6462 cx: &mut Context<Editor>,
6463 ) -> Option<(String, Range<Anchor>)> {
6464 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6465 return None;
6466 }
6467 if !EditorSettings::get_global(cx).selection_highlight {
6468 return None;
6469 }
6470 if self.selections.count() != 1 || self.selections.line_mode {
6471 return None;
6472 }
6473 let selection = self.selections.newest::<Point>(cx);
6474 if selection.is_empty() || selection.start.row != selection.end.row {
6475 return None;
6476 }
6477 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6478 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6479 let query = multi_buffer_snapshot
6480 .text_for_range(selection_anchor_range.clone())
6481 .collect::<String>();
6482 if query.trim().is_empty() {
6483 return None;
6484 }
6485 Some((query, selection_anchor_range))
6486 }
6487
6488 fn update_selection_occurrence_highlights(
6489 &mut self,
6490 query_text: String,
6491 query_range: Range<Anchor>,
6492 multi_buffer_range_to_query: Range<Point>,
6493 use_debounce: bool,
6494 window: &mut Window,
6495 cx: &mut Context<Editor>,
6496 ) -> Task<()> {
6497 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6498 cx.spawn_in(window, async move |editor, cx| {
6499 if use_debounce {
6500 cx.background_executor()
6501 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6502 .await;
6503 }
6504 let match_task = cx.background_spawn(async move {
6505 let buffer_ranges = multi_buffer_snapshot
6506 .range_to_buffer_ranges(multi_buffer_range_to_query)
6507 .into_iter()
6508 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6509 let mut match_ranges = Vec::new();
6510 let Ok(regex) = project::search::SearchQuery::text(
6511 query_text.clone(),
6512 false,
6513 false,
6514 false,
6515 Default::default(),
6516 Default::default(),
6517 false,
6518 None,
6519 ) else {
6520 return Vec::default();
6521 };
6522 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6523 match_ranges.extend(
6524 regex
6525 .search(&buffer_snapshot, Some(search_range.clone()))
6526 .await
6527 .into_iter()
6528 .filter_map(|match_range| {
6529 let match_start = buffer_snapshot
6530 .anchor_after(search_range.start + match_range.start);
6531 let match_end = buffer_snapshot
6532 .anchor_before(search_range.start + match_range.end);
6533 let match_anchor_range = Anchor::range_in_buffer(
6534 excerpt_id,
6535 buffer_snapshot.remote_id(),
6536 match_start..match_end,
6537 );
6538 (match_anchor_range != query_range).then_some(match_anchor_range)
6539 }),
6540 );
6541 }
6542 match_ranges
6543 });
6544 let match_ranges = match_task.await;
6545 editor
6546 .update_in(cx, |editor, _, cx| {
6547 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6548 if !match_ranges.is_empty() {
6549 editor.highlight_background::<SelectedTextHighlight>(
6550 &match_ranges,
6551 |theme| theme.editor_document_highlight_bracket_background,
6552 cx,
6553 )
6554 }
6555 })
6556 .log_err();
6557 })
6558 }
6559
6560 fn refresh_selected_text_highlights(
6561 &mut self,
6562 on_buffer_edit: bool,
6563 window: &mut Window,
6564 cx: &mut Context<Editor>,
6565 ) {
6566 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6567 else {
6568 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6569 self.quick_selection_highlight_task.take();
6570 self.debounced_selection_highlight_task.take();
6571 return;
6572 };
6573 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6574 if on_buffer_edit
6575 || self
6576 .quick_selection_highlight_task
6577 .as_ref()
6578 .map_or(true, |(prev_anchor_range, _)| {
6579 prev_anchor_range != &query_range
6580 })
6581 {
6582 let multi_buffer_visible_start = self
6583 .scroll_manager
6584 .anchor()
6585 .anchor
6586 .to_point(&multi_buffer_snapshot);
6587 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6588 multi_buffer_visible_start
6589 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6590 Bias::Left,
6591 );
6592 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6593 self.quick_selection_highlight_task = Some((
6594 query_range.clone(),
6595 self.update_selection_occurrence_highlights(
6596 query_text.clone(),
6597 query_range.clone(),
6598 multi_buffer_visible_range,
6599 false,
6600 window,
6601 cx,
6602 ),
6603 ));
6604 }
6605 if on_buffer_edit
6606 || self
6607 .debounced_selection_highlight_task
6608 .as_ref()
6609 .map_or(true, |(prev_anchor_range, _)| {
6610 prev_anchor_range != &query_range
6611 })
6612 {
6613 let multi_buffer_start = multi_buffer_snapshot
6614 .anchor_before(0)
6615 .to_point(&multi_buffer_snapshot);
6616 let multi_buffer_end = multi_buffer_snapshot
6617 .anchor_after(multi_buffer_snapshot.len())
6618 .to_point(&multi_buffer_snapshot);
6619 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6620 self.debounced_selection_highlight_task = Some((
6621 query_range.clone(),
6622 self.update_selection_occurrence_highlights(
6623 query_text,
6624 query_range,
6625 multi_buffer_full_range,
6626 true,
6627 window,
6628 cx,
6629 ),
6630 ));
6631 }
6632 }
6633
6634 pub fn refresh_inline_completion(
6635 &mut self,
6636 debounce: bool,
6637 user_requested: bool,
6638 window: &mut Window,
6639 cx: &mut Context<Self>,
6640 ) -> Option<()> {
6641 let provider = self.edit_prediction_provider()?;
6642 let cursor = self.selections.newest_anchor().head();
6643 let (buffer, cursor_buffer_position) =
6644 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6645
6646 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6647 self.discard_inline_completion(false, cx);
6648 return None;
6649 }
6650
6651 if !user_requested
6652 && (!self.should_show_edit_predictions()
6653 || !self.is_focused(window)
6654 || buffer.read(cx).is_empty())
6655 {
6656 self.discard_inline_completion(false, cx);
6657 return None;
6658 }
6659
6660 self.update_visible_inline_completion(window, cx);
6661 provider.refresh(
6662 self.project.clone(),
6663 buffer,
6664 cursor_buffer_position,
6665 debounce,
6666 cx,
6667 );
6668 Some(())
6669 }
6670
6671 fn show_edit_predictions_in_menu(&self) -> bool {
6672 match self.edit_prediction_settings {
6673 EditPredictionSettings::Disabled => false,
6674 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6675 }
6676 }
6677
6678 pub fn edit_predictions_enabled(&self) -> bool {
6679 match self.edit_prediction_settings {
6680 EditPredictionSettings::Disabled => false,
6681 EditPredictionSettings::Enabled { .. } => true,
6682 }
6683 }
6684
6685 fn edit_prediction_requires_modifier(&self) -> bool {
6686 match self.edit_prediction_settings {
6687 EditPredictionSettings::Disabled => false,
6688 EditPredictionSettings::Enabled {
6689 preview_requires_modifier,
6690 ..
6691 } => preview_requires_modifier,
6692 }
6693 }
6694
6695 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6696 if self.edit_prediction_provider.is_none() {
6697 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6698 } else {
6699 let selection = self.selections.newest_anchor();
6700 let cursor = selection.head();
6701
6702 if let Some((buffer, cursor_buffer_position)) =
6703 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6704 {
6705 self.edit_prediction_settings =
6706 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6707 }
6708 }
6709 }
6710
6711 fn edit_prediction_settings_at_position(
6712 &self,
6713 buffer: &Entity<Buffer>,
6714 buffer_position: language::Anchor,
6715 cx: &App,
6716 ) -> EditPredictionSettings {
6717 if !self.mode.is_full()
6718 || !self.show_inline_completions_override.unwrap_or(true)
6719 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6720 {
6721 return EditPredictionSettings::Disabled;
6722 }
6723
6724 let buffer = buffer.read(cx);
6725
6726 let file = buffer.file();
6727
6728 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6729 return EditPredictionSettings::Disabled;
6730 };
6731
6732 let by_provider = matches!(
6733 self.menu_inline_completions_policy,
6734 MenuInlineCompletionsPolicy::ByProvider
6735 );
6736
6737 let show_in_menu = by_provider
6738 && self
6739 .edit_prediction_provider
6740 .as_ref()
6741 .map_or(false, |provider| {
6742 provider.provider.show_completions_in_menu()
6743 });
6744
6745 let preview_requires_modifier =
6746 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6747
6748 EditPredictionSettings::Enabled {
6749 show_in_menu,
6750 preview_requires_modifier,
6751 }
6752 }
6753
6754 fn should_show_edit_predictions(&self) -> bool {
6755 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6756 }
6757
6758 pub fn edit_prediction_preview_is_active(&self) -> bool {
6759 matches!(
6760 self.edit_prediction_preview,
6761 EditPredictionPreview::Active { .. }
6762 )
6763 }
6764
6765 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6766 let cursor = self.selections.newest_anchor().head();
6767 if let Some((buffer, cursor_position)) =
6768 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6769 {
6770 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6771 } else {
6772 false
6773 }
6774 }
6775
6776 pub fn supports_minimap(&self, cx: &App) -> bool {
6777 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6778 }
6779
6780 fn edit_predictions_enabled_in_buffer(
6781 &self,
6782 buffer: &Entity<Buffer>,
6783 buffer_position: language::Anchor,
6784 cx: &App,
6785 ) -> bool {
6786 maybe!({
6787 if self.read_only(cx) {
6788 return Some(false);
6789 }
6790 let provider = self.edit_prediction_provider()?;
6791 if !provider.is_enabled(&buffer, buffer_position, cx) {
6792 return Some(false);
6793 }
6794 let buffer = buffer.read(cx);
6795 let Some(file) = buffer.file() else {
6796 return Some(true);
6797 };
6798 let settings = all_language_settings(Some(file), cx);
6799 Some(settings.edit_predictions_enabled_for_file(file, cx))
6800 })
6801 .unwrap_or(false)
6802 }
6803
6804 fn cycle_inline_completion(
6805 &mut self,
6806 direction: Direction,
6807 window: &mut Window,
6808 cx: &mut Context<Self>,
6809 ) -> Option<()> {
6810 let provider = self.edit_prediction_provider()?;
6811 let cursor = self.selections.newest_anchor().head();
6812 let (buffer, cursor_buffer_position) =
6813 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6814 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6815 return None;
6816 }
6817
6818 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6819 self.update_visible_inline_completion(window, cx);
6820
6821 Some(())
6822 }
6823
6824 pub fn show_inline_completion(
6825 &mut self,
6826 _: &ShowEditPrediction,
6827 window: &mut Window,
6828 cx: &mut Context<Self>,
6829 ) {
6830 if !self.has_active_inline_completion() {
6831 self.refresh_inline_completion(false, true, window, cx);
6832 return;
6833 }
6834
6835 self.update_visible_inline_completion(window, cx);
6836 }
6837
6838 pub fn display_cursor_names(
6839 &mut self,
6840 _: &DisplayCursorNames,
6841 window: &mut Window,
6842 cx: &mut Context<Self>,
6843 ) {
6844 self.show_cursor_names(window, cx);
6845 }
6846
6847 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6848 self.show_cursor_names = true;
6849 cx.notify();
6850 cx.spawn_in(window, async move |this, cx| {
6851 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6852 this.update(cx, |this, cx| {
6853 this.show_cursor_names = false;
6854 cx.notify()
6855 })
6856 .ok()
6857 })
6858 .detach();
6859 }
6860
6861 pub fn next_edit_prediction(
6862 &mut self,
6863 _: &NextEditPrediction,
6864 window: &mut Window,
6865 cx: &mut Context<Self>,
6866 ) {
6867 if self.has_active_inline_completion() {
6868 self.cycle_inline_completion(Direction::Next, window, cx);
6869 } else {
6870 let is_copilot_disabled = self
6871 .refresh_inline_completion(false, true, window, cx)
6872 .is_none();
6873 if is_copilot_disabled {
6874 cx.propagate();
6875 }
6876 }
6877 }
6878
6879 pub fn previous_edit_prediction(
6880 &mut self,
6881 _: &PreviousEditPrediction,
6882 window: &mut Window,
6883 cx: &mut Context<Self>,
6884 ) {
6885 if self.has_active_inline_completion() {
6886 self.cycle_inline_completion(Direction::Prev, window, cx);
6887 } else {
6888 let is_copilot_disabled = self
6889 .refresh_inline_completion(false, true, window, cx)
6890 .is_none();
6891 if is_copilot_disabled {
6892 cx.propagate();
6893 }
6894 }
6895 }
6896
6897 pub fn accept_edit_prediction(
6898 &mut self,
6899 _: &AcceptEditPrediction,
6900 window: &mut Window,
6901 cx: &mut Context<Self>,
6902 ) {
6903 if self.show_edit_predictions_in_menu() {
6904 self.hide_context_menu(window, cx);
6905 }
6906
6907 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6908 return;
6909 };
6910
6911 self.report_inline_completion_event(
6912 active_inline_completion.completion_id.clone(),
6913 true,
6914 cx,
6915 );
6916
6917 match &active_inline_completion.completion {
6918 InlineCompletion::Move { target, .. } => {
6919 let target = *target;
6920
6921 if let Some(position_map) = &self.last_position_map {
6922 if position_map
6923 .visible_row_range
6924 .contains(&target.to_display_point(&position_map.snapshot).row())
6925 || !self.edit_prediction_requires_modifier()
6926 {
6927 self.unfold_ranges(&[target..target], true, false, cx);
6928 // Note that this is also done in vim's handler of the Tab action.
6929 self.change_selections(
6930 Some(Autoscroll::newest()),
6931 window,
6932 cx,
6933 |selections| {
6934 selections.select_anchor_ranges([target..target]);
6935 },
6936 );
6937 self.clear_row_highlights::<EditPredictionPreview>();
6938
6939 self.edit_prediction_preview
6940 .set_previous_scroll_position(None);
6941 } else {
6942 self.edit_prediction_preview
6943 .set_previous_scroll_position(Some(
6944 position_map.snapshot.scroll_anchor,
6945 ));
6946
6947 self.highlight_rows::<EditPredictionPreview>(
6948 target..target,
6949 cx.theme().colors().editor_highlighted_line_background,
6950 RowHighlightOptions {
6951 autoscroll: true,
6952 ..Default::default()
6953 },
6954 cx,
6955 );
6956 self.request_autoscroll(Autoscroll::fit(), cx);
6957 }
6958 }
6959 }
6960 InlineCompletion::Edit { edits, .. } => {
6961 if let Some(provider) = self.edit_prediction_provider() {
6962 provider.accept(cx);
6963 }
6964
6965 // Store the transaction ID and selections before applying the edit
6966 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6967
6968 let snapshot = self.buffer.read(cx).snapshot(cx);
6969 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6970
6971 self.buffer.update(cx, |buffer, cx| {
6972 buffer.edit(edits.iter().cloned(), None, cx)
6973 });
6974
6975 self.change_selections(None, window, cx, |s| {
6976 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6977 });
6978
6979 let selections = self.selections.disjoint_anchors();
6980 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6981 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6982 if has_new_transaction {
6983 self.selection_history
6984 .insert_transaction(transaction_id_now, selections);
6985 }
6986 }
6987
6988 self.update_visible_inline_completion(window, cx);
6989 if self.active_inline_completion.is_none() {
6990 self.refresh_inline_completion(true, true, window, cx);
6991 }
6992
6993 cx.notify();
6994 }
6995 }
6996
6997 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6998 }
6999
7000 pub fn accept_partial_inline_completion(
7001 &mut self,
7002 _: &AcceptPartialEditPrediction,
7003 window: &mut Window,
7004 cx: &mut Context<Self>,
7005 ) {
7006 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7007 return;
7008 };
7009 if self.selections.count() != 1 {
7010 return;
7011 }
7012
7013 self.report_inline_completion_event(
7014 active_inline_completion.completion_id.clone(),
7015 true,
7016 cx,
7017 );
7018
7019 match &active_inline_completion.completion {
7020 InlineCompletion::Move { target, .. } => {
7021 let target = *target;
7022 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7023 selections.select_anchor_ranges([target..target]);
7024 });
7025 }
7026 InlineCompletion::Edit { edits, .. } => {
7027 // Find an insertion that starts at the cursor position.
7028 let snapshot = self.buffer.read(cx).snapshot(cx);
7029 let cursor_offset = self.selections.newest::<usize>(cx).head();
7030 let insertion = edits.iter().find_map(|(range, text)| {
7031 let range = range.to_offset(&snapshot);
7032 if range.is_empty() && range.start == cursor_offset {
7033 Some(text)
7034 } else {
7035 None
7036 }
7037 });
7038
7039 if let Some(text) = insertion {
7040 let mut partial_completion = text
7041 .chars()
7042 .by_ref()
7043 .take_while(|c| c.is_alphabetic())
7044 .collect::<String>();
7045 if partial_completion.is_empty() {
7046 partial_completion = text
7047 .chars()
7048 .by_ref()
7049 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7050 .collect::<String>();
7051 }
7052
7053 cx.emit(EditorEvent::InputHandled {
7054 utf16_range_to_replace: None,
7055 text: partial_completion.clone().into(),
7056 });
7057
7058 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7059
7060 self.refresh_inline_completion(true, true, window, cx);
7061 cx.notify();
7062 } else {
7063 self.accept_edit_prediction(&Default::default(), window, cx);
7064 }
7065 }
7066 }
7067 }
7068
7069 fn discard_inline_completion(
7070 &mut self,
7071 should_report_inline_completion_event: bool,
7072 cx: &mut Context<Self>,
7073 ) -> bool {
7074 if should_report_inline_completion_event {
7075 let completion_id = self
7076 .active_inline_completion
7077 .as_ref()
7078 .and_then(|active_completion| active_completion.completion_id.clone());
7079
7080 self.report_inline_completion_event(completion_id, false, cx);
7081 }
7082
7083 if let Some(provider) = self.edit_prediction_provider() {
7084 provider.discard(cx);
7085 }
7086
7087 self.take_active_inline_completion(cx)
7088 }
7089
7090 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7091 let Some(provider) = self.edit_prediction_provider() else {
7092 return;
7093 };
7094
7095 let Some((_, buffer, _)) = self
7096 .buffer
7097 .read(cx)
7098 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7099 else {
7100 return;
7101 };
7102
7103 let extension = buffer
7104 .read(cx)
7105 .file()
7106 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7107
7108 let event_type = match accepted {
7109 true => "Edit Prediction Accepted",
7110 false => "Edit Prediction Discarded",
7111 };
7112 telemetry::event!(
7113 event_type,
7114 provider = provider.name(),
7115 prediction_id = id,
7116 suggestion_accepted = accepted,
7117 file_extension = extension,
7118 );
7119 }
7120
7121 pub fn has_active_inline_completion(&self) -> bool {
7122 self.active_inline_completion.is_some()
7123 }
7124
7125 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7126 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7127 return false;
7128 };
7129
7130 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7131 self.clear_highlights::<InlineCompletionHighlight>(cx);
7132 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7133 true
7134 }
7135
7136 /// Returns true when we're displaying the edit prediction popover below the cursor
7137 /// like we are not previewing and the LSP autocomplete menu is visible
7138 /// or we are in `when_holding_modifier` mode.
7139 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7140 if self.edit_prediction_preview_is_active()
7141 || !self.show_edit_predictions_in_menu()
7142 || !self.edit_predictions_enabled()
7143 {
7144 return false;
7145 }
7146
7147 if self.has_visible_completions_menu() {
7148 return true;
7149 }
7150
7151 has_completion && self.edit_prediction_requires_modifier()
7152 }
7153
7154 fn handle_modifiers_changed(
7155 &mut self,
7156 modifiers: Modifiers,
7157 position_map: &PositionMap,
7158 window: &mut Window,
7159 cx: &mut Context<Self>,
7160 ) {
7161 if self.show_edit_predictions_in_menu() {
7162 self.update_edit_prediction_preview(&modifiers, window, cx);
7163 }
7164
7165 self.update_selection_mode(&modifiers, position_map, window, cx);
7166
7167 let mouse_position = window.mouse_position();
7168 if !position_map.text_hitbox.is_hovered(window) {
7169 return;
7170 }
7171
7172 self.update_hovered_link(
7173 position_map.point_for_position(mouse_position),
7174 &position_map.snapshot,
7175 modifiers,
7176 window,
7177 cx,
7178 )
7179 }
7180
7181 fn multi_cursor_modifier(
7182 cursor_event: bool,
7183 modifiers: &Modifiers,
7184 cx: &mut Context<Self>,
7185 ) -> bool {
7186 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7187 if cursor_event {
7188 match multi_cursor_setting {
7189 MultiCursorModifier::Alt => modifiers.alt,
7190 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7191 }
7192 } else {
7193 match multi_cursor_setting {
7194 MultiCursorModifier::Alt => modifiers.secondary(),
7195 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7196 }
7197 }
7198 }
7199
7200 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7201 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7202 }
7203
7204 fn update_selection_mode(
7205 &mut self,
7206 modifiers: &Modifiers,
7207 position_map: &PositionMap,
7208 window: &mut Window,
7209 cx: &mut Context<Self>,
7210 ) {
7211 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7212 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7213 || self.selections.pending.is_none()
7214 {
7215 return;
7216 }
7217
7218 let mouse_position = window.mouse_position();
7219 let point_for_position = position_map.point_for_position(mouse_position);
7220 let position = point_for_position.previous_valid;
7221
7222 self.select(
7223 SelectPhase::BeginColumnar {
7224 position,
7225 reset: false,
7226 goal_column: point_for_position.exact_unclipped.column(),
7227 },
7228 window,
7229 cx,
7230 );
7231 }
7232
7233 fn update_edit_prediction_preview(
7234 &mut self,
7235 modifiers: &Modifiers,
7236 window: &mut Window,
7237 cx: &mut Context<Self>,
7238 ) {
7239 let mut modifiers_held = false;
7240 if let Some(accept_keystroke) = self
7241 .accept_edit_prediction_keybind(false, window, cx)
7242 .keystroke()
7243 {
7244 modifiers_held = modifiers_held
7245 || (&accept_keystroke.modifiers == modifiers
7246 && accept_keystroke.modifiers.modified());
7247 };
7248 if let Some(accept_partial_keystroke) = self
7249 .accept_edit_prediction_keybind(true, window, cx)
7250 .keystroke()
7251 {
7252 modifiers_held = modifiers_held
7253 || (&accept_partial_keystroke.modifiers == modifiers
7254 && accept_partial_keystroke.modifiers.modified());
7255 }
7256
7257 if modifiers_held {
7258 if matches!(
7259 self.edit_prediction_preview,
7260 EditPredictionPreview::Inactive { .. }
7261 ) {
7262 self.edit_prediction_preview = EditPredictionPreview::Active {
7263 previous_scroll_position: None,
7264 since: Instant::now(),
7265 };
7266
7267 self.update_visible_inline_completion(window, cx);
7268 cx.notify();
7269 }
7270 } else if let EditPredictionPreview::Active {
7271 previous_scroll_position,
7272 since,
7273 } = self.edit_prediction_preview
7274 {
7275 if let (Some(previous_scroll_position), Some(position_map)) =
7276 (previous_scroll_position, self.last_position_map.as_ref())
7277 {
7278 self.set_scroll_position(
7279 previous_scroll_position
7280 .scroll_position(&position_map.snapshot.display_snapshot),
7281 window,
7282 cx,
7283 );
7284 }
7285
7286 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7287 released_too_fast: since.elapsed() < Duration::from_millis(200),
7288 };
7289 self.clear_row_highlights::<EditPredictionPreview>();
7290 self.update_visible_inline_completion(window, cx);
7291 cx.notify();
7292 }
7293 }
7294
7295 fn update_visible_inline_completion(
7296 &mut self,
7297 _window: &mut Window,
7298 cx: &mut Context<Self>,
7299 ) -> Option<()> {
7300 let selection = self.selections.newest_anchor();
7301 let cursor = selection.head();
7302 let multibuffer = self.buffer.read(cx).snapshot(cx);
7303 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7304 let excerpt_id = cursor.excerpt_id;
7305
7306 let show_in_menu = self.show_edit_predictions_in_menu();
7307 let completions_menu_has_precedence = !show_in_menu
7308 && (self.context_menu.borrow().is_some()
7309 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7310
7311 if completions_menu_has_precedence
7312 || !offset_selection.is_empty()
7313 || self
7314 .active_inline_completion
7315 .as_ref()
7316 .map_or(false, |completion| {
7317 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7318 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7319 !invalidation_range.contains(&offset_selection.head())
7320 })
7321 {
7322 self.discard_inline_completion(false, cx);
7323 return None;
7324 }
7325
7326 self.take_active_inline_completion(cx);
7327 let Some(provider) = self.edit_prediction_provider() else {
7328 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7329 return None;
7330 };
7331
7332 let (buffer, cursor_buffer_position) =
7333 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7334
7335 self.edit_prediction_settings =
7336 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7337
7338 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7339
7340 if self.edit_prediction_indent_conflict {
7341 let cursor_point = cursor.to_point(&multibuffer);
7342
7343 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7344
7345 if let Some((_, indent)) = indents.iter().next() {
7346 if indent.len == cursor_point.column {
7347 self.edit_prediction_indent_conflict = false;
7348 }
7349 }
7350 }
7351
7352 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7353 let edits = inline_completion
7354 .edits
7355 .into_iter()
7356 .flat_map(|(range, new_text)| {
7357 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7358 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7359 Some((start..end, new_text))
7360 })
7361 .collect::<Vec<_>>();
7362 if edits.is_empty() {
7363 return None;
7364 }
7365
7366 let first_edit_start = edits.first().unwrap().0.start;
7367 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7368 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7369
7370 let last_edit_end = edits.last().unwrap().0.end;
7371 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7372 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7373
7374 let cursor_row = cursor.to_point(&multibuffer).row;
7375
7376 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7377
7378 let mut inlay_ids = Vec::new();
7379 let invalidation_row_range;
7380 let move_invalidation_row_range = if cursor_row < edit_start_row {
7381 Some(cursor_row..edit_end_row)
7382 } else if cursor_row > edit_end_row {
7383 Some(edit_start_row..cursor_row)
7384 } else {
7385 None
7386 };
7387 let is_move =
7388 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7389 let completion = if is_move {
7390 invalidation_row_range =
7391 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7392 let target = first_edit_start;
7393 InlineCompletion::Move { target, snapshot }
7394 } else {
7395 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7396 && !self.inline_completions_hidden_for_vim_mode;
7397
7398 if show_completions_in_buffer {
7399 if edits
7400 .iter()
7401 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7402 {
7403 let mut inlays = Vec::new();
7404 for (range, new_text) in &edits {
7405 let inlay = Inlay::inline_completion(
7406 post_inc(&mut self.next_inlay_id),
7407 range.start,
7408 new_text.as_str(),
7409 );
7410 inlay_ids.push(inlay.id);
7411 inlays.push(inlay);
7412 }
7413
7414 self.splice_inlays(&[], inlays, cx);
7415 } else {
7416 let background_color = cx.theme().status().deleted_background;
7417 self.highlight_text::<InlineCompletionHighlight>(
7418 edits.iter().map(|(range, _)| range.clone()).collect(),
7419 HighlightStyle {
7420 background_color: Some(background_color),
7421 ..Default::default()
7422 },
7423 cx,
7424 );
7425 }
7426 }
7427
7428 invalidation_row_range = edit_start_row..edit_end_row;
7429
7430 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7431 if provider.show_tab_accept_marker() {
7432 EditDisplayMode::TabAccept
7433 } else {
7434 EditDisplayMode::Inline
7435 }
7436 } else {
7437 EditDisplayMode::DiffPopover
7438 };
7439
7440 InlineCompletion::Edit {
7441 edits,
7442 edit_preview: inline_completion.edit_preview,
7443 display_mode,
7444 snapshot,
7445 }
7446 };
7447
7448 let invalidation_range = multibuffer
7449 .anchor_before(Point::new(invalidation_row_range.start, 0))
7450 ..multibuffer.anchor_after(Point::new(
7451 invalidation_row_range.end,
7452 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7453 ));
7454
7455 self.stale_inline_completion_in_menu = None;
7456 self.active_inline_completion = Some(InlineCompletionState {
7457 inlay_ids,
7458 completion,
7459 completion_id: inline_completion.id,
7460 invalidation_range,
7461 });
7462
7463 cx.notify();
7464
7465 Some(())
7466 }
7467
7468 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7469 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7470 }
7471
7472 fn clear_tasks(&mut self) {
7473 self.tasks.clear()
7474 }
7475
7476 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7477 if self.tasks.insert(key, value).is_some() {
7478 // This case should hopefully be rare, but just in case...
7479 log::error!(
7480 "multiple different run targets found on a single line, only the last target will be rendered"
7481 )
7482 }
7483 }
7484
7485 /// Get all display points of breakpoints that will be rendered within editor
7486 ///
7487 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7488 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7489 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7490 fn active_breakpoints(
7491 &self,
7492 range: Range<DisplayRow>,
7493 window: &mut Window,
7494 cx: &mut Context<Self>,
7495 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7496 let mut breakpoint_display_points = HashMap::default();
7497
7498 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7499 return breakpoint_display_points;
7500 };
7501
7502 let snapshot = self.snapshot(window, cx);
7503
7504 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7505 let Some(project) = self.project.as_ref() else {
7506 return breakpoint_display_points;
7507 };
7508
7509 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7510 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7511
7512 for (buffer_snapshot, range, excerpt_id) in
7513 multi_buffer_snapshot.range_to_buffer_ranges(range)
7514 {
7515 let Some(buffer) = project
7516 .read(cx)
7517 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7518 else {
7519 continue;
7520 };
7521 let breakpoints = breakpoint_store.read(cx).breakpoints(
7522 &buffer,
7523 Some(
7524 buffer_snapshot.anchor_before(range.start)
7525 ..buffer_snapshot.anchor_after(range.end),
7526 ),
7527 buffer_snapshot,
7528 cx,
7529 );
7530 for (breakpoint, state) in breakpoints {
7531 let multi_buffer_anchor =
7532 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7533 let position = multi_buffer_anchor
7534 .to_point(&multi_buffer_snapshot)
7535 .to_display_point(&snapshot);
7536
7537 breakpoint_display_points.insert(
7538 position.row(),
7539 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7540 );
7541 }
7542 }
7543
7544 breakpoint_display_points
7545 }
7546
7547 fn breakpoint_context_menu(
7548 &self,
7549 anchor: Anchor,
7550 window: &mut Window,
7551 cx: &mut Context<Self>,
7552 ) -> Entity<ui::ContextMenu> {
7553 let weak_editor = cx.weak_entity();
7554 let focus_handle = self.focus_handle(cx);
7555
7556 let row = self
7557 .buffer
7558 .read(cx)
7559 .snapshot(cx)
7560 .summary_for_anchor::<Point>(&anchor)
7561 .row;
7562
7563 let breakpoint = self
7564 .breakpoint_at_row(row, window, cx)
7565 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7566
7567 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7568 "Edit Log Breakpoint"
7569 } else {
7570 "Set Log Breakpoint"
7571 };
7572
7573 let condition_breakpoint_msg = if breakpoint
7574 .as_ref()
7575 .is_some_and(|bp| bp.1.condition.is_some())
7576 {
7577 "Edit Condition Breakpoint"
7578 } else {
7579 "Set Condition Breakpoint"
7580 };
7581
7582 let hit_condition_breakpoint_msg = if breakpoint
7583 .as_ref()
7584 .is_some_and(|bp| bp.1.hit_condition.is_some())
7585 {
7586 "Edit Hit Condition Breakpoint"
7587 } else {
7588 "Set Hit Condition Breakpoint"
7589 };
7590
7591 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7592 "Unset Breakpoint"
7593 } else {
7594 "Set Breakpoint"
7595 };
7596
7597 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7598
7599 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7600 BreakpointState::Enabled => Some("Disable"),
7601 BreakpointState::Disabled => Some("Enable"),
7602 });
7603
7604 let (anchor, breakpoint) =
7605 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7606
7607 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7608 menu.on_blur_subscription(Subscription::new(|| {}))
7609 .context(focus_handle)
7610 .when(run_to_cursor, |this| {
7611 let weak_editor = weak_editor.clone();
7612 this.entry("Run to cursor", None, move |window, cx| {
7613 weak_editor
7614 .update(cx, |editor, cx| {
7615 editor.change_selections(None, window, cx, |s| {
7616 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7617 });
7618 })
7619 .ok();
7620
7621 window.dispatch_action(Box::new(RunToCursor), cx);
7622 })
7623 .separator()
7624 })
7625 .when_some(toggle_state_msg, |this, msg| {
7626 this.entry(msg, None, {
7627 let weak_editor = weak_editor.clone();
7628 let breakpoint = breakpoint.clone();
7629 move |_window, cx| {
7630 weak_editor
7631 .update(cx, |this, cx| {
7632 this.edit_breakpoint_at_anchor(
7633 anchor,
7634 breakpoint.as_ref().clone(),
7635 BreakpointEditAction::InvertState,
7636 cx,
7637 );
7638 })
7639 .log_err();
7640 }
7641 })
7642 })
7643 .entry(set_breakpoint_msg, None, {
7644 let weak_editor = weak_editor.clone();
7645 let breakpoint = breakpoint.clone();
7646 move |_window, cx| {
7647 weak_editor
7648 .update(cx, |this, cx| {
7649 this.edit_breakpoint_at_anchor(
7650 anchor,
7651 breakpoint.as_ref().clone(),
7652 BreakpointEditAction::Toggle,
7653 cx,
7654 );
7655 })
7656 .log_err();
7657 }
7658 })
7659 .entry(log_breakpoint_msg, None, {
7660 let breakpoint = breakpoint.clone();
7661 let weak_editor = weak_editor.clone();
7662 move |window, cx| {
7663 weak_editor
7664 .update(cx, |this, cx| {
7665 this.add_edit_breakpoint_block(
7666 anchor,
7667 breakpoint.as_ref(),
7668 BreakpointPromptEditAction::Log,
7669 window,
7670 cx,
7671 );
7672 })
7673 .log_err();
7674 }
7675 })
7676 .entry(condition_breakpoint_msg, None, {
7677 let breakpoint = breakpoint.clone();
7678 let weak_editor = weak_editor.clone();
7679 move |window, cx| {
7680 weak_editor
7681 .update(cx, |this, cx| {
7682 this.add_edit_breakpoint_block(
7683 anchor,
7684 breakpoint.as_ref(),
7685 BreakpointPromptEditAction::Condition,
7686 window,
7687 cx,
7688 );
7689 })
7690 .log_err();
7691 }
7692 })
7693 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7694 weak_editor
7695 .update(cx, |this, cx| {
7696 this.add_edit_breakpoint_block(
7697 anchor,
7698 breakpoint.as_ref(),
7699 BreakpointPromptEditAction::HitCondition,
7700 window,
7701 cx,
7702 );
7703 })
7704 .log_err();
7705 })
7706 })
7707 }
7708
7709 fn render_breakpoint(
7710 &self,
7711 position: Anchor,
7712 row: DisplayRow,
7713 breakpoint: &Breakpoint,
7714 state: Option<BreakpointSessionState>,
7715 cx: &mut Context<Self>,
7716 ) -> IconButton {
7717 let is_rejected = state.is_some_and(|s| !s.verified);
7718 // Is it a breakpoint that shows up when hovering over gutter?
7719 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7720 (false, false),
7721 |PhantomBreakpointIndicator {
7722 is_active,
7723 display_row,
7724 collides_with_existing_breakpoint,
7725 }| {
7726 (
7727 is_active && display_row == row,
7728 collides_with_existing_breakpoint,
7729 )
7730 },
7731 );
7732
7733 let (color, icon) = {
7734 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7735 (false, false) => ui::IconName::DebugBreakpoint,
7736 (true, false) => ui::IconName::DebugLogBreakpoint,
7737 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7738 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7739 };
7740
7741 let color = if is_phantom {
7742 Color::Hint
7743 } else if is_rejected {
7744 Color::Disabled
7745 } else {
7746 Color::Debugger
7747 };
7748
7749 (color, icon)
7750 };
7751
7752 let breakpoint = Arc::from(breakpoint.clone());
7753
7754 let alt_as_text = gpui::Keystroke {
7755 modifiers: Modifiers::secondary_key(),
7756 ..Default::default()
7757 };
7758 let primary_action_text = if breakpoint.is_disabled() {
7759 "Enable breakpoint"
7760 } else if is_phantom && !collides_with_existing {
7761 "Set breakpoint"
7762 } else {
7763 "Unset breakpoint"
7764 };
7765 let focus_handle = self.focus_handle.clone();
7766
7767 let meta = if is_rejected {
7768 SharedString::from("No executable code is associated with this line.")
7769 } else if collides_with_existing && !breakpoint.is_disabled() {
7770 SharedString::from(format!(
7771 "{alt_as_text}-click to disable,\nright-click for more options."
7772 ))
7773 } else {
7774 SharedString::from("Right-click for more options.")
7775 };
7776 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7777 .icon_size(IconSize::XSmall)
7778 .size(ui::ButtonSize::None)
7779 .when(is_rejected, |this| {
7780 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7781 })
7782 .icon_color(color)
7783 .style(ButtonStyle::Transparent)
7784 .on_click(cx.listener({
7785 let breakpoint = breakpoint.clone();
7786
7787 move |editor, event: &ClickEvent, window, cx| {
7788 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7789 BreakpointEditAction::InvertState
7790 } else {
7791 BreakpointEditAction::Toggle
7792 };
7793
7794 window.focus(&editor.focus_handle(cx));
7795 editor.edit_breakpoint_at_anchor(
7796 position,
7797 breakpoint.as_ref().clone(),
7798 edit_action,
7799 cx,
7800 );
7801 }
7802 }))
7803 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7804 editor.set_breakpoint_context_menu(
7805 row,
7806 Some(position),
7807 event.down.position,
7808 window,
7809 cx,
7810 );
7811 }))
7812 .tooltip(move |window, cx| {
7813 Tooltip::with_meta_in(
7814 primary_action_text,
7815 Some(&ToggleBreakpoint),
7816 meta.clone(),
7817 &focus_handle,
7818 window,
7819 cx,
7820 )
7821 })
7822 }
7823
7824 fn build_tasks_context(
7825 project: &Entity<Project>,
7826 buffer: &Entity<Buffer>,
7827 buffer_row: u32,
7828 tasks: &Arc<RunnableTasks>,
7829 cx: &mut Context<Self>,
7830 ) -> Task<Option<task::TaskContext>> {
7831 let position = Point::new(buffer_row, tasks.column);
7832 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7833 let location = Location {
7834 buffer: buffer.clone(),
7835 range: range_start..range_start,
7836 };
7837 // Fill in the environmental variables from the tree-sitter captures
7838 let mut captured_task_variables = TaskVariables::default();
7839 for (capture_name, value) in tasks.extra_variables.clone() {
7840 captured_task_variables.insert(
7841 task::VariableName::Custom(capture_name.into()),
7842 value.clone(),
7843 );
7844 }
7845 project.update(cx, |project, cx| {
7846 project.task_store().update(cx, |task_store, cx| {
7847 task_store.task_context_for_location(captured_task_variables, location, cx)
7848 })
7849 })
7850 }
7851
7852 pub fn spawn_nearest_task(
7853 &mut self,
7854 action: &SpawnNearestTask,
7855 window: &mut Window,
7856 cx: &mut Context<Self>,
7857 ) {
7858 let Some((workspace, _)) = self.workspace.clone() else {
7859 return;
7860 };
7861 let Some(project) = self.project.clone() else {
7862 return;
7863 };
7864
7865 // Try to find a closest, enclosing node using tree-sitter that has a
7866 // task
7867 let Some((buffer, buffer_row, tasks)) = self
7868 .find_enclosing_node_task(cx)
7869 // Or find the task that's closest in row-distance.
7870 .or_else(|| self.find_closest_task(cx))
7871 else {
7872 return;
7873 };
7874
7875 let reveal_strategy = action.reveal;
7876 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7877 cx.spawn_in(window, async move |_, cx| {
7878 let context = task_context.await?;
7879 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7880
7881 let resolved = &mut resolved_task.resolved;
7882 resolved.reveal = reveal_strategy;
7883
7884 workspace
7885 .update_in(cx, |workspace, window, cx| {
7886 workspace.schedule_resolved_task(
7887 task_source_kind,
7888 resolved_task,
7889 false,
7890 window,
7891 cx,
7892 );
7893 })
7894 .ok()
7895 })
7896 .detach();
7897 }
7898
7899 fn find_closest_task(
7900 &mut self,
7901 cx: &mut Context<Self>,
7902 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7903 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7904
7905 let ((buffer_id, row), tasks) = self
7906 .tasks
7907 .iter()
7908 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7909
7910 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7911 let tasks = Arc::new(tasks.to_owned());
7912 Some((buffer, *row, tasks))
7913 }
7914
7915 fn find_enclosing_node_task(
7916 &mut self,
7917 cx: &mut Context<Self>,
7918 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7919 let snapshot = self.buffer.read(cx).snapshot(cx);
7920 let offset = self.selections.newest::<usize>(cx).head();
7921 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7922 let buffer_id = excerpt.buffer().remote_id();
7923
7924 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7925 let mut cursor = layer.node().walk();
7926
7927 while cursor.goto_first_child_for_byte(offset).is_some() {
7928 if cursor.node().end_byte() == offset {
7929 cursor.goto_next_sibling();
7930 }
7931 }
7932
7933 // Ascend to the smallest ancestor that contains the range and has a task.
7934 loop {
7935 let node = cursor.node();
7936 let node_range = node.byte_range();
7937 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7938
7939 // Check if this node contains our offset
7940 if node_range.start <= offset && node_range.end >= offset {
7941 // If it contains offset, check for task
7942 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7943 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7944 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7945 }
7946 }
7947
7948 if !cursor.goto_parent() {
7949 break;
7950 }
7951 }
7952 None
7953 }
7954
7955 fn render_run_indicator(
7956 &self,
7957 _style: &EditorStyle,
7958 is_active: bool,
7959 row: DisplayRow,
7960 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7961 cx: &mut Context<Self>,
7962 ) -> IconButton {
7963 let color = Color::Muted;
7964 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7965
7966 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7967 .shape(ui::IconButtonShape::Square)
7968 .icon_size(IconSize::XSmall)
7969 .icon_color(color)
7970 .toggle_state(is_active)
7971 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7972 let quick_launch = e.down.button == MouseButton::Left;
7973 window.focus(&editor.focus_handle(cx));
7974 editor.toggle_code_actions(
7975 &ToggleCodeActions {
7976 deployed_from: Some(CodeActionSource::RunMenu(row)),
7977 quick_launch,
7978 },
7979 window,
7980 cx,
7981 );
7982 }))
7983 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7984 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7985 }))
7986 }
7987
7988 pub fn context_menu_visible(&self) -> bool {
7989 !self.edit_prediction_preview_is_active()
7990 && self
7991 .context_menu
7992 .borrow()
7993 .as_ref()
7994 .map_or(false, |menu| menu.visible())
7995 }
7996
7997 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7998 self.context_menu
7999 .borrow()
8000 .as_ref()
8001 .map(|menu| menu.origin())
8002 }
8003
8004 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8005 self.context_menu_options = Some(options);
8006 }
8007
8008 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8009 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8010
8011 fn render_edit_prediction_popover(
8012 &mut self,
8013 text_bounds: &Bounds<Pixels>,
8014 content_origin: gpui::Point<Pixels>,
8015 right_margin: Pixels,
8016 editor_snapshot: &EditorSnapshot,
8017 visible_row_range: Range<DisplayRow>,
8018 scroll_top: f32,
8019 scroll_bottom: f32,
8020 line_layouts: &[LineWithInvisibles],
8021 line_height: Pixels,
8022 scroll_pixel_position: gpui::Point<Pixels>,
8023 newest_selection_head: Option<DisplayPoint>,
8024 editor_width: Pixels,
8025 style: &EditorStyle,
8026 window: &mut Window,
8027 cx: &mut App,
8028 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8029 if self.mode().is_minimap() {
8030 return None;
8031 }
8032 let active_inline_completion = self.active_inline_completion.as_ref()?;
8033
8034 if self.edit_prediction_visible_in_cursor_popover(true) {
8035 return None;
8036 }
8037
8038 match &active_inline_completion.completion {
8039 InlineCompletion::Move { target, .. } => {
8040 let target_display_point = target.to_display_point(editor_snapshot);
8041
8042 if self.edit_prediction_requires_modifier() {
8043 if !self.edit_prediction_preview_is_active() {
8044 return None;
8045 }
8046
8047 self.render_edit_prediction_modifier_jump_popover(
8048 text_bounds,
8049 content_origin,
8050 visible_row_range,
8051 line_layouts,
8052 line_height,
8053 scroll_pixel_position,
8054 newest_selection_head,
8055 target_display_point,
8056 window,
8057 cx,
8058 )
8059 } else {
8060 self.render_edit_prediction_eager_jump_popover(
8061 text_bounds,
8062 content_origin,
8063 editor_snapshot,
8064 visible_row_range,
8065 scroll_top,
8066 scroll_bottom,
8067 line_height,
8068 scroll_pixel_position,
8069 target_display_point,
8070 editor_width,
8071 window,
8072 cx,
8073 )
8074 }
8075 }
8076 InlineCompletion::Edit {
8077 display_mode: EditDisplayMode::Inline,
8078 ..
8079 } => None,
8080 InlineCompletion::Edit {
8081 display_mode: EditDisplayMode::TabAccept,
8082 edits,
8083 ..
8084 } => {
8085 let range = &edits.first()?.0;
8086 let target_display_point = range.end.to_display_point(editor_snapshot);
8087
8088 self.render_edit_prediction_end_of_line_popover(
8089 "Accept",
8090 editor_snapshot,
8091 visible_row_range,
8092 target_display_point,
8093 line_height,
8094 scroll_pixel_position,
8095 content_origin,
8096 editor_width,
8097 window,
8098 cx,
8099 )
8100 }
8101 InlineCompletion::Edit {
8102 edits,
8103 edit_preview,
8104 display_mode: EditDisplayMode::DiffPopover,
8105 snapshot,
8106 } => self.render_edit_prediction_diff_popover(
8107 text_bounds,
8108 content_origin,
8109 right_margin,
8110 editor_snapshot,
8111 visible_row_range,
8112 line_layouts,
8113 line_height,
8114 scroll_pixel_position,
8115 newest_selection_head,
8116 editor_width,
8117 style,
8118 edits,
8119 edit_preview,
8120 snapshot,
8121 window,
8122 cx,
8123 ),
8124 }
8125 }
8126
8127 fn render_edit_prediction_modifier_jump_popover(
8128 &mut self,
8129 text_bounds: &Bounds<Pixels>,
8130 content_origin: gpui::Point<Pixels>,
8131 visible_row_range: Range<DisplayRow>,
8132 line_layouts: &[LineWithInvisibles],
8133 line_height: Pixels,
8134 scroll_pixel_position: gpui::Point<Pixels>,
8135 newest_selection_head: Option<DisplayPoint>,
8136 target_display_point: DisplayPoint,
8137 window: &mut Window,
8138 cx: &mut App,
8139 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8140 let scrolled_content_origin =
8141 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8142
8143 const SCROLL_PADDING_Y: Pixels = px(12.);
8144
8145 if target_display_point.row() < visible_row_range.start {
8146 return self.render_edit_prediction_scroll_popover(
8147 |_| SCROLL_PADDING_Y,
8148 IconName::ArrowUp,
8149 visible_row_range,
8150 line_layouts,
8151 newest_selection_head,
8152 scrolled_content_origin,
8153 window,
8154 cx,
8155 );
8156 } else if target_display_point.row() >= visible_row_range.end {
8157 return self.render_edit_prediction_scroll_popover(
8158 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8159 IconName::ArrowDown,
8160 visible_row_range,
8161 line_layouts,
8162 newest_selection_head,
8163 scrolled_content_origin,
8164 window,
8165 cx,
8166 );
8167 }
8168
8169 const POLE_WIDTH: Pixels = px(2.);
8170
8171 let line_layout =
8172 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8173 let target_column = target_display_point.column() as usize;
8174
8175 let target_x = line_layout.x_for_index(target_column);
8176 let target_y =
8177 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8178
8179 let flag_on_right = target_x < text_bounds.size.width / 2.;
8180
8181 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8182 border_color.l += 0.001;
8183
8184 let mut element = v_flex()
8185 .items_end()
8186 .when(flag_on_right, |el| el.items_start())
8187 .child(if flag_on_right {
8188 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8189 .rounded_bl(px(0.))
8190 .rounded_tl(px(0.))
8191 .border_l_2()
8192 .border_color(border_color)
8193 } else {
8194 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8195 .rounded_br(px(0.))
8196 .rounded_tr(px(0.))
8197 .border_r_2()
8198 .border_color(border_color)
8199 })
8200 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8201 .into_any();
8202
8203 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8204
8205 let mut origin = scrolled_content_origin + point(target_x, target_y)
8206 - point(
8207 if flag_on_right {
8208 POLE_WIDTH
8209 } else {
8210 size.width - POLE_WIDTH
8211 },
8212 size.height - line_height,
8213 );
8214
8215 origin.x = origin.x.max(content_origin.x);
8216
8217 element.prepaint_at(origin, window, cx);
8218
8219 Some((element, origin))
8220 }
8221
8222 fn render_edit_prediction_scroll_popover(
8223 &mut self,
8224 to_y: impl Fn(Size<Pixels>) -> Pixels,
8225 scroll_icon: IconName,
8226 visible_row_range: Range<DisplayRow>,
8227 line_layouts: &[LineWithInvisibles],
8228 newest_selection_head: Option<DisplayPoint>,
8229 scrolled_content_origin: gpui::Point<Pixels>,
8230 window: &mut Window,
8231 cx: &mut App,
8232 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8233 let mut element = self
8234 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8235 .into_any();
8236
8237 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8238
8239 let cursor = newest_selection_head?;
8240 let cursor_row_layout =
8241 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8242 let cursor_column = cursor.column() as usize;
8243
8244 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8245
8246 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8247
8248 element.prepaint_at(origin, window, cx);
8249 Some((element, origin))
8250 }
8251
8252 fn render_edit_prediction_eager_jump_popover(
8253 &mut self,
8254 text_bounds: &Bounds<Pixels>,
8255 content_origin: gpui::Point<Pixels>,
8256 editor_snapshot: &EditorSnapshot,
8257 visible_row_range: Range<DisplayRow>,
8258 scroll_top: f32,
8259 scroll_bottom: f32,
8260 line_height: Pixels,
8261 scroll_pixel_position: gpui::Point<Pixels>,
8262 target_display_point: DisplayPoint,
8263 editor_width: Pixels,
8264 window: &mut Window,
8265 cx: &mut App,
8266 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8267 if target_display_point.row().as_f32() < scroll_top {
8268 let mut element = self
8269 .render_edit_prediction_line_popover(
8270 "Jump to Edit",
8271 Some(IconName::ArrowUp),
8272 window,
8273 cx,
8274 )?
8275 .into_any();
8276
8277 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8278 let offset = point(
8279 (text_bounds.size.width - size.width) / 2.,
8280 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8281 );
8282
8283 let origin = text_bounds.origin + offset;
8284 element.prepaint_at(origin, window, cx);
8285 Some((element, origin))
8286 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8287 let mut element = self
8288 .render_edit_prediction_line_popover(
8289 "Jump to Edit",
8290 Some(IconName::ArrowDown),
8291 window,
8292 cx,
8293 )?
8294 .into_any();
8295
8296 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8297 let offset = point(
8298 (text_bounds.size.width - size.width) / 2.,
8299 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8300 );
8301
8302 let origin = text_bounds.origin + offset;
8303 element.prepaint_at(origin, window, cx);
8304 Some((element, origin))
8305 } else {
8306 self.render_edit_prediction_end_of_line_popover(
8307 "Jump to Edit",
8308 editor_snapshot,
8309 visible_row_range,
8310 target_display_point,
8311 line_height,
8312 scroll_pixel_position,
8313 content_origin,
8314 editor_width,
8315 window,
8316 cx,
8317 )
8318 }
8319 }
8320
8321 fn render_edit_prediction_end_of_line_popover(
8322 self: &mut Editor,
8323 label: &'static str,
8324 editor_snapshot: &EditorSnapshot,
8325 visible_row_range: Range<DisplayRow>,
8326 target_display_point: DisplayPoint,
8327 line_height: Pixels,
8328 scroll_pixel_position: gpui::Point<Pixels>,
8329 content_origin: gpui::Point<Pixels>,
8330 editor_width: Pixels,
8331 window: &mut Window,
8332 cx: &mut App,
8333 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8334 let target_line_end = DisplayPoint::new(
8335 target_display_point.row(),
8336 editor_snapshot.line_len(target_display_point.row()),
8337 );
8338
8339 let mut element = self
8340 .render_edit_prediction_line_popover(label, None, window, cx)?
8341 .into_any();
8342
8343 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8344
8345 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8346
8347 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8348 let mut origin = start_point
8349 + line_origin
8350 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8351 origin.x = origin.x.max(content_origin.x);
8352
8353 let max_x = content_origin.x + editor_width - size.width;
8354
8355 if origin.x > max_x {
8356 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8357
8358 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8359 origin.y += offset;
8360 IconName::ArrowUp
8361 } else {
8362 origin.y -= offset;
8363 IconName::ArrowDown
8364 };
8365
8366 element = self
8367 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8368 .into_any();
8369
8370 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8371
8372 origin.x = content_origin.x + editor_width - size.width - px(2.);
8373 }
8374
8375 element.prepaint_at(origin, window, cx);
8376 Some((element, origin))
8377 }
8378
8379 fn render_edit_prediction_diff_popover(
8380 self: &Editor,
8381 text_bounds: &Bounds<Pixels>,
8382 content_origin: gpui::Point<Pixels>,
8383 right_margin: Pixels,
8384 editor_snapshot: &EditorSnapshot,
8385 visible_row_range: Range<DisplayRow>,
8386 line_layouts: &[LineWithInvisibles],
8387 line_height: Pixels,
8388 scroll_pixel_position: gpui::Point<Pixels>,
8389 newest_selection_head: Option<DisplayPoint>,
8390 editor_width: Pixels,
8391 style: &EditorStyle,
8392 edits: &Vec<(Range<Anchor>, String)>,
8393 edit_preview: &Option<language::EditPreview>,
8394 snapshot: &language::BufferSnapshot,
8395 window: &mut Window,
8396 cx: &mut App,
8397 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8398 let edit_start = edits
8399 .first()
8400 .unwrap()
8401 .0
8402 .start
8403 .to_display_point(editor_snapshot);
8404 let edit_end = edits
8405 .last()
8406 .unwrap()
8407 .0
8408 .end
8409 .to_display_point(editor_snapshot);
8410
8411 let is_visible = visible_row_range.contains(&edit_start.row())
8412 || visible_row_range.contains(&edit_end.row());
8413 if !is_visible {
8414 return None;
8415 }
8416
8417 let highlighted_edits =
8418 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8419
8420 let styled_text = highlighted_edits.to_styled_text(&style.text);
8421 let line_count = highlighted_edits.text.lines().count();
8422
8423 const BORDER_WIDTH: Pixels = px(1.);
8424
8425 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8426 let has_keybind = keybind.is_some();
8427
8428 let mut element = h_flex()
8429 .items_start()
8430 .child(
8431 h_flex()
8432 .bg(cx.theme().colors().editor_background)
8433 .border(BORDER_WIDTH)
8434 .shadow_sm()
8435 .border_color(cx.theme().colors().border)
8436 .rounded_l_lg()
8437 .when(line_count > 1, |el| el.rounded_br_lg())
8438 .pr_1()
8439 .child(styled_text),
8440 )
8441 .child(
8442 h_flex()
8443 .h(line_height + BORDER_WIDTH * 2.)
8444 .px_1p5()
8445 .gap_1()
8446 // Workaround: For some reason, there's a gap if we don't do this
8447 .ml(-BORDER_WIDTH)
8448 .shadow(vec![gpui::BoxShadow {
8449 color: gpui::black().opacity(0.05),
8450 offset: point(px(1.), px(1.)),
8451 blur_radius: px(2.),
8452 spread_radius: px(0.),
8453 }])
8454 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8455 .border(BORDER_WIDTH)
8456 .border_color(cx.theme().colors().border)
8457 .rounded_r_lg()
8458 .id("edit_prediction_diff_popover_keybind")
8459 .when(!has_keybind, |el| {
8460 let status_colors = cx.theme().status();
8461
8462 el.bg(status_colors.error_background)
8463 .border_color(status_colors.error.opacity(0.6))
8464 .child(Icon::new(IconName::Info).color(Color::Error))
8465 .cursor_default()
8466 .hoverable_tooltip(move |_window, cx| {
8467 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8468 })
8469 })
8470 .children(keybind),
8471 )
8472 .into_any();
8473
8474 let longest_row =
8475 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8476 let longest_line_width = if visible_row_range.contains(&longest_row) {
8477 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8478 } else {
8479 layout_line(
8480 longest_row,
8481 editor_snapshot,
8482 style,
8483 editor_width,
8484 |_| false,
8485 window,
8486 cx,
8487 )
8488 .width
8489 };
8490
8491 let viewport_bounds =
8492 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8493 right: -right_margin,
8494 ..Default::default()
8495 });
8496
8497 let x_after_longest =
8498 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8499 - scroll_pixel_position.x;
8500
8501 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8502
8503 // Fully visible if it can be displayed within the window (allow overlapping other
8504 // panes). However, this is only allowed if the popover starts within text_bounds.
8505 let can_position_to_the_right = x_after_longest < text_bounds.right()
8506 && x_after_longest + element_bounds.width < viewport_bounds.right();
8507
8508 let mut origin = if can_position_to_the_right {
8509 point(
8510 x_after_longest,
8511 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8512 - scroll_pixel_position.y,
8513 )
8514 } else {
8515 let cursor_row = newest_selection_head.map(|head| head.row());
8516 let above_edit = edit_start
8517 .row()
8518 .0
8519 .checked_sub(line_count as u32)
8520 .map(DisplayRow);
8521 let below_edit = Some(edit_end.row() + 1);
8522 let above_cursor =
8523 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8524 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8525
8526 // Place the edit popover adjacent to the edit if there is a location
8527 // available that is onscreen and does not obscure the cursor. Otherwise,
8528 // place it adjacent to the cursor.
8529 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8530 .into_iter()
8531 .flatten()
8532 .find(|&start_row| {
8533 let end_row = start_row + line_count as u32;
8534 visible_row_range.contains(&start_row)
8535 && visible_row_range.contains(&end_row)
8536 && cursor_row.map_or(true, |cursor_row| {
8537 !((start_row..end_row).contains(&cursor_row))
8538 })
8539 })?;
8540
8541 content_origin
8542 + point(
8543 -scroll_pixel_position.x,
8544 row_target.as_f32() * line_height - scroll_pixel_position.y,
8545 )
8546 };
8547
8548 origin.x -= BORDER_WIDTH;
8549
8550 window.defer_draw(element, origin, 1);
8551
8552 // Do not return an element, since it will already be drawn due to defer_draw.
8553 None
8554 }
8555
8556 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8557 px(30.)
8558 }
8559
8560 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8561 if self.read_only(cx) {
8562 cx.theme().players().read_only()
8563 } else {
8564 self.style.as_ref().unwrap().local_player
8565 }
8566 }
8567
8568 fn render_edit_prediction_accept_keybind(
8569 &self,
8570 window: &mut Window,
8571 cx: &App,
8572 ) -> Option<AnyElement> {
8573 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8574 let accept_keystroke = accept_binding.keystroke()?;
8575
8576 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8577
8578 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8579 Color::Accent
8580 } else {
8581 Color::Muted
8582 };
8583
8584 h_flex()
8585 .px_0p5()
8586 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8587 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8588 .text_size(TextSize::XSmall.rems(cx))
8589 .child(h_flex().children(ui::render_modifiers(
8590 &accept_keystroke.modifiers,
8591 PlatformStyle::platform(),
8592 Some(modifiers_color),
8593 Some(IconSize::XSmall.rems().into()),
8594 true,
8595 )))
8596 .when(is_platform_style_mac, |parent| {
8597 parent.child(accept_keystroke.key.clone())
8598 })
8599 .when(!is_platform_style_mac, |parent| {
8600 parent.child(
8601 Key::new(
8602 util::capitalize(&accept_keystroke.key),
8603 Some(Color::Default),
8604 )
8605 .size(Some(IconSize::XSmall.rems().into())),
8606 )
8607 })
8608 .into_any()
8609 .into()
8610 }
8611
8612 fn render_edit_prediction_line_popover(
8613 &self,
8614 label: impl Into<SharedString>,
8615 icon: Option<IconName>,
8616 window: &mut Window,
8617 cx: &App,
8618 ) -> Option<Stateful<Div>> {
8619 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8620
8621 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8622 let has_keybind = keybind.is_some();
8623
8624 let result = h_flex()
8625 .id("ep-line-popover")
8626 .py_0p5()
8627 .pl_1()
8628 .pr(padding_right)
8629 .gap_1()
8630 .rounded_md()
8631 .border_1()
8632 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8633 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8634 .shadow_sm()
8635 .when(!has_keybind, |el| {
8636 let status_colors = cx.theme().status();
8637
8638 el.bg(status_colors.error_background)
8639 .border_color(status_colors.error.opacity(0.6))
8640 .pl_2()
8641 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8642 .cursor_default()
8643 .hoverable_tooltip(move |_window, cx| {
8644 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8645 })
8646 })
8647 .children(keybind)
8648 .child(
8649 Label::new(label)
8650 .size(LabelSize::Small)
8651 .when(!has_keybind, |el| {
8652 el.color(cx.theme().status().error.into()).strikethrough()
8653 }),
8654 )
8655 .when(!has_keybind, |el| {
8656 el.child(
8657 h_flex().ml_1().child(
8658 Icon::new(IconName::Info)
8659 .size(IconSize::Small)
8660 .color(cx.theme().status().error.into()),
8661 ),
8662 )
8663 })
8664 .when_some(icon, |element, icon| {
8665 element.child(
8666 div()
8667 .mt(px(1.5))
8668 .child(Icon::new(icon).size(IconSize::Small)),
8669 )
8670 });
8671
8672 Some(result)
8673 }
8674
8675 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8676 let accent_color = cx.theme().colors().text_accent;
8677 let editor_bg_color = cx.theme().colors().editor_background;
8678 editor_bg_color.blend(accent_color.opacity(0.1))
8679 }
8680
8681 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8682 let accent_color = cx.theme().colors().text_accent;
8683 let editor_bg_color = cx.theme().colors().editor_background;
8684 editor_bg_color.blend(accent_color.opacity(0.6))
8685 }
8686
8687 fn render_edit_prediction_cursor_popover(
8688 &self,
8689 min_width: Pixels,
8690 max_width: Pixels,
8691 cursor_point: Point,
8692 style: &EditorStyle,
8693 accept_keystroke: Option<&gpui::Keystroke>,
8694 _window: &Window,
8695 cx: &mut Context<Editor>,
8696 ) -> Option<AnyElement> {
8697 let provider = self.edit_prediction_provider.as_ref()?;
8698
8699 if provider.provider.needs_terms_acceptance(cx) {
8700 return Some(
8701 h_flex()
8702 .min_w(min_width)
8703 .flex_1()
8704 .px_2()
8705 .py_1()
8706 .gap_3()
8707 .elevation_2(cx)
8708 .hover(|style| style.bg(cx.theme().colors().element_hover))
8709 .id("accept-terms")
8710 .cursor_pointer()
8711 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8712 .on_click(cx.listener(|this, _event, window, cx| {
8713 cx.stop_propagation();
8714 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8715 window.dispatch_action(
8716 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8717 cx,
8718 );
8719 }))
8720 .child(
8721 h_flex()
8722 .flex_1()
8723 .gap_2()
8724 .child(Icon::new(IconName::ZedPredict))
8725 .child(Label::new("Accept Terms of Service"))
8726 .child(div().w_full())
8727 .child(
8728 Icon::new(IconName::ArrowUpRight)
8729 .color(Color::Muted)
8730 .size(IconSize::Small),
8731 )
8732 .into_any_element(),
8733 )
8734 .into_any(),
8735 );
8736 }
8737
8738 let is_refreshing = provider.provider.is_refreshing(cx);
8739
8740 fn pending_completion_container() -> Div {
8741 h_flex()
8742 .h_full()
8743 .flex_1()
8744 .gap_2()
8745 .child(Icon::new(IconName::ZedPredict))
8746 }
8747
8748 let completion = match &self.active_inline_completion {
8749 Some(prediction) => {
8750 if !self.has_visible_completions_menu() {
8751 const RADIUS: Pixels = px(6.);
8752 const BORDER_WIDTH: Pixels = px(1.);
8753
8754 return Some(
8755 h_flex()
8756 .elevation_2(cx)
8757 .border(BORDER_WIDTH)
8758 .border_color(cx.theme().colors().border)
8759 .when(accept_keystroke.is_none(), |el| {
8760 el.border_color(cx.theme().status().error)
8761 })
8762 .rounded(RADIUS)
8763 .rounded_tl(px(0.))
8764 .overflow_hidden()
8765 .child(div().px_1p5().child(match &prediction.completion {
8766 InlineCompletion::Move { target, snapshot } => {
8767 use text::ToPoint as _;
8768 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8769 {
8770 Icon::new(IconName::ZedPredictDown)
8771 } else {
8772 Icon::new(IconName::ZedPredictUp)
8773 }
8774 }
8775 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8776 }))
8777 .child(
8778 h_flex()
8779 .gap_1()
8780 .py_1()
8781 .px_2()
8782 .rounded_r(RADIUS - BORDER_WIDTH)
8783 .border_l_1()
8784 .border_color(cx.theme().colors().border)
8785 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8786 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8787 el.child(
8788 Label::new("Hold")
8789 .size(LabelSize::Small)
8790 .when(accept_keystroke.is_none(), |el| {
8791 el.strikethrough()
8792 })
8793 .line_height_style(LineHeightStyle::UiLabel),
8794 )
8795 })
8796 .id("edit_prediction_cursor_popover_keybind")
8797 .when(accept_keystroke.is_none(), |el| {
8798 let status_colors = cx.theme().status();
8799
8800 el.bg(status_colors.error_background)
8801 .border_color(status_colors.error.opacity(0.6))
8802 .child(Icon::new(IconName::Info).color(Color::Error))
8803 .cursor_default()
8804 .hoverable_tooltip(move |_window, cx| {
8805 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8806 .into()
8807 })
8808 })
8809 .when_some(
8810 accept_keystroke.as_ref(),
8811 |el, accept_keystroke| {
8812 el.child(h_flex().children(ui::render_modifiers(
8813 &accept_keystroke.modifiers,
8814 PlatformStyle::platform(),
8815 Some(Color::Default),
8816 Some(IconSize::XSmall.rems().into()),
8817 false,
8818 )))
8819 },
8820 ),
8821 )
8822 .into_any(),
8823 );
8824 }
8825
8826 self.render_edit_prediction_cursor_popover_preview(
8827 prediction,
8828 cursor_point,
8829 style,
8830 cx,
8831 )?
8832 }
8833
8834 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8835 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8836 stale_completion,
8837 cursor_point,
8838 style,
8839 cx,
8840 )?,
8841
8842 None => {
8843 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8844 }
8845 },
8846
8847 None => pending_completion_container().child(Label::new("No Prediction")),
8848 };
8849
8850 let completion = if is_refreshing {
8851 completion
8852 .with_animation(
8853 "loading-completion",
8854 Animation::new(Duration::from_secs(2))
8855 .repeat()
8856 .with_easing(pulsating_between(0.4, 0.8)),
8857 |label, delta| label.opacity(delta),
8858 )
8859 .into_any_element()
8860 } else {
8861 completion.into_any_element()
8862 };
8863
8864 let has_completion = self.active_inline_completion.is_some();
8865
8866 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8867 Some(
8868 h_flex()
8869 .min_w(min_width)
8870 .max_w(max_width)
8871 .flex_1()
8872 .elevation_2(cx)
8873 .border_color(cx.theme().colors().border)
8874 .child(
8875 div()
8876 .flex_1()
8877 .py_1()
8878 .px_2()
8879 .overflow_hidden()
8880 .child(completion),
8881 )
8882 .when_some(accept_keystroke, |el, accept_keystroke| {
8883 if !accept_keystroke.modifiers.modified() {
8884 return el;
8885 }
8886
8887 el.child(
8888 h_flex()
8889 .h_full()
8890 .border_l_1()
8891 .rounded_r_lg()
8892 .border_color(cx.theme().colors().border)
8893 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8894 .gap_1()
8895 .py_1()
8896 .px_2()
8897 .child(
8898 h_flex()
8899 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8900 .when(is_platform_style_mac, |parent| parent.gap_1())
8901 .child(h_flex().children(ui::render_modifiers(
8902 &accept_keystroke.modifiers,
8903 PlatformStyle::platform(),
8904 Some(if !has_completion {
8905 Color::Muted
8906 } else {
8907 Color::Default
8908 }),
8909 None,
8910 false,
8911 ))),
8912 )
8913 .child(Label::new("Preview").into_any_element())
8914 .opacity(if has_completion { 1.0 } else { 0.4 }),
8915 )
8916 })
8917 .into_any(),
8918 )
8919 }
8920
8921 fn render_edit_prediction_cursor_popover_preview(
8922 &self,
8923 completion: &InlineCompletionState,
8924 cursor_point: Point,
8925 style: &EditorStyle,
8926 cx: &mut Context<Editor>,
8927 ) -> Option<Div> {
8928 use text::ToPoint as _;
8929
8930 fn render_relative_row_jump(
8931 prefix: impl Into<String>,
8932 current_row: u32,
8933 target_row: u32,
8934 ) -> Div {
8935 let (row_diff, arrow) = if target_row < current_row {
8936 (current_row - target_row, IconName::ArrowUp)
8937 } else {
8938 (target_row - current_row, IconName::ArrowDown)
8939 };
8940
8941 h_flex()
8942 .child(
8943 Label::new(format!("{}{}", prefix.into(), row_diff))
8944 .color(Color::Muted)
8945 .size(LabelSize::Small),
8946 )
8947 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8948 }
8949
8950 match &completion.completion {
8951 InlineCompletion::Move {
8952 target, snapshot, ..
8953 } => Some(
8954 h_flex()
8955 .px_2()
8956 .gap_2()
8957 .flex_1()
8958 .child(
8959 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8960 Icon::new(IconName::ZedPredictDown)
8961 } else {
8962 Icon::new(IconName::ZedPredictUp)
8963 },
8964 )
8965 .child(Label::new("Jump to Edit")),
8966 ),
8967
8968 InlineCompletion::Edit {
8969 edits,
8970 edit_preview,
8971 snapshot,
8972 display_mode: _,
8973 } => {
8974 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8975
8976 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8977 &snapshot,
8978 &edits,
8979 edit_preview.as_ref()?,
8980 true,
8981 cx,
8982 )
8983 .first_line_preview();
8984
8985 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8986 .with_default_highlights(&style.text, highlighted_edits.highlights);
8987
8988 let preview = h_flex()
8989 .gap_1()
8990 .min_w_16()
8991 .child(styled_text)
8992 .when(has_more_lines, |parent| parent.child("…"));
8993
8994 let left = if first_edit_row != cursor_point.row {
8995 render_relative_row_jump("", cursor_point.row, first_edit_row)
8996 .into_any_element()
8997 } else {
8998 Icon::new(IconName::ZedPredict).into_any_element()
8999 };
9000
9001 Some(
9002 h_flex()
9003 .h_full()
9004 .flex_1()
9005 .gap_2()
9006 .pr_1()
9007 .overflow_x_hidden()
9008 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9009 .child(left)
9010 .child(preview),
9011 )
9012 }
9013 }
9014 }
9015
9016 pub fn render_context_menu(
9017 &self,
9018 style: &EditorStyle,
9019 max_height_in_lines: u32,
9020 window: &mut Window,
9021 cx: &mut Context<Editor>,
9022 ) -> Option<AnyElement> {
9023 let menu = self.context_menu.borrow();
9024 let menu = menu.as_ref()?;
9025 if !menu.visible() {
9026 return None;
9027 };
9028 Some(menu.render(style, max_height_in_lines, window, cx))
9029 }
9030
9031 fn render_context_menu_aside(
9032 &mut self,
9033 max_size: Size<Pixels>,
9034 window: &mut Window,
9035 cx: &mut Context<Editor>,
9036 ) -> Option<AnyElement> {
9037 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9038 if menu.visible() {
9039 menu.render_aside(max_size, window, cx)
9040 } else {
9041 None
9042 }
9043 })
9044 }
9045
9046 fn hide_context_menu(
9047 &mut self,
9048 window: &mut Window,
9049 cx: &mut Context<Self>,
9050 ) -> Option<CodeContextMenu> {
9051 cx.notify();
9052 self.completion_tasks.clear();
9053 let context_menu = self.context_menu.borrow_mut().take();
9054 self.stale_inline_completion_in_menu.take();
9055 self.update_visible_inline_completion(window, cx);
9056 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9057 if let Some(completion_provider) = &self.completion_provider {
9058 completion_provider.selection_changed(None, window, cx);
9059 }
9060 }
9061 context_menu
9062 }
9063
9064 fn show_snippet_choices(
9065 &mut self,
9066 choices: &Vec<String>,
9067 selection: Range<Anchor>,
9068 cx: &mut Context<Self>,
9069 ) {
9070 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9071 (Some(a), Some(b)) if a == b => a,
9072 _ => {
9073 log::error!("expected anchor range to have matching buffer IDs");
9074 return;
9075 }
9076 };
9077 let multi_buffer = self.buffer().read(cx);
9078 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9079 return;
9080 };
9081
9082 let id = post_inc(&mut self.next_completion_id);
9083 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9084 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9085 CompletionsMenu::new_snippet_choices(
9086 id,
9087 true,
9088 choices,
9089 selection,
9090 buffer,
9091 snippet_sort_order,
9092 ),
9093 ));
9094 }
9095
9096 pub fn insert_snippet(
9097 &mut self,
9098 insertion_ranges: &[Range<usize>],
9099 snippet: Snippet,
9100 window: &mut Window,
9101 cx: &mut Context<Self>,
9102 ) -> Result<()> {
9103 struct Tabstop<T> {
9104 is_end_tabstop: bool,
9105 ranges: Vec<Range<T>>,
9106 choices: Option<Vec<String>>,
9107 }
9108
9109 let tabstops = self.buffer.update(cx, |buffer, cx| {
9110 let snippet_text: Arc<str> = snippet.text.clone().into();
9111 let edits = insertion_ranges
9112 .iter()
9113 .cloned()
9114 .map(|range| (range, snippet_text.clone()));
9115 let autoindent_mode = AutoindentMode::Block {
9116 original_indent_columns: Vec::new(),
9117 };
9118 buffer.edit(edits, Some(autoindent_mode), cx);
9119
9120 let snapshot = &*buffer.read(cx);
9121 let snippet = &snippet;
9122 snippet
9123 .tabstops
9124 .iter()
9125 .map(|tabstop| {
9126 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9127 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9128 });
9129 let mut tabstop_ranges = tabstop
9130 .ranges
9131 .iter()
9132 .flat_map(|tabstop_range| {
9133 let mut delta = 0_isize;
9134 insertion_ranges.iter().map(move |insertion_range| {
9135 let insertion_start = insertion_range.start as isize + delta;
9136 delta +=
9137 snippet.text.len() as isize - insertion_range.len() as isize;
9138
9139 let start = ((insertion_start + tabstop_range.start) as usize)
9140 .min(snapshot.len());
9141 let end = ((insertion_start + tabstop_range.end) as usize)
9142 .min(snapshot.len());
9143 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9144 })
9145 })
9146 .collect::<Vec<_>>();
9147 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9148
9149 Tabstop {
9150 is_end_tabstop,
9151 ranges: tabstop_ranges,
9152 choices: tabstop.choices.clone(),
9153 }
9154 })
9155 .collect::<Vec<_>>()
9156 });
9157 if let Some(tabstop) = tabstops.first() {
9158 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9159 // Reverse order so that the first range is the newest created selection.
9160 // Completions will use it and autoscroll will prioritize it.
9161 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9162 });
9163
9164 if let Some(choices) = &tabstop.choices {
9165 if let Some(selection) = tabstop.ranges.first() {
9166 self.show_snippet_choices(choices, selection.clone(), cx)
9167 }
9168 }
9169
9170 // If we're already at the last tabstop and it's at the end of the snippet,
9171 // we're done, we don't need to keep the state around.
9172 if !tabstop.is_end_tabstop {
9173 let choices = tabstops
9174 .iter()
9175 .map(|tabstop| tabstop.choices.clone())
9176 .collect();
9177
9178 let ranges = tabstops
9179 .into_iter()
9180 .map(|tabstop| tabstop.ranges)
9181 .collect::<Vec<_>>();
9182
9183 self.snippet_stack.push(SnippetState {
9184 active_index: 0,
9185 ranges,
9186 choices,
9187 });
9188 }
9189
9190 // Check whether the just-entered snippet ends with an auto-closable bracket.
9191 if self.autoclose_regions.is_empty() {
9192 let snapshot = self.buffer.read(cx).snapshot(cx);
9193 for selection in &mut self.selections.all::<Point>(cx) {
9194 let selection_head = selection.head();
9195 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9196 continue;
9197 };
9198
9199 let mut bracket_pair = None;
9200 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9201 let prev_chars = snapshot
9202 .reversed_chars_at(selection_head)
9203 .collect::<String>();
9204 for (pair, enabled) in scope.brackets() {
9205 if enabled
9206 && pair.close
9207 && prev_chars.starts_with(pair.start.as_str())
9208 && next_chars.starts_with(pair.end.as_str())
9209 {
9210 bracket_pair = Some(pair.clone());
9211 break;
9212 }
9213 }
9214 if let Some(pair) = bracket_pair {
9215 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9216 let autoclose_enabled =
9217 self.use_autoclose && snapshot_settings.use_autoclose;
9218 if autoclose_enabled {
9219 let start = snapshot.anchor_after(selection_head);
9220 let end = snapshot.anchor_after(selection_head);
9221 self.autoclose_regions.push(AutocloseRegion {
9222 selection_id: selection.id,
9223 range: start..end,
9224 pair,
9225 });
9226 }
9227 }
9228 }
9229 }
9230 }
9231 Ok(())
9232 }
9233
9234 pub fn move_to_next_snippet_tabstop(
9235 &mut self,
9236 window: &mut Window,
9237 cx: &mut Context<Self>,
9238 ) -> bool {
9239 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9240 }
9241
9242 pub fn move_to_prev_snippet_tabstop(
9243 &mut self,
9244 window: &mut Window,
9245 cx: &mut Context<Self>,
9246 ) -> bool {
9247 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9248 }
9249
9250 pub fn move_to_snippet_tabstop(
9251 &mut self,
9252 bias: Bias,
9253 window: &mut Window,
9254 cx: &mut Context<Self>,
9255 ) -> bool {
9256 if let Some(mut snippet) = self.snippet_stack.pop() {
9257 match bias {
9258 Bias::Left => {
9259 if snippet.active_index > 0 {
9260 snippet.active_index -= 1;
9261 } else {
9262 self.snippet_stack.push(snippet);
9263 return false;
9264 }
9265 }
9266 Bias::Right => {
9267 if snippet.active_index + 1 < snippet.ranges.len() {
9268 snippet.active_index += 1;
9269 } else {
9270 self.snippet_stack.push(snippet);
9271 return false;
9272 }
9273 }
9274 }
9275 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9276 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9277 // Reverse order so that the first range is the newest created selection.
9278 // Completions will use it and autoscroll will prioritize it.
9279 s.select_ranges(current_ranges.iter().rev().cloned())
9280 });
9281
9282 if let Some(choices) = &snippet.choices[snippet.active_index] {
9283 if let Some(selection) = current_ranges.first() {
9284 self.show_snippet_choices(&choices, selection.clone(), cx);
9285 }
9286 }
9287
9288 // If snippet state is not at the last tabstop, push it back on the stack
9289 if snippet.active_index + 1 < snippet.ranges.len() {
9290 self.snippet_stack.push(snippet);
9291 }
9292 return true;
9293 }
9294 }
9295
9296 false
9297 }
9298
9299 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9300 self.transact(window, cx, |this, window, cx| {
9301 this.select_all(&SelectAll, window, cx);
9302 this.insert("", window, cx);
9303 });
9304 }
9305
9306 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9307 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9308 self.transact(window, cx, |this, window, cx| {
9309 this.select_autoclose_pair(window, cx);
9310 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9311 if !this.linked_edit_ranges.is_empty() {
9312 let selections = this.selections.all::<MultiBufferPoint>(cx);
9313 let snapshot = this.buffer.read(cx).snapshot(cx);
9314
9315 for selection in selections.iter() {
9316 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9317 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9318 if selection_start.buffer_id != selection_end.buffer_id {
9319 continue;
9320 }
9321 if let Some(ranges) =
9322 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9323 {
9324 for (buffer, entries) in ranges {
9325 linked_ranges.entry(buffer).or_default().extend(entries);
9326 }
9327 }
9328 }
9329 }
9330
9331 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9332 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9333 for selection in &mut selections {
9334 if selection.is_empty() {
9335 let old_head = selection.head();
9336 let mut new_head =
9337 movement::left(&display_map, old_head.to_display_point(&display_map))
9338 .to_point(&display_map);
9339 if let Some((buffer, line_buffer_range)) = display_map
9340 .buffer_snapshot
9341 .buffer_line_for_row(MultiBufferRow(old_head.row))
9342 {
9343 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9344 let indent_len = match indent_size.kind {
9345 IndentKind::Space => {
9346 buffer.settings_at(line_buffer_range.start, cx).tab_size
9347 }
9348 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9349 };
9350 if old_head.column <= indent_size.len && old_head.column > 0 {
9351 let indent_len = indent_len.get();
9352 new_head = cmp::min(
9353 new_head,
9354 MultiBufferPoint::new(
9355 old_head.row,
9356 ((old_head.column - 1) / indent_len) * indent_len,
9357 ),
9358 );
9359 }
9360 }
9361
9362 selection.set_head(new_head, SelectionGoal::None);
9363 }
9364 }
9365
9366 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9367 s.select(selections)
9368 });
9369 this.insert("", window, cx);
9370 let empty_str: Arc<str> = Arc::from("");
9371 for (buffer, edits) in linked_ranges {
9372 let snapshot = buffer.read(cx).snapshot();
9373 use text::ToPoint as TP;
9374
9375 let edits = edits
9376 .into_iter()
9377 .map(|range| {
9378 let end_point = TP::to_point(&range.end, &snapshot);
9379 let mut start_point = TP::to_point(&range.start, &snapshot);
9380
9381 if end_point == start_point {
9382 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9383 .saturating_sub(1);
9384 start_point =
9385 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9386 };
9387
9388 (start_point..end_point, empty_str.clone())
9389 })
9390 .sorted_by_key(|(range, _)| range.start)
9391 .collect::<Vec<_>>();
9392 buffer.update(cx, |this, cx| {
9393 this.edit(edits, None, cx);
9394 })
9395 }
9396 this.refresh_inline_completion(true, false, window, cx);
9397 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9398 });
9399 }
9400
9401 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9402 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9403 self.transact(window, cx, |this, window, cx| {
9404 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9405 s.move_with(|map, selection| {
9406 if selection.is_empty() {
9407 let cursor = movement::right(map, selection.head());
9408 selection.end = cursor;
9409 selection.reversed = true;
9410 selection.goal = SelectionGoal::None;
9411 }
9412 })
9413 });
9414 this.insert("", window, cx);
9415 this.refresh_inline_completion(true, false, window, cx);
9416 });
9417 }
9418
9419 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9420 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9421 if self.move_to_prev_snippet_tabstop(window, cx) {
9422 return;
9423 }
9424 self.outdent(&Outdent, window, cx);
9425 }
9426
9427 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9428 if self.move_to_next_snippet_tabstop(window, cx) {
9429 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9430 return;
9431 }
9432 if self.read_only(cx) {
9433 return;
9434 }
9435 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9436 let mut selections = self.selections.all_adjusted(cx);
9437 let buffer = self.buffer.read(cx);
9438 let snapshot = buffer.snapshot(cx);
9439 let rows_iter = selections.iter().map(|s| s.head().row);
9440 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9441
9442 let has_some_cursor_in_whitespace = selections
9443 .iter()
9444 .filter(|selection| selection.is_empty())
9445 .any(|selection| {
9446 let cursor = selection.head();
9447 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9448 cursor.column < current_indent.len
9449 });
9450
9451 let mut edits = Vec::new();
9452 let mut prev_edited_row = 0;
9453 let mut row_delta = 0;
9454 for selection in &mut selections {
9455 if selection.start.row != prev_edited_row {
9456 row_delta = 0;
9457 }
9458 prev_edited_row = selection.end.row;
9459
9460 // If the selection is non-empty, then increase the indentation of the selected lines.
9461 if !selection.is_empty() {
9462 row_delta =
9463 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9464 continue;
9465 }
9466
9467 let cursor = selection.head();
9468 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9469 if let Some(suggested_indent) =
9470 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9471 {
9472 // Don't do anything if already at suggested indent
9473 // and there is any other cursor which is not
9474 if has_some_cursor_in_whitespace
9475 && cursor.column == current_indent.len
9476 && current_indent.len == suggested_indent.len
9477 {
9478 continue;
9479 }
9480
9481 // Adjust line and move cursor to suggested indent
9482 // if cursor is not at suggested indent
9483 if cursor.column < suggested_indent.len
9484 && cursor.column <= current_indent.len
9485 && current_indent.len <= suggested_indent.len
9486 {
9487 selection.start = Point::new(cursor.row, suggested_indent.len);
9488 selection.end = selection.start;
9489 if row_delta == 0 {
9490 edits.extend(Buffer::edit_for_indent_size_adjustment(
9491 cursor.row,
9492 current_indent,
9493 suggested_indent,
9494 ));
9495 row_delta = suggested_indent.len - current_indent.len;
9496 }
9497 continue;
9498 }
9499
9500 // If current indent is more than suggested indent
9501 // only move cursor to current indent and skip indent
9502 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9503 selection.start = Point::new(cursor.row, current_indent.len);
9504 selection.end = selection.start;
9505 continue;
9506 }
9507 }
9508
9509 // Otherwise, insert a hard or soft tab.
9510 let settings = buffer.language_settings_at(cursor, cx);
9511 let tab_size = if settings.hard_tabs {
9512 IndentSize::tab()
9513 } else {
9514 let tab_size = settings.tab_size.get();
9515 let indent_remainder = snapshot
9516 .text_for_range(Point::new(cursor.row, 0)..cursor)
9517 .flat_map(str::chars)
9518 .fold(row_delta % tab_size, |counter: u32, c| {
9519 if c == '\t' {
9520 0
9521 } else {
9522 (counter + 1) % tab_size
9523 }
9524 });
9525
9526 let chars_to_next_tab_stop = tab_size - indent_remainder;
9527 IndentSize::spaces(chars_to_next_tab_stop)
9528 };
9529 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9530 selection.end = selection.start;
9531 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9532 row_delta += tab_size.len;
9533 }
9534
9535 self.transact(window, cx, |this, window, cx| {
9536 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9537 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9538 s.select(selections)
9539 });
9540 this.refresh_inline_completion(true, false, window, cx);
9541 });
9542 }
9543
9544 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9545 if self.read_only(cx) {
9546 return;
9547 }
9548 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9549 let mut selections = self.selections.all::<Point>(cx);
9550 let mut prev_edited_row = 0;
9551 let mut row_delta = 0;
9552 let mut edits = Vec::new();
9553 let buffer = self.buffer.read(cx);
9554 let snapshot = buffer.snapshot(cx);
9555 for selection in &mut selections {
9556 if selection.start.row != prev_edited_row {
9557 row_delta = 0;
9558 }
9559 prev_edited_row = selection.end.row;
9560
9561 row_delta =
9562 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9563 }
9564
9565 self.transact(window, cx, |this, window, cx| {
9566 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9567 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9568 s.select(selections)
9569 });
9570 });
9571 }
9572
9573 fn indent_selection(
9574 buffer: &MultiBuffer,
9575 snapshot: &MultiBufferSnapshot,
9576 selection: &mut Selection<Point>,
9577 edits: &mut Vec<(Range<Point>, String)>,
9578 delta_for_start_row: u32,
9579 cx: &App,
9580 ) -> u32 {
9581 let settings = buffer.language_settings_at(selection.start, cx);
9582 let tab_size = settings.tab_size.get();
9583 let indent_kind = if settings.hard_tabs {
9584 IndentKind::Tab
9585 } else {
9586 IndentKind::Space
9587 };
9588 let mut start_row = selection.start.row;
9589 let mut end_row = selection.end.row + 1;
9590
9591 // If a selection ends at the beginning of a line, don't indent
9592 // that last line.
9593 if selection.end.column == 0 && selection.end.row > selection.start.row {
9594 end_row -= 1;
9595 }
9596
9597 // Avoid re-indenting a row that has already been indented by a
9598 // previous selection, but still update this selection's column
9599 // to reflect that indentation.
9600 if delta_for_start_row > 0 {
9601 start_row += 1;
9602 selection.start.column += delta_for_start_row;
9603 if selection.end.row == selection.start.row {
9604 selection.end.column += delta_for_start_row;
9605 }
9606 }
9607
9608 let mut delta_for_end_row = 0;
9609 let has_multiple_rows = start_row + 1 != end_row;
9610 for row in start_row..end_row {
9611 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9612 let indent_delta = match (current_indent.kind, indent_kind) {
9613 (IndentKind::Space, IndentKind::Space) => {
9614 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9615 IndentSize::spaces(columns_to_next_tab_stop)
9616 }
9617 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9618 (_, IndentKind::Tab) => IndentSize::tab(),
9619 };
9620
9621 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9622 0
9623 } else {
9624 selection.start.column
9625 };
9626 let row_start = Point::new(row, start);
9627 edits.push((
9628 row_start..row_start,
9629 indent_delta.chars().collect::<String>(),
9630 ));
9631
9632 // Update this selection's endpoints to reflect the indentation.
9633 if row == selection.start.row {
9634 selection.start.column += indent_delta.len;
9635 }
9636 if row == selection.end.row {
9637 selection.end.column += indent_delta.len;
9638 delta_for_end_row = indent_delta.len;
9639 }
9640 }
9641
9642 if selection.start.row == selection.end.row {
9643 delta_for_start_row + delta_for_end_row
9644 } else {
9645 delta_for_end_row
9646 }
9647 }
9648
9649 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9650 if self.read_only(cx) {
9651 return;
9652 }
9653 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9655 let selections = self.selections.all::<Point>(cx);
9656 let mut deletion_ranges = Vec::new();
9657 let mut last_outdent = None;
9658 {
9659 let buffer = self.buffer.read(cx);
9660 let snapshot = buffer.snapshot(cx);
9661 for selection in &selections {
9662 let settings = buffer.language_settings_at(selection.start, cx);
9663 let tab_size = settings.tab_size.get();
9664 let mut rows = selection.spanned_rows(false, &display_map);
9665
9666 // Avoid re-outdenting a row that has already been outdented by a
9667 // previous selection.
9668 if let Some(last_row) = last_outdent {
9669 if last_row == rows.start {
9670 rows.start = rows.start.next_row();
9671 }
9672 }
9673 let has_multiple_rows = rows.len() > 1;
9674 for row in rows.iter_rows() {
9675 let indent_size = snapshot.indent_size_for_line(row);
9676 if indent_size.len > 0 {
9677 let deletion_len = match indent_size.kind {
9678 IndentKind::Space => {
9679 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9680 if columns_to_prev_tab_stop == 0 {
9681 tab_size
9682 } else {
9683 columns_to_prev_tab_stop
9684 }
9685 }
9686 IndentKind::Tab => 1,
9687 };
9688 let start = if has_multiple_rows
9689 || deletion_len > selection.start.column
9690 || indent_size.len < selection.start.column
9691 {
9692 0
9693 } else {
9694 selection.start.column - deletion_len
9695 };
9696 deletion_ranges.push(
9697 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9698 );
9699 last_outdent = Some(row);
9700 }
9701 }
9702 }
9703 }
9704
9705 self.transact(window, cx, |this, window, cx| {
9706 this.buffer.update(cx, |buffer, cx| {
9707 let empty_str: Arc<str> = Arc::default();
9708 buffer.edit(
9709 deletion_ranges
9710 .into_iter()
9711 .map(|range| (range, empty_str.clone())),
9712 None,
9713 cx,
9714 );
9715 });
9716 let selections = this.selections.all::<usize>(cx);
9717 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9718 s.select(selections)
9719 });
9720 });
9721 }
9722
9723 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9724 if self.read_only(cx) {
9725 return;
9726 }
9727 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9728 let selections = self
9729 .selections
9730 .all::<usize>(cx)
9731 .into_iter()
9732 .map(|s| s.range());
9733
9734 self.transact(window, cx, |this, window, cx| {
9735 this.buffer.update(cx, |buffer, cx| {
9736 buffer.autoindent_ranges(selections, cx);
9737 });
9738 let selections = this.selections.all::<usize>(cx);
9739 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9740 s.select(selections)
9741 });
9742 });
9743 }
9744
9745 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9746 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9747 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9748 let selections = self.selections.all::<Point>(cx);
9749
9750 let mut new_cursors = Vec::new();
9751 let mut edit_ranges = Vec::new();
9752 let mut selections = selections.iter().peekable();
9753 while let Some(selection) = selections.next() {
9754 let mut rows = selection.spanned_rows(false, &display_map);
9755 let goal_display_column = selection.head().to_display_point(&display_map).column();
9756
9757 // Accumulate contiguous regions of rows that we want to delete.
9758 while let Some(next_selection) = selections.peek() {
9759 let next_rows = next_selection.spanned_rows(false, &display_map);
9760 if next_rows.start <= rows.end {
9761 rows.end = next_rows.end;
9762 selections.next().unwrap();
9763 } else {
9764 break;
9765 }
9766 }
9767
9768 let buffer = &display_map.buffer_snapshot;
9769 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9770 let edit_end;
9771 let cursor_buffer_row;
9772 if buffer.max_point().row >= rows.end.0 {
9773 // If there's a line after the range, delete the \n from the end of the row range
9774 // and position the cursor on the next line.
9775 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9776 cursor_buffer_row = rows.end;
9777 } else {
9778 // If there isn't a line after the range, delete the \n from the line before the
9779 // start of the row range and position the cursor there.
9780 edit_start = edit_start.saturating_sub(1);
9781 edit_end = buffer.len();
9782 cursor_buffer_row = rows.start.previous_row();
9783 }
9784
9785 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9786 *cursor.column_mut() =
9787 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9788
9789 new_cursors.push((
9790 selection.id,
9791 buffer.anchor_after(cursor.to_point(&display_map)),
9792 ));
9793 edit_ranges.push(edit_start..edit_end);
9794 }
9795
9796 self.transact(window, cx, |this, window, cx| {
9797 let buffer = this.buffer.update(cx, |buffer, cx| {
9798 let empty_str: Arc<str> = Arc::default();
9799 buffer.edit(
9800 edit_ranges
9801 .into_iter()
9802 .map(|range| (range, empty_str.clone())),
9803 None,
9804 cx,
9805 );
9806 buffer.snapshot(cx)
9807 });
9808 let new_selections = new_cursors
9809 .into_iter()
9810 .map(|(id, cursor)| {
9811 let cursor = cursor.to_point(&buffer);
9812 Selection {
9813 id,
9814 start: cursor,
9815 end: cursor,
9816 reversed: false,
9817 goal: SelectionGoal::None,
9818 }
9819 })
9820 .collect();
9821
9822 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9823 s.select(new_selections);
9824 });
9825 });
9826 }
9827
9828 pub fn join_lines_impl(
9829 &mut self,
9830 insert_whitespace: bool,
9831 window: &mut Window,
9832 cx: &mut Context<Self>,
9833 ) {
9834 if self.read_only(cx) {
9835 return;
9836 }
9837 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9838 for selection in self.selections.all::<Point>(cx) {
9839 let start = MultiBufferRow(selection.start.row);
9840 // Treat single line selections as if they include the next line. Otherwise this action
9841 // would do nothing for single line selections individual cursors.
9842 let end = if selection.start.row == selection.end.row {
9843 MultiBufferRow(selection.start.row + 1)
9844 } else {
9845 MultiBufferRow(selection.end.row)
9846 };
9847
9848 if let Some(last_row_range) = row_ranges.last_mut() {
9849 if start <= last_row_range.end {
9850 last_row_range.end = end;
9851 continue;
9852 }
9853 }
9854 row_ranges.push(start..end);
9855 }
9856
9857 let snapshot = self.buffer.read(cx).snapshot(cx);
9858 let mut cursor_positions = Vec::new();
9859 for row_range in &row_ranges {
9860 let anchor = snapshot.anchor_before(Point::new(
9861 row_range.end.previous_row().0,
9862 snapshot.line_len(row_range.end.previous_row()),
9863 ));
9864 cursor_positions.push(anchor..anchor);
9865 }
9866
9867 self.transact(window, cx, |this, window, cx| {
9868 for row_range in row_ranges.into_iter().rev() {
9869 for row in row_range.iter_rows().rev() {
9870 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9871 let next_line_row = row.next_row();
9872 let indent = snapshot.indent_size_for_line(next_line_row);
9873 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9874
9875 let replace =
9876 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9877 " "
9878 } else {
9879 ""
9880 };
9881
9882 this.buffer.update(cx, |buffer, cx| {
9883 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9884 });
9885 }
9886 }
9887
9888 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9889 s.select_anchor_ranges(cursor_positions)
9890 });
9891 });
9892 }
9893
9894 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9895 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9896 self.join_lines_impl(true, window, cx);
9897 }
9898
9899 pub fn sort_lines_case_sensitive(
9900 &mut self,
9901 _: &SortLinesCaseSensitive,
9902 window: &mut Window,
9903 cx: &mut Context<Self>,
9904 ) {
9905 self.manipulate_lines(window, cx, |lines| lines.sort())
9906 }
9907
9908 pub fn sort_lines_case_insensitive(
9909 &mut self,
9910 _: &SortLinesCaseInsensitive,
9911 window: &mut Window,
9912 cx: &mut Context<Self>,
9913 ) {
9914 self.manipulate_lines(window, cx, |lines| {
9915 lines.sort_by_key(|line| line.to_lowercase())
9916 })
9917 }
9918
9919 pub fn unique_lines_case_insensitive(
9920 &mut self,
9921 _: &UniqueLinesCaseInsensitive,
9922 window: &mut Window,
9923 cx: &mut Context<Self>,
9924 ) {
9925 self.manipulate_lines(window, cx, |lines| {
9926 let mut seen = HashSet::default();
9927 lines.retain(|line| seen.insert(line.to_lowercase()));
9928 })
9929 }
9930
9931 pub fn unique_lines_case_sensitive(
9932 &mut self,
9933 _: &UniqueLinesCaseSensitive,
9934 window: &mut Window,
9935 cx: &mut Context<Self>,
9936 ) {
9937 self.manipulate_lines(window, cx, |lines| {
9938 let mut seen = HashSet::default();
9939 lines.retain(|line| seen.insert(*line));
9940 })
9941 }
9942
9943 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9944 let Some(project) = self.project.clone() else {
9945 return;
9946 };
9947 self.reload(project, window, cx)
9948 .detach_and_notify_err(window, cx);
9949 }
9950
9951 pub fn restore_file(
9952 &mut self,
9953 _: &::git::RestoreFile,
9954 window: &mut Window,
9955 cx: &mut Context<Self>,
9956 ) {
9957 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9958 let mut buffer_ids = HashSet::default();
9959 let snapshot = self.buffer().read(cx).snapshot(cx);
9960 for selection in self.selections.all::<usize>(cx) {
9961 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9962 }
9963
9964 let buffer = self.buffer().read(cx);
9965 let ranges = buffer_ids
9966 .into_iter()
9967 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9968 .collect::<Vec<_>>();
9969
9970 self.restore_hunks_in_ranges(ranges, window, cx);
9971 }
9972
9973 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9974 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9975 let selections = self
9976 .selections
9977 .all(cx)
9978 .into_iter()
9979 .map(|s| s.range())
9980 .collect();
9981 self.restore_hunks_in_ranges(selections, window, cx);
9982 }
9983
9984 pub fn restore_hunks_in_ranges(
9985 &mut self,
9986 ranges: Vec<Range<Point>>,
9987 window: &mut Window,
9988 cx: &mut Context<Editor>,
9989 ) {
9990 let mut revert_changes = HashMap::default();
9991 let chunk_by = self
9992 .snapshot(window, cx)
9993 .hunks_for_ranges(ranges)
9994 .into_iter()
9995 .chunk_by(|hunk| hunk.buffer_id);
9996 for (buffer_id, hunks) in &chunk_by {
9997 let hunks = hunks.collect::<Vec<_>>();
9998 for hunk in &hunks {
9999 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10000 }
10001 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10002 }
10003 drop(chunk_by);
10004 if !revert_changes.is_empty() {
10005 self.transact(window, cx, |editor, window, cx| {
10006 editor.restore(revert_changes, window, cx);
10007 });
10008 }
10009 }
10010
10011 pub fn open_active_item_in_terminal(
10012 &mut self,
10013 _: &OpenInTerminal,
10014 window: &mut Window,
10015 cx: &mut Context<Self>,
10016 ) {
10017 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10018 let project_path = buffer.read(cx).project_path(cx)?;
10019 let project = self.project.as_ref()?.read(cx);
10020 let entry = project.entry_for_path(&project_path, cx)?;
10021 let parent = match &entry.canonical_path {
10022 Some(canonical_path) => canonical_path.to_path_buf(),
10023 None => project.absolute_path(&project_path, cx)?,
10024 }
10025 .parent()?
10026 .to_path_buf();
10027 Some(parent)
10028 }) {
10029 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10030 }
10031 }
10032
10033 fn set_breakpoint_context_menu(
10034 &mut self,
10035 display_row: DisplayRow,
10036 position: Option<Anchor>,
10037 clicked_point: gpui::Point<Pixels>,
10038 window: &mut Window,
10039 cx: &mut Context<Self>,
10040 ) {
10041 if !cx.has_flag::<DebuggerFeatureFlag>() {
10042 return;
10043 }
10044 let source = self
10045 .buffer
10046 .read(cx)
10047 .snapshot(cx)
10048 .anchor_before(Point::new(display_row.0, 0u32));
10049
10050 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10051
10052 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10053 self,
10054 source,
10055 clicked_point,
10056 context_menu,
10057 window,
10058 cx,
10059 );
10060 }
10061
10062 fn add_edit_breakpoint_block(
10063 &mut self,
10064 anchor: Anchor,
10065 breakpoint: &Breakpoint,
10066 edit_action: BreakpointPromptEditAction,
10067 window: &mut Window,
10068 cx: &mut Context<Self>,
10069 ) {
10070 let weak_editor = cx.weak_entity();
10071 let bp_prompt = cx.new(|cx| {
10072 BreakpointPromptEditor::new(
10073 weak_editor,
10074 anchor,
10075 breakpoint.clone(),
10076 edit_action,
10077 window,
10078 cx,
10079 )
10080 });
10081
10082 let height = bp_prompt.update(cx, |this, cx| {
10083 this.prompt
10084 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10085 });
10086 let cloned_prompt = bp_prompt.clone();
10087 let blocks = vec![BlockProperties {
10088 style: BlockStyle::Sticky,
10089 placement: BlockPlacement::Above(anchor),
10090 height: Some(height),
10091 render: Arc::new(move |cx| {
10092 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10093 cloned_prompt.clone().into_any_element()
10094 }),
10095 priority: 0,
10096 render_in_minimap: true,
10097 }];
10098
10099 let focus_handle = bp_prompt.focus_handle(cx);
10100 window.focus(&focus_handle);
10101
10102 let block_ids = self.insert_blocks(blocks, None, cx);
10103 bp_prompt.update(cx, |prompt, _| {
10104 prompt.add_block_ids(block_ids);
10105 });
10106 }
10107
10108 pub(crate) fn breakpoint_at_row(
10109 &self,
10110 row: u32,
10111 window: &mut Window,
10112 cx: &mut Context<Self>,
10113 ) -> Option<(Anchor, Breakpoint)> {
10114 let snapshot = self.snapshot(window, cx);
10115 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10116
10117 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10118 }
10119
10120 pub(crate) fn breakpoint_at_anchor(
10121 &self,
10122 breakpoint_position: Anchor,
10123 snapshot: &EditorSnapshot,
10124 cx: &mut Context<Self>,
10125 ) -> Option<(Anchor, Breakpoint)> {
10126 let project = self.project.clone()?;
10127
10128 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10129 snapshot
10130 .buffer_snapshot
10131 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10132 })?;
10133
10134 let enclosing_excerpt = breakpoint_position.excerpt_id;
10135 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10136 let buffer_snapshot = buffer.read(cx).snapshot();
10137
10138 let row = buffer_snapshot
10139 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10140 .row;
10141
10142 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10143 let anchor_end = snapshot
10144 .buffer_snapshot
10145 .anchor_after(Point::new(row, line_len));
10146
10147 let bp = self
10148 .breakpoint_store
10149 .as_ref()?
10150 .read_with(cx, |breakpoint_store, cx| {
10151 breakpoint_store
10152 .breakpoints(
10153 &buffer,
10154 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10155 &buffer_snapshot,
10156 cx,
10157 )
10158 .next()
10159 .and_then(|(bp, _)| {
10160 let breakpoint_row = buffer_snapshot
10161 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10162 .row;
10163
10164 if breakpoint_row == row {
10165 snapshot
10166 .buffer_snapshot
10167 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10168 .map(|position| (position, bp.bp.clone()))
10169 } else {
10170 None
10171 }
10172 })
10173 });
10174 bp
10175 }
10176
10177 pub fn edit_log_breakpoint(
10178 &mut self,
10179 _: &EditLogBreakpoint,
10180 window: &mut Window,
10181 cx: &mut Context<Self>,
10182 ) {
10183 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10184 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10185 message: None,
10186 state: BreakpointState::Enabled,
10187 condition: None,
10188 hit_condition: None,
10189 });
10190
10191 self.add_edit_breakpoint_block(
10192 anchor,
10193 &breakpoint,
10194 BreakpointPromptEditAction::Log,
10195 window,
10196 cx,
10197 );
10198 }
10199 }
10200
10201 fn breakpoints_at_cursors(
10202 &self,
10203 window: &mut Window,
10204 cx: &mut Context<Self>,
10205 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10206 let snapshot = self.snapshot(window, cx);
10207 let cursors = self
10208 .selections
10209 .disjoint_anchors()
10210 .into_iter()
10211 .map(|selection| {
10212 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10213
10214 let breakpoint_position = self
10215 .breakpoint_at_row(cursor_position.row, window, cx)
10216 .map(|bp| bp.0)
10217 .unwrap_or_else(|| {
10218 snapshot
10219 .display_snapshot
10220 .buffer_snapshot
10221 .anchor_after(Point::new(cursor_position.row, 0))
10222 });
10223
10224 let breakpoint = self
10225 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10226 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10227
10228 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10229 })
10230 // 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.
10231 .collect::<HashMap<Anchor, _>>();
10232
10233 cursors.into_iter().collect()
10234 }
10235
10236 pub fn enable_breakpoint(
10237 &mut self,
10238 _: &crate::actions::EnableBreakpoint,
10239 window: &mut Window,
10240 cx: &mut Context<Self>,
10241 ) {
10242 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10243 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10244 continue;
10245 };
10246 self.edit_breakpoint_at_anchor(
10247 anchor,
10248 breakpoint,
10249 BreakpointEditAction::InvertState,
10250 cx,
10251 );
10252 }
10253 }
10254
10255 pub fn disable_breakpoint(
10256 &mut self,
10257 _: &crate::actions::DisableBreakpoint,
10258 window: &mut Window,
10259 cx: &mut Context<Self>,
10260 ) {
10261 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10262 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10263 continue;
10264 };
10265 self.edit_breakpoint_at_anchor(
10266 anchor,
10267 breakpoint,
10268 BreakpointEditAction::InvertState,
10269 cx,
10270 );
10271 }
10272 }
10273
10274 pub fn toggle_breakpoint(
10275 &mut self,
10276 _: &crate::actions::ToggleBreakpoint,
10277 window: &mut Window,
10278 cx: &mut Context<Self>,
10279 ) {
10280 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10281 if let Some(breakpoint) = breakpoint {
10282 self.edit_breakpoint_at_anchor(
10283 anchor,
10284 breakpoint,
10285 BreakpointEditAction::Toggle,
10286 cx,
10287 );
10288 } else {
10289 self.edit_breakpoint_at_anchor(
10290 anchor,
10291 Breakpoint::new_standard(),
10292 BreakpointEditAction::Toggle,
10293 cx,
10294 );
10295 }
10296 }
10297 }
10298
10299 pub fn edit_breakpoint_at_anchor(
10300 &mut self,
10301 breakpoint_position: Anchor,
10302 breakpoint: Breakpoint,
10303 edit_action: BreakpointEditAction,
10304 cx: &mut Context<Self>,
10305 ) {
10306 let Some(breakpoint_store) = &self.breakpoint_store else {
10307 return;
10308 };
10309
10310 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10311 if breakpoint_position == Anchor::min() {
10312 self.buffer()
10313 .read(cx)
10314 .excerpt_buffer_ids()
10315 .into_iter()
10316 .next()
10317 } else {
10318 None
10319 }
10320 }) else {
10321 return;
10322 };
10323
10324 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10325 return;
10326 };
10327
10328 breakpoint_store.update(cx, |breakpoint_store, cx| {
10329 breakpoint_store.toggle_breakpoint(
10330 buffer,
10331 BreakpointWithPosition {
10332 position: breakpoint_position.text_anchor,
10333 bp: breakpoint,
10334 },
10335 edit_action,
10336 cx,
10337 );
10338 });
10339
10340 cx.notify();
10341 }
10342
10343 #[cfg(any(test, feature = "test-support"))]
10344 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10345 self.breakpoint_store.clone()
10346 }
10347
10348 pub fn prepare_restore_change(
10349 &self,
10350 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10351 hunk: &MultiBufferDiffHunk,
10352 cx: &mut App,
10353 ) -> Option<()> {
10354 if hunk.is_created_file() {
10355 return None;
10356 }
10357 let buffer = self.buffer.read(cx);
10358 let diff = buffer.diff_for(hunk.buffer_id)?;
10359 let buffer = buffer.buffer(hunk.buffer_id)?;
10360 let buffer = buffer.read(cx);
10361 let original_text = diff
10362 .read(cx)
10363 .base_text()
10364 .as_rope()
10365 .slice(hunk.diff_base_byte_range.clone());
10366 let buffer_snapshot = buffer.snapshot();
10367 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10368 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10369 probe
10370 .0
10371 .start
10372 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10373 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10374 }) {
10375 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10376 Some(())
10377 } else {
10378 None
10379 }
10380 }
10381
10382 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10383 self.manipulate_lines(window, cx, |lines| lines.reverse())
10384 }
10385
10386 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10387 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10388 }
10389
10390 fn manipulate_lines<Fn>(
10391 &mut self,
10392 window: &mut Window,
10393 cx: &mut Context<Self>,
10394 mut callback: Fn,
10395 ) where
10396 Fn: FnMut(&mut Vec<&str>),
10397 {
10398 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10399
10400 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10401 let buffer = self.buffer.read(cx).snapshot(cx);
10402
10403 let mut edits = Vec::new();
10404
10405 let selections = self.selections.all::<Point>(cx);
10406 let mut selections = selections.iter().peekable();
10407 let mut contiguous_row_selections = Vec::new();
10408 let mut new_selections = Vec::new();
10409 let mut added_lines = 0;
10410 let mut removed_lines = 0;
10411
10412 while let Some(selection) = selections.next() {
10413 let (start_row, end_row) = consume_contiguous_rows(
10414 &mut contiguous_row_selections,
10415 selection,
10416 &display_map,
10417 &mut selections,
10418 );
10419
10420 let start_point = Point::new(start_row.0, 0);
10421 let end_point = Point::new(
10422 end_row.previous_row().0,
10423 buffer.line_len(end_row.previous_row()),
10424 );
10425 let text = buffer
10426 .text_for_range(start_point..end_point)
10427 .collect::<String>();
10428
10429 let mut lines = text.split('\n').collect_vec();
10430
10431 let lines_before = lines.len();
10432 callback(&mut lines);
10433 let lines_after = lines.len();
10434
10435 edits.push((start_point..end_point, lines.join("\n")));
10436
10437 // Selections must change based on added and removed line count
10438 let start_row =
10439 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10440 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10441 new_selections.push(Selection {
10442 id: selection.id,
10443 start: start_row,
10444 end: end_row,
10445 goal: SelectionGoal::None,
10446 reversed: selection.reversed,
10447 });
10448
10449 if lines_after > lines_before {
10450 added_lines += lines_after - lines_before;
10451 } else if lines_before > lines_after {
10452 removed_lines += lines_before - lines_after;
10453 }
10454 }
10455
10456 self.transact(window, cx, |this, window, cx| {
10457 let buffer = this.buffer.update(cx, |buffer, cx| {
10458 buffer.edit(edits, None, cx);
10459 buffer.snapshot(cx)
10460 });
10461
10462 // Recalculate offsets on newly edited buffer
10463 let new_selections = new_selections
10464 .iter()
10465 .map(|s| {
10466 let start_point = Point::new(s.start.0, 0);
10467 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10468 Selection {
10469 id: s.id,
10470 start: buffer.point_to_offset(start_point),
10471 end: buffer.point_to_offset(end_point),
10472 goal: s.goal,
10473 reversed: s.reversed,
10474 }
10475 })
10476 .collect();
10477
10478 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10479 s.select(new_selections);
10480 });
10481
10482 this.request_autoscroll(Autoscroll::fit(), cx);
10483 });
10484 }
10485
10486 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10487 self.manipulate_text(window, cx, |text| {
10488 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10489 if has_upper_case_characters {
10490 text.to_lowercase()
10491 } else {
10492 text.to_uppercase()
10493 }
10494 })
10495 }
10496
10497 pub fn convert_to_upper_case(
10498 &mut self,
10499 _: &ConvertToUpperCase,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 self.manipulate_text(window, cx, |text| text.to_uppercase())
10504 }
10505
10506 pub fn convert_to_lower_case(
10507 &mut self,
10508 _: &ConvertToLowerCase,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.manipulate_text(window, cx, |text| text.to_lowercase())
10513 }
10514
10515 pub fn convert_to_title_case(
10516 &mut self,
10517 _: &ConvertToTitleCase,
10518 window: &mut Window,
10519 cx: &mut Context<Self>,
10520 ) {
10521 self.manipulate_text(window, cx, |text| {
10522 text.split('\n')
10523 .map(|line| line.to_case(Case::Title))
10524 .join("\n")
10525 })
10526 }
10527
10528 pub fn convert_to_snake_case(
10529 &mut self,
10530 _: &ConvertToSnakeCase,
10531 window: &mut Window,
10532 cx: &mut Context<Self>,
10533 ) {
10534 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10535 }
10536
10537 pub fn convert_to_kebab_case(
10538 &mut self,
10539 _: &ConvertToKebabCase,
10540 window: &mut Window,
10541 cx: &mut Context<Self>,
10542 ) {
10543 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10544 }
10545
10546 pub fn convert_to_upper_camel_case(
10547 &mut self,
10548 _: &ConvertToUpperCamelCase,
10549 window: &mut Window,
10550 cx: &mut Context<Self>,
10551 ) {
10552 self.manipulate_text(window, cx, |text| {
10553 text.split('\n')
10554 .map(|line| line.to_case(Case::UpperCamel))
10555 .join("\n")
10556 })
10557 }
10558
10559 pub fn convert_to_lower_camel_case(
10560 &mut self,
10561 _: &ConvertToLowerCamelCase,
10562 window: &mut Window,
10563 cx: &mut Context<Self>,
10564 ) {
10565 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10566 }
10567
10568 pub fn convert_to_opposite_case(
10569 &mut self,
10570 _: &ConvertToOppositeCase,
10571 window: &mut Window,
10572 cx: &mut Context<Self>,
10573 ) {
10574 self.manipulate_text(window, cx, |text| {
10575 text.chars()
10576 .fold(String::with_capacity(text.len()), |mut t, c| {
10577 if c.is_uppercase() {
10578 t.extend(c.to_lowercase());
10579 } else {
10580 t.extend(c.to_uppercase());
10581 }
10582 t
10583 })
10584 })
10585 }
10586
10587 pub fn convert_to_rot13(
10588 &mut self,
10589 _: &ConvertToRot13,
10590 window: &mut Window,
10591 cx: &mut Context<Self>,
10592 ) {
10593 self.manipulate_text(window, cx, |text| {
10594 text.chars()
10595 .map(|c| match c {
10596 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10597 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10598 _ => c,
10599 })
10600 .collect()
10601 })
10602 }
10603
10604 pub fn convert_to_rot47(
10605 &mut self,
10606 _: &ConvertToRot47,
10607 window: &mut Window,
10608 cx: &mut Context<Self>,
10609 ) {
10610 self.manipulate_text(window, cx, |text| {
10611 text.chars()
10612 .map(|c| {
10613 let code_point = c as u32;
10614 if code_point >= 33 && code_point <= 126 {
10615 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10616 }
10617 c
10618 })
10619 .collect()
10620 })
10621 }
10622
10623 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10624 where
10625 Fn: FnMut(&str) -> String,
10626 {
10627 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10628 let buffer = self.buffer.read(cx).snapshot(cx);
10629
10630 let mut new_selections = Vec::new();
10631 let mut edits = Vec::new();
10632 let mut selection_adjustment = 0i32;
10633
10634 for selection in self.selections.all::<usize>(cx) {
10635 let selection_is_empty = selection.is_empty();
10636
10637 let (start, end) = if selection_is_empty {
10638 let word_range = movement::surrounding_word(
10639 &display_map,
10640 selection.start.to_display_point(&display_map),
10641 );
10642 let start = word_range.start.to_offset(&display_map, Bias::Left);
10643 let end = word_range.end.to_offset(&display_map, Bias::Left);
10644 (start, end)
10645 } else {
10646 (selection.start, selection.end)
10647 };
10648
10649 let text = buffer.text_for_range(start..end).collect::<String>();
10650 let old_length = text.len() as i32;
10651 let text = callback(&text);
10652
10653 new_selections.push(Selection {
10654 start: (start as i32 - selection_adjustment) as usize,
10655 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10656 goal: SelectionGoal::None,
10657 ..selection
10658 });
10659
10660 selection_adjustment += old_length - text.len() as i32;
10661
10662 edits.push((start..end, text));
10663 }
10664
10665 self.transact(window, cx, |this, window, cx| {
10666 this.buffer.update(cx, |buffer, cx| {
10667 buffer.edit(edits, None, cx);
10668 });
10669
10670 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10671 s.select(new_selections);
10672 });
10673
10674 this.request_autoscroll(Autoscroll::fit(), cx);
10675 });
10676 }
10677
10678 pub fn move_selection_on_drop(
10679 &mut self,
10680 selection: &Selection<Anchor>,
10681 target: DisplayPoint,
10682 is_cut: bool,
10683 window: &mut Window,
10684 cx: &mut Context<Self>,
10685 ) {
10686 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10687 let buffer = &display_map.buffer_snapshot;
10688 let mut edits = Vec::new();
10689 let insert_point = display_map
10690 .clip_point(target, Bias::Left)
10691 .to_point(&display_map);
10692 let text = buffer
10693 .text_for_range(selection.start..selection.end)
10694 .collect::<String>();
10695 if is_cut {
10696 edits.push(((selection.start..selection.end), String::new()));
10697 }
10698 let insert_anchor = buffer.anchor_before(insert_point);
10699 edits.push(((insert_anchor..insert_anchor), text));
10700 let last_edit_start = insert_anchor.bias_left(buffer);
10701 let last_edit_end = insert_anchor.bias_right(buffer);
10702 self.transact(window, cx, |this, window, cx| {
10703 this.buffer.update(cx, |buffer, cx| {
10704 buffer.edit(edits, None, cx);
10705 });
10706 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10707 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10708 });
10709 });
10710 }
10711
10712 pub fn clear_selection_drag_state(&mut self) {
10713 self.selection_drag_state = SelectionDragState::None;
10714 }
10715
10716 pub fn duplicate(
10717 &mut self,
10718 upwards: bool,
10719 whole_lines: bool,
10720 window: &mut Window,
10721 cx: &mut Context<Self>,
10722 ) {
10723 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10724
10725 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10726 let buffer = &display_map.buffer_snapshot;
10727 let selections = self.selections.all::<Point>(cx);
10728
10729 let mut edits = Vec::new();
10730 let mut selections_iter = selections.iter().peekable();
10731 while let Some(selection) = selections_iter.next() {
10732 let mut rows = selection.spanned_rows(false, &display_map);
10733 // duplicate line-wise
10734 if whole_lines || selection.start == selection.end {
10735 // Avoid duplicating the same lines twice.
10736 while let Some(next_selection) = selections_iter.peek() {
10737 let next_rows = next_selection.spanned_rows(false, &display_map);
10738 if next_rows.start < rows.end {
10739 rows.end = next_rows.end;
10740 selections_iter.next().unwrap();
10741 } else {
10742 break;
10743 }
10744 }
10745
10746 // Copy the text from the selected row region and splice it either at the start
10747 // or end of the region.
10748 let start = Point::new(rows.start.0, 0);
10749 let end = Point::new(
10750 rows.end.previous_row().0,
10751 buffer.line_len(rows.end.previous_row()),
10752 );
10753 let text = buffer
10754 .text_for_range(start..end)
10755 .chain(Some("\n"))
10756 .collect::<String>();
10757 let insert_location = if upwards {
10758 Point::new(rows.end.0, 0)
10759 } else {
10760 start
10761 };
10762 edits.push((insert_location..insert_location, text));
10763 } else {
10764 // duplicate character-wise
10765 let start = selection.start;
10766 let end = selection.end;
10767 let text = buffer.text_for_range(start..end).collect::<String>();
10768 edits.push((selection.end..selection.end, text));
10769 }
10770 }
10771
10772 self.transact(window, cx, |this, _, cx| {
10773 this.buffer.update(cx, |buffer, cx| {
10774 buffer.edit(edits, None, cx);
10775 });
10776
10777 this.request_autoscroll(Autoscroll::fit(), cx);
10778 });
10779 }
10780
10781 pub fn duplicate_line_up(
10782 &mut self,
10783 _: &DuplicateLineUp,
10784 window: &mut Window,
10785 cx: &mut Context<Self>,
10786 ) {
10787 self.duplicate(true, true, window, cx);
10788 }
10789
10790 pub fn duplicate_line_down(
10791 &mut self,
10792 _: &DuplicateLineDown,
10793 window: &mut Window,
10794 cx: &mut Context<Self>,
10795 ) {
10796 self.duplicate(false, true, window, cx);
10797 }
10798
10799 pub fn duplicate_selection(
10800 &mut self,
10801 _: &DuplicateSelection,
10802 window: &mut Window,
10803 cx: &mut Context<Self>,
10804 ) {
10805 self.duplicate(false, false, window, cx);
10806 }
10807
10808 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10809 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10810
10811 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10812 let buffer = self.buffer.read(cx).snapshot(cx);
10813
10814 let mut edits = Vec::new();
10815 let mut unfold_ranges = Vec::new();
10816 let mut refold_creases = Vec::new();
10817
10818 let selections = self.selections.all::<Point>(cx);
10819 let mut selections = selections.iter().peekable();
10820 let mut contiguous_row_selections = Vec::new();
10821 let mut new_selections = Vec::new();
10822
10823 while let Some(selection) = selections.next() {
10824 // Find all the selections that span a contiguous row range
10825 let (start_row, end_row) = consume_contiguous_rows(
10826 &mut contiguous_row_selections,
10827 selection,
10828 &display_map,
10829 &mut selections,
10830 );
10831
10832 // Move the text spanned by the row range to be before the line preceding the row range
10833 if start_row.0 > 0 {
10834 let range_to_move = Point::new(
10835 start_row.previous_row().0,
10836 buffer.line_len(start_row.previous_row()),
10837 )
10838 ..Point::new(
10839 end_row.previous_row().0,
10840 buffer.line_len(end_row.previous_row()),
10841 );
10842 let insertion_point = display_map
10843 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10844 .0;
10845
10846 // Don't move lines across excerpts
10847 if buffer
10848 .excerpt_containing(insertion_point..range_to_move.end)
10849 .is_some()
10850 {
10851 let text = buffer
10852 .text_for_range(range_to_move.clone())
10853 .flat_map(|s| s.chars())
10854 .skip(1)
10855 .chain(['\n'])
10856 .collect::<String>();
10857
10858 edits.push((
10859 buffer.anchor_after(range_to_move.start)
10860 ..buffer.anchor_before(range_to_move.end),
10861 String::new(),
10862 ));
10863 let insertion_anchor = buffer.anchor_after(insertion_point);
10864 edits.push((insertion_anchor..insertion_anchor, text));
10865
10866 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10867
10868 // Move selections up
10869 new_selections.extend(contiguous_row_selections.drain(..).map(
10870 |mut selection| {
10871 selection.start.row -= row_delta;
10872 selection.end.row -= row_delta;
10873 selection
10874 },
10875 ));
10876
10877 // Move folds up
10878 unfold_ranges.push(range_to_move.clone());
10879 for fold in display_map.folds_in_range(
10880 buffer.anchor_before(range_to_move.start)
10881 ..buffer.anchor_after(range_to_move.end),
10882 ) {
10883 let mut start = fold.range.start.to_point(&buffer);
10884 let mut end = fold.range.end.to_point(&buffer);
10885 start.row -= row_delta;
10886 end.row -= row_delta;
10887 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10888 }
10889 }
10890 }
10891
10892 // If we didn't move line(s), preserve the existing selections
10893 new_selections.append(&mut contiguous_row_selections);
10894 }
10895
10896 self.transact(window, cx, |this, window, cx| {
10897 this.unfold_ranges(&unfold_ranges, true, true, cx);
10898 this.buffer.update(cx, |buffer, cx| {
10899 for (range, text) in edits {
10900 buffer.edit([(range, text)], None, cx);
10901 }
10902 });
10903 this.fold_creases(refold_creases, true, window, cx);
10904 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10905 s.select(new_selections);
10906 })
10907 });
10908 }
10909
10910 pub fn move_line_down(
10911 &mut self,
10912 _: &MoveLineDown,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10917
10918 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10919 let buffer = self.buffer.read(cx).snapshot(cx);
10920
10921 let mut edits = Vec::new();
10922 let mut unfold_ranges = Vec::new();
10923 let mut refold_creases = Vec::new();
10924
10925 let selections = self.selections.all::<Point>(cx);
10926 let mut selections = selections.iter().peekable();
10927 let mut contiguous_row_selections = Vec::new();
10928 let mut new_selections = Vec::new();
10929
10930 while let Some(selection) = selections.next() {
10931 // Find all the selections that span a contiguous row range
10932 let (start_row, end_row) = consume_contiguous_rows(
10933 &mut contiguous_row_selections,
10934 selection,
10935 &display_map,
10936 &mut selections,
10937 );
10938
10939 // Move the text spanned by the row range to be after the last line of the row range
10940 if end_row.0 <= buffer.max_point().row {
10941 let range_to_move =
10942 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10943 let insertion_point = display_map
10944 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10945 .0;
10946
10947 // Don't move lines across excerpt boundaries
10948 if buffer
10949 .excerpt_containing(range_to_move.start..insertion_point)
10950 .is_some()
10951 {
10952 let mut text = String::from("\n");
10953 text.extend(buffer.text_for_range(range_to_move.clone()));
10954 text.pop(); // Drop trailing newline
10955 edits.push((
10956 buffer.anchor_after(range_to_move.start)
10957 ..buffer.anchor_before(range_to_move.end),
10958 String::new(),
10959 ));
10960 let insertion_anchor = buffer.anchor_after(insertion_point);
10961 edits.push((insertion_anchor..insertion_anchor, text));
10962
10963 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10964
10965 // Move selections down
10966 new_selections.extend(contiguous_row_selections.drain(..).map(
10967 |mut selection| {
10968 selection.start.row += row_delta;
10969 selection.end.row += row_delta;
10970 selection
10971 },
10972 ));
10973
10974 // Move folds down
10975 unfold_ranges.push(range_to_move.clone());
10976 for fold in display_map.folds_in_range(
10977 buffer.anchor_before(range_to_move.start)
10978 ..buffer.anchor_after(range_to_move.end),
10979 ) {
10980 let mut start = fold.range.start.to_point(&buffer);
10981 let mut end = fold.range.end.to_point(&buffer);
10982 start.row += row_delta;
10983 end.row += row_delta;
10984 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10985 }
10986 }
10987 }
10988
10989 // If we didn't move line(s), preserve the existing selections
10990 new_selections.append(&mut contiguous_row_selections);
10991 }
10992
10993 self.transact(window, cx, |this, window, cx| {
10994 this.unfold_ranges(&unfold_ranges, true, true, cx);
10995 this.buffer.update(cx, |buffer, cx| {
10996 for (range, text) in edits {
10997 buffer.edit([(range, text)], None, cx);
10998 }
10999 });
11000 this.fold_creases(refold_creases, true, window, cx);
11001 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11002 s.select(new_selections)
11003 });
11004 });
11005 }
11006
11007 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11008 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11009 let text_layout_details = &self.text_layout_details(window);
11010 self.transact(window, cx, |this, window, cx| {
11011 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11012 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11013 s.move_with(|display_map, selection| {
11014 if !selection.is_empty() {
11015 return;
11016 }
11017
11018 let mut head = selection.head();
11019 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11020 if head.column() == display_map.line_len(head.row()) {
11021 transpose_offset = display_map
11022 .buffer_snapshot
11023 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11024 }
11025
11026 if transpose_offset == 0 {
11027 return;
11028 }
11029
11030 *head.column_mut() += 1;
11031 head = display_map.clip_point(head, Bias::Right);
11032 let goal = SelectionGoal::HorizontalPosition(
11033 display_map
11034 .x_for_display_point(head, text_layout_details)
11035 .into(),
11036 );
11037 selection.collapse_to(head, goal);
11038
11039 let transpose_start = display_map
11040 .buffer_snapshot
11041 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11042 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11043 let transpose_end = display_map
11044 .buffer_snapshot
11045 .clip_offset(transpose_offset + 1, Bias::Right);
11046 if let Some(ch) =
11047 display_map.buffer_snapshot.chars_at(transpose_start).next()
11048 {
11049 edits.push((transpose_start..transpose_offset, String::new()));
11050 edits.push((transpose_end..transpose_end, ch.to_string()));
11051 }
11052 }
11053 });
11054 edits
11055 });
11056 this.buffer
11057 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11058 let selections = this.selections.all::<usize>(cx);
11059 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11060 s.select(selections);
11061 });
11062 });
11063 }
11064
11065 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11066 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11067 self.rewrap_impl(RewrapOptions::default(), cx)
11068 }
11069
11070 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11071 let buffer = self.buffer.read(cx).snapshot(cx);
11072 let selections = self.selections.all::<Point>(cx);
11073
11074 // Shrink and split selections to respect paragraph boundaries.
11075 let ranges = selections.into_iter().flat_map(|selection| {
11076 let language_settings = buffer.language_settings_at(selection.head(), cx);
11077 let language_scope = buffer.language_scope_at(selection.head());
11078
11079 let Some(start_row) = (selection.start.row..=selection.end.row)
11080 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11081 else {
11082 return vec![];
11083 };
11084 let Some(end_row) = (selection.start.row..=selection.end.row)
11085 .rev()
11086 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11087 else {
11088 return vec![];
11089 };
11090
11091 let mut row = start_row;
11092 let mut ranges = Vec::new();
11093 while let Some(blank_row) =
11094 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11095 {
11096 let next_paragraph_start = (blank_row + 1..=end_row)
11097 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11098 .unwrap();
11099 ranges.push((
11100 language_settings.clone(),
11101 language_scope.clone(),
11102 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11103 ));
11104 row = next_paragraph_start;
11105 }
11106 ranges.push((
11107 language_settings.clone(),
11108 language_scope.clone(),
11109 Point::new(row, 0)..Point::new(end_row, 0),
11110 ));
11111
11112 ranges
11113 });
11114
11115 let mut edits = Vec::new();
11116 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11117
11118 for (language_settings, language_scope, range) in ranges {
11119 let mut start_row = range.start.row;
11120 let mut end_row = range.end.row;
11121
11122 // Skip selections that overlap with a range that has already been rewrapped.
11123 let selection_range = start_row..end_row;
11124 if rewrapped_row_ranges
11125 .iter()
11126 .any(|range| range.overlaps(&selection_range))
11127 {
11128 continue;
11129 }
11130
11131 let tab_size = language_settings.tab_size;
11132
11133 // Since not all lines in the selection may be at the same indent
11134 // level, choose the indent size that is the most common between all
11135 // of the lines.
11136 //
11137 // If there is a tie, we use the deepest indent.
11138 let (indent_size, indent_end) = {
11139 let mut indent_size_occurrences = HashMap::default();
11140 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11141
11142 for row in start_row..=end_row {
11143 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11144 rows_by_indent_size.entry(indent).or_default().push(row);
11145 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11146 }
11147
11148 let indent_size = indent_size_occurrences
11149 .into_iter()
11150 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11151 .map(|(indent, _)| indent)
11152 .unwrap_or_default();
11153 let row = rows_by_indent_size[&indent_size][0];
11154 let indent_end = Point::new(row, indent_size.len);
11155
11156 (indent_size, indent_end)
11157 };
11158
11159 let mut line_prefix = indent_size.chars().collect::<String>();
11160
11161 let mut inside_comment = false;
11162 if let Some(comment_prefix) = language_scope.and_then(|language| {
11163 language
11164 .line_comment_prefixes()
11165 .iter()
11166 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11167 .cloned()
11168 }) {
11169 line_prefix.push_str(&comment_prefix);
11170 inside_comment = true;
11171 }
11172
11173 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11174 RewrapBehavior::InComments => inside_comment,
11175 RewrapBehavior::InSelections => !range.is_empty(),
11176 RewrapBehavior::Anywhere => true,
11177 };
11178
11179 let should_rewrap = options.override_language_settings
11180 || allow_rewrap_based_on_language
11181 || self.hard_wrap.is_some();
11182 if !should_rewrap {
11183 continue;
11184 }
11185
11186 if range.is_empty() {
11187 'expand_upwards: while start_row > 0 {
11188 let prev_row = start_row - 1;
11189 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11190 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11191 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11192 {
11193 start_row = prev_row;
11194 } else {
11195 break 'expand_upwards;
11196 }
11197 }
11198
11199 'expand_downwards: while end_row < buffer.max_point().row {
11200 let next_row = end_row + 1;
11201 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11202 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11203 && !buffer.is_line_blank(MultiBufferRow(next_row))
11204 {
11205 end_row = next_row;
11206 } else {
11207 break 'expand_downwards;
11208 }
11209 }
11210 }
11211
11212 let start = Point::new(start_row, 0);
11213 let start_offset = start.to_offset(&buffer);
11214 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11215 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11216 let Some(lines_without_prefixes) = selection_text
11217 .lines()
11218 .map(|line| {
11219 line.strip_prefix(&line_prefix)
11220 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11221 .with_context(|| {
11222 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11223 })
11224 })
11225 .collect::<Result<Vec<_>, _>>()
11226 .log_err()
11227 else {
11228 continue;
11229 };
11230
11231 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11232 buffer
11233 .language_settings_at(Point::new(start_row, 0), cx)
11234 .preferred_line_length as usize
11235 });
11236 let wrapped_text = wrap_with_prefix(
11237 line_prefix,
11238 lines_without_prefixes.join("\n"),
11239 wrap_column,
11240 tab_size,
11241 options.preserve_existing_whitespace,
11242 );
11243
11244 // TODO: should always use char-based diff while still supporting cursor behavior that
11245 // matches vim.
11246 let mut diff_options = DiffOptions::default();
11247 if options.override_language_settings {
11248 diff_options.max_word_diff_len = 0;
11249 diff_options.max_word_diff_line_count = 0;
11250 } else {
11251 diff_options.max_word_diff_len = usize::MAX;
11252 diff_options.max_word_diff_line_count = usize::MAX;
11253 }
11254
11255 for (old_range, new_text) in
11256 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11257 {
11258 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11259 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11260 edits.push((edit_start..edit_end, new_text));
11261 }
11262
11263 rewrapped_row_ranges.push(start_row..=end_row);
11264 }
11265
11266 self.buffer
11267 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11268 }
11269
11270 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11271 let mut text = String::new();
11272 let buffer = self.buffer.read(cx).snapshot(cx);
11273 let mut selections = self.selections.all::<Point>(cx);
11274 let mut clipboard_selections = Vec::with_capacity(selections.len());
11275 {
11276 let max_point = buffer.max_point();
11277 let mut is_first = true;
11278 for selection in &mut selections {
11279 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11280 if is_entire_line {
11281 selection.start = Point::new(selection.start.row, 0);
11282 if !selection.is_empty() && selection.end.column == 0 {
11283 selection.end = cmp::min(max_point, selection.end);
11284 } else {
11285 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11286 }
11287 selection.goal = SelectionGoal::None;
11288 }
11289 if is_first {
11290 is_first = false;
11291 } else {
11292 text += "\n";
11293 }
11294 let mut len = 0;
11295 for chunk in buffer.text_for_range(selection.start..selection.end) {
11296 text.push_str(chunk);
11297 len += chunk.len();
11298 }
11299 clipboard_selections.push(ClipboardSelection {
11300 len,
11301 is_entire_line,
11302 first_line_indent: buffer
11303 .indent_size_for_line(MultiBufferRow(selection.start.row))
11304 .len,
11305 });
11306 }
11307 }
11308
11309 self.transact(window, cx, |this, window, cx| {
11310 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11311 s.select(selections);
11312 });
11313 this.insert("", window, cx);
11314 });
11315 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11316 }
11317
11318 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11319 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11320 let item = self.cut_common(window, cx);
11321 cx.write_to_clipboard(item);
11322 }
11323
11324 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11325 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11326 self.change_selections(None, window, cx, |s| {
11327 s.move_with(|snapshot, sel| {
11328 if sel.is_empty() {
11329 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11330 }
11331 });
11332 });
11333 let item = self.cut_common(window, cx);
11334 cx.set_global(KillRing(item))
11335 }
11336
11337 pub fn kill_ring_yank(
11338 &mut self,
11339 _: &KillRingYank,
11340 window: &mut Window,
11341 cx: &mut Context<Self>,
11342 ) {
11343 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11344 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11345 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11346 (kill_ring.text().to_string(), kill_ring.metadata_json())
11347 } else {
11348 return;
11349 }
11350 } else {
11351 return;
11352 };
11353 self.do_paste(&text, metadata, false, window, cx);
11354 }
11355
11356 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11357 self.do_copy(true, cx);
11358 }
11359
11360 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11361 self.do_copy(false, cx);
11362 }
11363
11364 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11365 let selections = self.selections.all::<Point>(cx);
11366 let buffer = self.buffer.read(cx).read(cx);
11367 let mut text = String::new();
11368
11369 let mut clipboard_selections = Vec::with_capacity(selections.len());
11370 {
11371 let max_point = buffer.max_point();
11372 let mut is_first = true;
11373 for selection in &selections {
11374 let mut start = selection.start;
11375 let mut end = selection.end;
11376 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11377 if is_entire_line {
11378 start = Point::new(start.row, 0);
11379 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11380 }
11381
11382 let mut trimmed_selections = Vec::new();
11383 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11384 let row = MultiBufferRow(start.row);
11385 let first_indent = buffer.indent_size_for_line(row);
11386 if first_indent.len == 0 || start.column > first_indent.len {
11387 trimmed_selections.push(start..end);
11388 } else {
11389 trimmed_selections.push(
11390 Point::new(row.0, first_indent.len)
11391 ..Point::new(row.0, buffer.line_len(row)),
11392 );
11393 for row in start.row + 1..=end.row {
11394 let mut line_len = buffer.line_len(MultiBufferRow(row));
11395 if row == end.row {
11396 line_len = end.column;
11397 }
11398 if line_len == 0 {
11399 trimmed_selections
11400 .push(Point::new(row, 0)..Point::new(row, line_len));
11401 continue;
11402 }
11403 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11404 if row_indent_size.len >= first_indent.len {
11405 trimmed_selections.push(
11406 Point::new(row, first_indent.len)..Point::new(row, line_len),
11407 );
11408 } else {
11409 trimmed_selections.clear();
11410 trimmed_selections.push(start..end);
11411 break;
11412 }
11413 }
11414 }
11415 } else {
11416 trimmed_selections.push(start..end);
11417 }
11418
11419 for trimmed_range in trimmed_selections {
11420 if is_first {
11421 is_first = false;
11422 } else {
11423 text += "\n";
11424 }
11425 let mut len = 0;
11426 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11427 text.push_str(chunk);
11428 len += chunk.len();
11429 }
11430 clipboard_selections.push(ClipboardSelection {
11431 len,
11432 is_entire_line,
11433 first_line_indent: buffer
11434 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11435 .len,
11436 });
11437 }
11438 }
11439 }
11440
11441 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11442 text,
11443 clipboard_selections,
11444 ));
11445 }
11446
11447 pub fn do_paste(
11448 &mut self,
11449 text: &String,
11450 clipboard_selections: Option<Vec<ClipboardSelection>>,
11451 handle_entire_lines: bool,
11452 window: &mut Window,
11453 cx: &mut Context<Self>,
11454 ) {
11455 if self.read_only(cx) {
11456 return;
11457 }
11458
11459 let clipboard_text = Cow::Borrowed(text);
11460
11461 self.transact(window, cx, |this, window, cx| {
11462 if let Some(mut clipboard_selections) = clipboard_selections {
11463 let old_selections = this.selections.all::<usize>(cx);
11464 let all_selections_were_entire_line =
11465 clipboard_selections.iter().all(|s| s.is_entire_line);
11466 let first_selection_indent_column =
11467 clipboard_selections.first().map(|s| s.first_line_indent);
11468 if clipboard_selections.len() != old_selections.len() {
11469 clipboard_selections.drain(..);
11470 }
11471 let cursor_offset = this.selections.last::<usize>(cx).head();
11472 let mut auto_indent_on_paste = true;
11473
11474 this.buffer.update(cx, |buffer, cx| {
11475 let snapshot = buffer.read(cx);
11476 auto_indent_on_paste = snapshot
11477 .language_settings_at(cursor_offset, cx)
11478 .auto_indent_on_paste;
11479
11480 let mut start_offset = 0;
11481 let mut edits = Vec::new();
11482 let mut original_indent_columns = Vec::new();
11483 for (ix, selection) in old_selections.iter().enumerate() {
11484 let to_insert;
11485 let entire_line;
11486 let original_indent_column;
11487 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11488 let end_offset = start_offset + clipboard_selection.len;
11489 to_insert = &clipboard_text[start_offset..end_offset];
11490 entire_line = clipboard_selection.is_entire_line;
11491 start_offset = end_offset + 1;
11492 original_indent_column = Some(clipboard_selection.first_line_indent);
11493 } else {
11494 to_insert = clipboard_text.as_str();
11495 entire_line = all_selections_were_entire_line;
11496 original_indent_column = first_selection_indent_column
11497 }
11498
11499 // If the corresponding selection was empty when this slice of the
11500 // clipboard text was written, then the entire line containing the
11501 // selection was copied. If this selection is also currently empty,
11502 // then paste the line before the current line of the buffer.
11503 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11504 let column = selection.start.to_point(&snapshot).column as usize;
11505 let line_start = selection.start - column;
11506 line_start..line_start
11507 } else {
11508 selection.range()
11509 };
11510
11511 edits.push((range, to_insert));
11512 original_indent_columns.push(original_indent_column);
11513 }
11514 drop(snapshot);
11515
11516 buffer.edit(
11517 edits,
11518 if auto_indent_on_paste {
11519 Some(AutoindentMode::Block {
11520 original_indent_columns,
11521 })
11522 } else {
11523 None
11524 },
11525 cx,
11526 );
11527 });
11528
11529 let selections = this.selections.all::<usize>(cx);
11530 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11531 s.select(selections)
11532 });
11533 } else {
11534 this.insert(&clipboard_text, window, cx);
11535 }
11536 });
11537 }
11538
11539 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11540 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11541 if let Some(item) = cx.read_from_clipboard() {
11542 let entries = item.entries();
11543
11544 match entries.first() {
11545 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11546 // of all the pasted entries.
11547 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11548 .do_paste(
11549 clipboard_string.text(),
11550 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11551 true,
11552 window,
11553 cx,
11554 ),
11555 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11556 }
11557 }
11558 }
11559
11560 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11561 if self.read_only(cx) {
11562 return;
11563 }
11564
11565 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11566
11567 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11568 if let Some((selections, _)) =
11569 self.selection_history.transaction(transaction_id).cloned()
11570 {
11571 self.change_selections(None, window, cx, |s| {
11572 s.select_anchors(selections.to_vec());
11573 });
11574 } else {
11575 log::error!(
11576 "No entry in selection_history found for undo. \
11577 This may correspond to a bug where undo does not update the selection. \
11578 If this is occurring, please add details to \
11579 https://github.com/zed-industries/zed/issues/22692"
11580 );
11581 }
11582 self.request_autoscroll(Autoscroll::fit(), cx);
11583 self.unmark_text(window, cx);
11584 self.refresh_inline_completion(true, false, window, cx);
11585 cx.emit(EditorEvent::Edited { transaction_id });
11586 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11587 }
11588 }
11589
11590 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11591 if self.read_only(cx) {
11592 return;
11593 }
11594
11595 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11596
11597 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11598 if let Some((_, Some(selections))) =
11599 self.selection_history.transaction(transaction_id).cloned()
11600 {
11601 self.change_selections(None, window, cx, |s| {
11602 s.select_anchors(selections.to_vec());
11603 });
11604 } else {
11605 log::error!(
11606 "No entry in selection_history found for redo. \
11607 This may correspond to a bug where undo does not update the selection. \
11608 If this is occurring, please add details to \
11609 https://github.com/zed-industries/zed/issues/22692"
11610 );
11611 }
11612 self.request_autoscroll(Autoscroll::fit(), cx);
11613 self.unmark_text(window, cx);
11614 self.refresh_inline_completion(true, false, window, cx);
11615 cx.emit(EditorEvent::Edited { transaction_id });
11616 }
11617 }
11618
11619 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11620 self.buffer
11621 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11622 }
11623
11624 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11625 self.buffer
11626 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11627 }
11628
11629 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11630 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11631 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11632 s.move_with(|map, selection| {
11633 let cursor = if selection.is_empty() {
11634 movement::left(map, selection.start)
11635 } else {
11636 selection.start
11637 };
11638 selection.collapse_to(cursor, SelectionGoal::None);
11639 });
11640 })
11641 }
11642
11643 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11644 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11645 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11646 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11647 })
11648 }
11649
11650 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11653 s.move_with(|map, selection| {
11654 let cursor = if selection.is_empty() {
11655 movement::right(map, selection.end)
11656 } else {
11657 selection.end
11658 };
11659 selection.collapse_to(cursor, SelectionGoal::None)
11660 });
11661 })
11662 }
11663
11664 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11665 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11666 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11667 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11668 })
11669 }
11670
11671 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11672 if self.take_rename(true, window, cx).is_some() {
11673 return;
11674 }
11675
11676 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11677 cx.propagate();
11678 return;
11679 }
11680
11681 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11682
11683 let text_layout_details = &self.text_layout_details(window);
11684 let selection_count = self.selections.count();
11685 let first_selection = self.selections.first_anchor();
11686
11687 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11688 s.move_with(|map, selection| {
11689 if !selection.is_empty() {
11690 selection.goal = SelectionGoal::None;
11691 }
11692 let (cursor, goal) = movement::up(
11693 map,
11694 selection.start,
11695 selection.goal,
11696 false,
11697 text_layout_details,
11698 );
11699 selection.collapse_to(cursor, goal);
11700 });
11701 });
11702
11703 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11704 {
11705 cx.propagate();
11706 }
11707 }
11708
11709 pub fn move_up_by_lines(
11710 &mut self,
11711 action: &MoveUpByLines,
11712 window: &mut Window,
11713 cx: &mut Context<Self>,
11714 ) {
11715 if self.take_rename(true, window, cx).is_some() {
11716 return;
11717 }
11718
11719 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11720 cx.propagate();
11721 return;
11722 }
11723
11724 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11725
11726 let text_layout_details = &self.text_layout_details(window);
11727
11728 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11729 s.move_with(|map, selection| {
11730 if !selection.is_empty() {
11731 selection.goal = SelectionGoal::None;
11732 }
11733 let (cursor, goal) = movement::up_by_rows(
11734 map,
11735 selection.start,
11736 action.lines,
11737 selection.goal,
11738 false,
11739 text_layout_details,
11740 );
11741 selection.collapse_to(cursor, goal);
11742 });
11743 })
11744 }
11745
11746 pub fn move_down_by_lines(
11747 &mut self,
11748 action: &MoveDownByLines,
11749 window: &mut Window,
11750 cx: &mut Context<Self>,
11751 ) {
11752 if self.take_rename(true, window, cx).is_some() {
11753 return;
11754 }
11755
11756 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11757 cx.propagate();
11758 return;
11759 }
11760
11761 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11762
11763 let text_layout_details = &self.text_layout_details(window);
11764
11765 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11766 s.move_with(|map, selection| {
11767 if !selection.is_empty() {
11768 selection.goal = SelectionGoal::None;
11769 }
11770 let (cursor, goal) = movement::down_by_rows(
11771 map,
11772 selection.start,
11773 action.lines,
11774 selection.goal,
11775 false,
11776 text_layout_details,
11777 );
11778 selection.collapse_to(cursor, goal);
11779 });
11780 })
11781 }
11782
11783 pub fn select_down_by_lines(
11784 &mut self,
11785 action: &SelectDownByLines,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) {
11789 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11790 let text_layout_details = &self.text_layout_details(window);
11791 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11792 s.move_heads_with(|map, head, goal| {
11793 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11794 })
11795 })
11796 }
11797
11798 pub fn select_up_by_lines(
11799 &mut self,
11800 action: &SelectUpByLines,
11801 window: &mut Window,
11802 cx: &mut Context<Self>,
11803 ) {
11804 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11805 let text_layout_details = &self.text_layout_details(window);
11806 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11807 s.move_heads_with(|map, head, goal| {
11808 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11809 })
11810 })
11811 }
11812
11813 pub fn select_page_up(
11814 &mut self,
11815 _: &SelectPageUp,
11816 window: &mut Window,
11817 cx: &mut Context<Self>,
11818 ) {
11819 let Some(row_count) = self.visible_row_count() else {
11820 return;
11821 };
11822
11823 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11824
11825 let text_layout_details = &self.text_layout_details(window);
11826
11827 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11828 s.move_heads_with(|map, head, goal| {
11829 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11830 })
11831 })
11832 }
11833
11834 pub fn move_page_up(
11835 &mut self,
11836 action: &MovePageUp,
11837 window: &mut Window,
11838 cx: &mut Context<Self>,
11839 ) {
11840 if self.take_rename(true, window, cx).is_some() {
11841 return;
11842 }
11843
11844 if self
11845 .context_menu
11846 .borrow_mut()
11847 .as_mut()
11848 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11849 .unwrap_or(false)
11850 {
11851 return;
11852 }
11853
11854 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11855 cx.propagate();
11856 return;
11857 }
11858
11859 let Some(row_count) = self.visible_row_count() else {
11860 return;
11861 };
11862
11863 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11864
11865 let autoscroll = if action.center_cursor {
11866 Autoscroll::center()
11867 } else {
11868 Autoscroll::fit()
11869 };
11870
11871 let text_layout_details = &self.text_layout_details(window);
11872
11873 self.change_selections(Some(autoscroll), window, cx, |s| {
11874 s.move_with(|map, selection| {
11875 if !selection.is_empty() {
11876 selection.goal = SelectionGoal::None;
11877 }
11878 let (cursor, goal) = movement::up_by_rows(
11879 map,
11880 selection.end,
11881 row_count,
11882 selection.goal,
11883 false,
11884 text_layout_details,
11885 );
11886 selection.collapse_to(cursor, goal);
11887 });
11888 });
11889 }
11890
11891 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11893 let text_layout_details = &self.text_layout_details(window);
11894 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11895 s.move_heads_with(|map, head, goal| {
11896 movement::up(map, head, goal, false, text_layout_details)
11897 })
11898 })
11899 }
11900
11901 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11902 self.take_rename(true, window, cx);
11903
11904 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11905 cx.propagate();
11906 return;
11907 }
11908
11909 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11910
11911 let text_layout_details = &self.text_layout_details(window);
11912 let selection_count = self.selections.count();
11913 let first_selection = self.selections.first_anchor();
11914
11915 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11916 s.move_with(|map, selection| {
11917 if !selection.is_empty() {
11918 selection.goal = SelectionGoal::None;
11919 }
11920 let (cursor, goal) = movement::down(
11921 map,
11922 selection.end,
11923 selection.goal,
11924 false,
11925 text_layout_details,
11926 );
11927 selection.collapse_to(cursor, goal);
11928 });
11929 });
11930
11931 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11932 {
11933 cx.propagate();
11934 }
11935 }
11936
11937 pub fn select_page_down(
11938 &mut self,
11939 _: &SelectPageDown,
11940 window: &mut Window,
11941 cx: &mut Context<Self>,
11942 ) {
11943 let Some(row_count) = self.visible_row_count() else {
11944 return;
11945 };
11946
11947 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11948
11949 let text_layout_details = &self.text_layout_details(window);
11950
11951 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11952 s.move_heads_with(|map, head, goal| {
11953 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11954 })
11955 })
11956 }
11957
11958 pub fn move_page_down(
11959 &mut self,
11960 action: &MovePageDown,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 if self.take_rename(true, window, cx).is_some() {
11965 return;
11966 }
11967
11968 if self
11969 .context_menu
11970 .borrow_mut()
11971 .as_mut()
11972 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11973 .unwrap_or(false)
11974 {
11975 return;
11976 }
11977
11978 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11979 cx.propagate();
11980 return;
11981 }
11982
11983 let Some(row_count) = self.visible_row_count() else {
11984 return;
11985 };
11986
11987 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11988
11989 let autoscroll = if action.center_cursor {
11990 Autoscroll::center()
11991 } else {
11992 Autoscroll::fit()
11993 };
11994
11995 let text_layout_details = &self.text_layout_details(window);
11996 self.change_selections(Some(autoscroll), window, cx, |s| {
11997 s.move_with(|map, selection| {
11998 if !selection.is_empty() {
11999 selection.goal = SelectionGoal::None;
12000 }
12001 let (cursor, goal) = movement::down_by_rows(
12002 map,
12003 selection.end,
12004 row_count,
12005 selection.goal,
12006 false,
12007 text_layout_details,
12008 );
12009 selection.collapse_to(cursor, goal);
12010 });
12011 });
12012 }
12013
12014 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12015 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12016 let text_layout_details = &self.text_layout_details(window);
12017 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12018 s.move_heads_with(|map, head, goal| {
12019 movement::down(map, head, goal, false, text_layout_details)
12020 })
12021 });
12022 }
12023
12024 pub fn context_menu_first(
12025 &mut self,
12026 _: &ContextMenuFirst,
12027 window: &mut Window,
12028 cx: &mut Context<Self>,
12029 ) {
12030 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12031 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12032 }
12033 }
12034
12035 pub fn context_menu_prev(
12036 &mut self,
12037 _: &ContextMenuPrevious,
12038 window: &mut Window,
12039 cx: &mut Context<Self>,
12040 ) {
12041 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12042 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12043 }
12044 }
12045
12046 pub fn context_menu_next(
12047 &mut self,
12048 _: &ContextMenuNext,
12049 window: &mut Window,
12050 cx: &mut Context<Self>,
12051 ) {
12052 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12053 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12054 }
12055 }
12056
12057 pub fn context_menu_last(
12058 &mut self,
12059 _: &ContextMenuLast,
12060 window: &mut Window,
12061 cx: &mut Context<Self>,
12062 ) {
12063 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12064 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12065 }
12066 }
12067
12068 pub fn move_to_previous_word_start(
12069 &mut self,
12070 _: &MoveToPreviousWordStart,
12071 window: &mut Window,
12072 cx: &mut Context<Self>,
12073 ) {
12074 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12075 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12076 s.move_cursors_with(|map, head, _| {
12077 (
12078 movement::previous_word_start(map, head),
12079 SelectionGoal::None,
12080 )
12081 });
12082 })
12083 }
12084
12085 pub fn move_to_previous_subword_start(
12086 &mut self,
12087 _: &MoveToPreviousSubwordStart,
12088 window: &mut Window,
12089 cx: &mut Context<Self>,
12090 ) {
12091 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12092 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12093 s.move_cursors_with(|map, head, _| {
12094 (
12095 movement::previous_subword_start(map, head),
12096 SelectionGoal::None,
12097 )
12098 });
12099 })
12100 }
12101
12102 pub fn select_to_previous_word_start(
12103 &mut self,
12104 _: &SelectToPreviousWordStart,
12105 window: &mut Window,
12106 cx: &mut Context<Self>,
12107 ) {
12108 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12109 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12110 s.move_heads_with(|map, head, _| {
12111 (
12112 movement::previous_word_start(map, head),
12113 SelectionGoal::None,
12114 )
12115 });
12116 })
12117 }
12118
12119 pub fn select_to_previous_subword_start(
12120 &mut self,
12121 _: &SelectToPreviousSubwordStart,
12122 window: &mut Window,
12123 cx: &mut Context<Self>,
12124 ) {
12125 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12126 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12127 s.move_heads_with(|map, head, _| {
12128 (
12129 movement::previous_subword_start(map, head),
12130 SelectionGoal::None,
12131 )
12132 });
12133 })
12134 }
12135
12136 pub fn delete_to_previous_word_start(
12137 &mut self,
12138 action: &DeleteToPreviousWordStart,
12139 window: &mut Window,
12140 cx: &mut Context<Self>,
12141 ) {
12142 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12143 self.transact(window, cx, |this, window, cx| {
12144 this.select_autoclose_pair(window, cx);
12145 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12146 s.move_with(|map, selection| {
12147 if selection.is_empty() {
12148 let cursor = if action.ignore_newlines {
12149 movement::previous_word_start(map, selection.head())
12150 } else {
12151 movement::previous_word_start_or_newline(map, selection.head())
12152 };
12153 selection.set_head(cursor, SelectionGoal::None);
12154 }
12155 });
12156 });
12157 this.insert("", window, cx);
12158 });
12159 }
12160
12161 pub fn delete_to_previous_subword_start(
12162 &mut self,
12163 _: &DeleteToPreviousSubwordStart,
12164 window: &mut Window,
12165 cx: &mut Context<Self>,
12166 ) {
12167 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12168 self.transact(window, cx, |this, window, cx| {
12169 this.select_autoclose_pair(window, cx);
12170 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12171 s.move_with(|map, selection| {
12172 if selection.is_empty() {
12173 let cursor = movement::previous_subword_start(map, selection.head());
12174 selection.set_head(cursor, SelectionGoal::None);
12175 }
12176 });
12177 });
12178 this.insert("", window, cx);
12179 });
12180 }
12181
12182 pub fn move_to_next_word_end(
12183 &mut self,
12184 _: &MoveToNextWordEnd,
12185 window: &mut Window,
12186 cx: &mut Context<Self>,
12187 ) {
12188 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12189 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12190 s.move_cursors_with(|map, head, _| {
12191 (movement::next_word_end(map, head), SelectionGoal::None)
12192 });
12193 })
12194 }
12195
12196 pub fn move_to_next_subword_end(
12197 &mut self,
12198 _: &MoveToNextSubwordEnd,
12199 window: &mut Window,
12200 cx: &mut Context<Self>,
12201 ) {
12202 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12203 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12204 s.move_cursors_with(|map, head, _| {
12205 (movement::next_subword_end(map, head), SelectionGoal::None)
12206 });
12207 })
12208 }
12209
12210 pub fn select_to_next_word_end(
12211 &mut self,
12212 _: &SelectToNextWordEnd,
12213 window: &mut Window,
12214 cx: &mut Context<Self>,
12215 ) {
12216 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12218 s.move_heads_with(|map, head, _| {
12219 (movement::next_word_end(map, head), SelectionGoal::None)
12220 });
12221 })
12222 }
12223
12224 pub fn select_to_next_subword_end(
12225 &mut self,
12226 _: &SelectToNextSubwordEnd,
12227 window: &mut Window,
12228 cx: &mut Context<Self>,
12229 ) {
12230 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12231 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12232 s.move_heads_with(|map, head, _| {
12233 (movement::next_subword_end(map, head), SelectionGoal::None)
12234 });
12235 })
12236 }
12237
12238 pub fn delete_to_next_word_end(
12239 &mut self,
12240 action: &DeleteToNextWordEnd,
12241 window: &mut Window,
12242 cx: &mut Context<Self>,
12243 ) {
12244 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12245 self.transact(window, cx, |this, window, cx| {
12246 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12247 s.move_with(|map, selection| {
12248 if selection.is_empty() {
12249 let cursor = if action.ignore_newlines {
12250 movement::next_word_end(map, selection.head())
12251 } else {
12252 movement::next_word_end_or_newline(map, selection.head())
12253 };
12254 selection.set_head(cursor, SelectionGoal::None);
12255 }
12256 });
12257 });
12258 this.insert("", window, cx);
12259 });
12260 }
12261
12262 pub fn delete_to_next_subword_end(
12263 &mut self,
12264 _: &DeleteToNextSubwordEnd,
12265 window: &mut Window,
12266 cx: &mut Context<Self>,
12267 ) {
12268 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12269 self.transact(window, cx, |this, window, cx| {
12270 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12271 s.move_with(|map, selection| {
12272 if selection.is_empty() {
12273 let cursor = movement::next_subword_end(map, selection.head());
12274 selection.set_head(cursor, SelectionGoal::None);
12275 }
12276 });
12277 });
12278 this.insert("", window, cx);
12279 });
12280 }
12281
12282 pub fn move_to_beginning_of_line(
12283 &mut self,
12284 action: &MoveToBeginningOfLine,
12285 window: &mut Window,
12286 cx: &mut Context<Self>,
12287 ) {
12288 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12289 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12290 s.move_cursors_with(|map, head, _| {
12291 (
12292 movement::indented_line_beginning(
12293 map,
12294 head,
12295 action.stop_at_soft_wraps,
12296 action.stop_at_indent,
12297 ),
12298 SelectionGoal::None,
12299 )
12300 });
12301 })
12302 }
12303
12304 pub fn select_to_beginning_of_line(
12305 &mut self,
12306 action: &SelectToBeginningOfLine,
12307 window: &mut Window,
12308 cx: &mut Context<Self>,
12309 ) {
12310 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12311 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12312 s.move_heads_with(|map, head, _| {
12313 (
12314 movement::indented_line_beginning(
12315 map,
12316 head,
12317 action.stop_at_soft_wraps,
12318 action.stop_at_indent,
12319 ),
12320 SelectionGoal::None,
12321 )
12322 });
12323 });
12324 }
12325
12326 pub fn delete_to_beginning_of_line(
12327 &mut self,
12328 action: &DeleteToBeginningOfLine,
12329 window: &mut Window,
12330 cx: &mut Context<Self>,
12331 ) {
12332 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12333 self.transact(window, cx, |this, window, cx| {
12334 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12335 s.move_with(|_, selection| {
12336 selection.reversed = true;
12337 });
12338 });
12339
12340 this.select_to_beginning_of_line(
12341 &SelectToBeginningOfLine {
12342 stop_at_soft_wraps: false,
12343 stop_at_indent: action.stop_at_indent,
12344 },
12345 window,
12346 cx,
12347 );
12348 this.backspace(&Backspace, window, cx);
12349 });
12350 }
12351
12352 pub fn move_to_end_of_line(
12353 &mut self,
12354 action: &MoveToEndOfLine,
12355 window: &mut Window,
12356 cx: &mut Context<Self>,
12357 ) {
12358 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12359 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12360 s.move_cursors_with(|map, head, _| {
12361 (
12362 movement::line_end(map, head, action.stop_at_soft_wraps),
12363 SelectionGoal::None,
12364 )
12365 });
12366 })
12367 }
12368
12369 pub fn select_to_end_of_line(
12370 &mut self,
12371 action: &SelectToEndOfLine,
12372 window: &mut Window,
12373 cx: &mut Context<Self>,
12374 ) {
12375 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12376 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12377 s.move_heads_with(|map, head, _| {
12378 (
12379 movement::line_end(map, head, action.stop_at_soft_wraps),
12380 SelectionGoal::None,
12381 )
12382 });
12383 })
12384 }
12385
12386 pub fn delete_to_end_of_line(
12387 &mut self,
12388 _: &DeleteToEndOfLine,
12389 window: &mut Window,
12390 cx: &mut Context<Self>,
12391 ) {
12392 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12393 self.transact(window, cx, |this, window, cx| {
12394 this.select_to_end_of_line(
12395 &SelectToEndOfLine {
12396 stop_at_soft_wraps: false,
12397 },
12398 window,
12399 cx,
12400 );
12401 this.delete(&Delete, window, cx);
12402 });
12403 }
12404
12405 pub fn cut_to_end_of_line(
12406 &mut self,
12407 _: &CutToEndOfLine,
12408 window: &mut Window,
12409 cx: &mut Context<Self>,
12410 ) {
12411 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12412 self.transact(window, cx, |this, window, cx| {
12413 this.select_to_end_of_line(
12414 &SelectToEndOfLine {
12415 stop_at_soft_wraps: false,
12416 },
12417 window,
12418 cx,
12419 );
12420 this.cut(&Cut, window, cx);
12421 });
12422 }
12423
12424 pub fn move_to_start_of_paragraph(
12425 &mut self,
12426 _: &MoveToStartOfParagraph,
12427 window: &mut Window,
12428 cx: &mut Context<Self>,
12429 ) {
12430 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12431 cx.propagate();
12432 return;
12433 }
12434 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12435 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12436 s.move_with(|map, selection| {
12437 selection.collapse_to(
12438 movement::start_of_paragraph(map, selection.head(), 1),
12439 SelectionGoal::None,
12440 )
12441 });
12442 })
12443 }
12444
12445 pub fn move_to_end_of_paragraph(
12446 &mut self,
12447 _: &MoveToEndOfParagraph,
12448 window: &mut Window,
12449 cx: &mut Context<Self>,
12450 ) {
12451 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12452 cx.propagate();
12453 return;
12454 }
12455 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12456 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12457 s.move_with(|map, selection| {
12458 selection.collapse_to(
12459 movement::end_of_paragraph(map, selection.head(), 1),
12460 SelectionGoal::None,
12461 )
12462 });
12463 })
12464 }
12465
12466 pub fn select_to_start_of_paragraph(
12467 &mut self,
12468 _: &SelectToStartOfParagraph,
12469 window: &mut Window,
12470 cx: &mut Context<Self>,
12471 ) {
12472 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12473 cx.propagate();
12474 return;
12475 }
12476 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12477 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12478 s.move_heads_with(|map, head, _| {
12479 (
12480 movement::start_of_paragraph(map, head, 1),
12481 SelectionGoal::None,
12482 )
12483 });
12484 })
12485 }
12486
12487 pub fn select_to_end_of_paragraph(
12488 &mut self,
12489 _: &SelectToEndOfParagraph,
12490 window: &mut Window,
12491 cx: &mut Context<Self>,
12492 ) {
12493 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12494 cx.propagate();
12495 return;
12496 }
12497 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12498 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12499 s.move_heads_with(|map, head, _| {
12500 (
12501 movement::end_of_paragraph(map, head, 1),
12502 SelectionGoal::None,
12503 )
12504 });
12505 })
12506 }
12507
12508 pub fn move_to_start_of_excerpt(
12509 &mut self,
12510 _: &MoveToStartOfExcerpt,
12511 window: &mut Window,
12512 cx: &mut Context<Self>,
12513 ) {
12514 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12515 cx.propagate();
12516 return;
12517 }
12518 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12519 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12520 s.move_with(|map, selection| {
12521 selection.collapse_to(
12522 movement::start_of_excerpt(
12523 map,
12524 selection.head(),
12525 workspace::searchable::Direction::Prev,
12526 ),
12527 SelectionGoal::None,
12528 )
12529 });
12530 })
12531 }
12532
12533 pub fn move_to_start_of_next_excerpt(
12534 &mut self,
12535 _: &MoveToStartOfNextExcerpt,
12536 window: &mut Window,
12537 cx: &mut Context<Self>,
12538 ) {
12539 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12540 cx.propagate();
12541 return;
12542 }
12543
12544 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12545 s.move_with(|map, selection| {
12546 selection.collapse_to(
12547 movement::start_of_excerpt(
12548 map,
12549 selection.head(),
12550 workspace::searchable::Direction::Next,
12551 ),
12552 SelectionGoal::None,
12553 )
12554 });
12555 })
12556 }
12557
12558 pub fn move_to_end_of_excerpt(
12559 &mut self,
12560 _: &MoveToEndOfExcerpt,
12561 window: &mut Window,
12562 cx: &mut Context<Self>,
12563 ) {
12564 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12565 cx.propagate();
12566 return;
12567 }
12568 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12569 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12570 s.move_with(|map, selection| {
12571 selection.collapse_to(
12572 movement::end_of_excerpt(
12573 map,
12574 selection.head(),
12575 workspace::searchable::Direction::Next,
12576 ),
12577 SelectionGoal::None,
12578 )
12579 });
12580 })
12581 }
12582
12583 pub fn move_to_end_of_previous_excerpt(
12584 &mut self,
12585 _: &MoveToEndOfPreviousExcerpt,
12586 window: &mut Window,
12587 cx: &mut Context<Self>,
12588 ) {
12589 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12590 cx.propagate();
12591 return;
12592 }
12593 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12594 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12595 s.move_with(|map, selection| {
12596 selection.collapse_to(
12597 movement::end_of_excerpt(
12598 map,
12599 selection.head(),
12600 workspace::searchable::Direction::Prev,
12601 ),
12602 SelectionGoal::None,
12603 )
12604 });
12605 })
12606 }
12607
12608 pub fn select_to_start_of_excerpt(
12609 &mut self,
12610 _: &SelectToStartOfExcerpt,
12611 window: &mut Window,
12612 cx: &mut Context<Self>,
12613 ) {
12614 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12615 cx.propagate();
12616 return;
12617 }
12618 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12619 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12620 s.move_heads_with(|map, head, _| {
12621 (
12622 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12623 SelectionGoal::None,
12624 )
12625 });
12626 })
12627 }
12628
12629 pub fn select_to_start_of_next_excerpt(
12630 &mut self,
12631 _: &SelectToStartOfNextExcerpt,
12632 window: &mut Window,
12633 cx: &mut Context<Self>,
12634 ) {
12635 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12636 cx.propagate();
12637 return;
12638 }
12639 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12640 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12641 s.move_heads_with(|map, head, _| {
12642 (
12643 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12644 SelectionGoal::None,
12645 )
12646 });
12647 })
12648 }
12649
12650 pub fn select_to_end_of_excerpt(
12651 &mut self,
12652 _: &SelectToEndOfExcerpt,
12653 window: &mut Window,
12654 cx: &mut Context<Self>,
12655 ) {
12656 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12657 cx.propagate();
12658 return;
12659 }
12660 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12662 s.move_heads_with(|map, head, _| {
12663 (
12664 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12665 SelectionGoal::None,
12666 )
12667 });
12668 })
12669 }
12670
12671 pub fn select_to_end_of_previous_excerpt(
12672 &mut self,
12673 _: &SelectToEndOfPreviousExcerpt,
12674 window: &mut Window,
12675 cx: &mut Context<Self>,
12676 ) {
12677 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12678 cx.propagate();
12679 return;
12680 }
12681 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12682 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12683 s.move_heads_with(|map, head, _| {
12684 (
12685 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12686 SelectionGoal::None,
12687 )
12688 });
12689 })
12690 }
12691
12692 pub fn move_to_beginning(
12693 &mut self,
12694 _: &MoveToBeginning,
12695 window: &mut Window,
12696 cx: &mut Context<Self>,
12697 ) {
12698 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12699 cx.propagate();
12700 return;
12701 }
12702 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12703 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12704 s.select_ranges(vec![0..0]);
12705 });
12706 }
12707
12708 pub fn select_to_beginning(
12709 &mut self,
12710 _: &SelectToBeginning,
12711 window: &mut Window,
12712 cx: &mut Context<Self>,
12713 ) {
12714 let mut selection = self.selections.last::<Point>(cx);
12715 selection.set_head(Point::zero(), SelectionGoal::None);
12716 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12717 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12718 s.select(vec![selection]);
12719 });
12720 }
12721
12722 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12723 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12724 cx.propagate();
12725 return;
12726 }
12727 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12728 let cursor = self.buffer.read(cx).read(cx).len();
12729 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12730 s.select_ranges(vec![cursor..cursor])
12731 });
12732 }
12733
12734 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12735 self.nav_history = nav_history;
12736 }
12737
12738 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12739 self.nav_history.as_ref()
12740 }
12741
12742 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12743 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12744 }
12745
12746 fn push_to_nav_history(
12747 &mut self,
12748 cursor_anchor: Anchor,
12749 new_position: Option<Point>,
12750 is_deactivate: bool,
12751 cx: &mut Context<Self>,
12752 ) {
12753 if let Some(nav_history) = self.nav_history.as_mut() {
12754 let buffer = self.buffer.read(cx).read(cx);
12755 let cursor_position = cursor_anchor.to_point(&buffer);
12756 let scroll_state = self.scroll_manager.anchor();
12757 let scroll_top_row = scroll_state.top_row(&buffer);
12758 drop(buffer);
12759
12760 if let Some(new_position) = new_position {
12761 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12762 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12763 return;
12764 }
12765 }
12766
12767 nav_history.push(
12768 Some(NavigationData {
12769 cursor_anchor,
12770 cursor_position,
12771 scroll_anchor: scroll_state,
12772 scroll_top_row,
12773 }),
12774 cx,
12775 );
12776 cx.emit(EditorEvent::PushedToNavHistory {
12777 anchor: cursor_anchor,
12778 is_deactivate,
12779 })
12780 }
12781 }
12782
12783 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12784 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12785 let buffer = self.buffer.read(cx).snapshot(cx);
12786 let mut selection = self.selections.first::<usize>(cx);
12787 selection.set_head(buffer.len(), SelectionGoal::None);
12788 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12789 s.select(vec![selection]);
12790 });
12791 }
12792
12793 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12794 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12795 let end = self.buffer.read(cx).read(cx).len();
12796 self.change_selections(None, window, cx, |s| {
12797 s.select_ranges(vec![0..end]);
12798 });
12799 }
12800
12801 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12802 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12803 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12804 let mut selections = self.selections.all::<Point>(cx);
12805 let max_point = display_map.buffer_snapshot.max_point();
12806 for selection in &mut selections {
12807 let rows = selection.spanned_rows(true, &display_map);
12808 selection.start = Point::new(rows.start.0, 0);
12809 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12810 selection.reversed = false;
12811 }
12812 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12813 s.select(selections);
12814 });
12815 }
12816
12817 pub fn split_selection_into_lines(
12818 &mut self,
12819 _: &SplitSelectionIntoLines,
12820 window: &mut Window,
12821 cx: &mut Context<Self>,
12822 ) {
12823 let selections = self
12824 .selections
12825 .all::<Point>(cx)
12826 .into_iter()
12827 .map(|selection| selection.start..selection.end)
12828 .collect::<Vec<_>>();
12829 self.unfold_ranges(&selections, true, true, cx);
12830
12831 let mut new_selection_ranges = Vec::new();
12832 {
12833 let buffer = self.buffer.read(cx).read(cx);
12834 for selection in selections {
12835 for row in selection.start.row..selection.end.row {
12836 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12837 new_selection_ranges.push(cursor..cursor);
12838 }
12839
12840 let is_multiline_selection = selection.start.row != selection.end.row;
12841 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12842 // so this action feels more ergonomic when paired with other selection operations
12843 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12844 if !should_skip_last {
12845 new_selection_ranges.push(selection.end..selection.end);
12846 }
12847 }
12848 }
12849 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12850 s.select_ranges(new_selection_ranges);
12851 });
12852 }
12853
12854 pub fn add_selection_above(
12855 &mut self,
12856 _: &AddSelectionAbove,
12857 window: &mut Window,
12858 cx: &mut Context<Self>,
12859 ) {
12860 self.add_selection(true, window, cx);
12861 }
12862
12863 pub fn add_selection_below(
12864 &mut self,
12865 _: &AddSelectionBelow,
12866 window: &mut Window,
12867 cx: &mut Context<Self>,
12868 ) {
12869 self.add_selection(false, window, cx);
12870 }
12871
12872 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12873 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12874
12875 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12876 let all_selections = self.selections.all::<Point>(cx);
12877 let text_layout_details = self.text_layout_details(window);
12878
12879 let (mut columnar_selections, new_selections_to_columnarize) = {
12880 if let Some(state) = self.add_selections_state.as_ref() {
12881 let columnar_selection_ids: HashSet<_> = state
12882 .groups
12883 .iter()
12884 .flat_map(|group| group.stack.iter())
12885 .copied()
12886 .collect();
12887
12888 all_selections
12889 .into_iter()
12890 .partition(|s| columnar_selection_ids.contains(&s.id))
12891 } else {
12892 (Vec::new(), all_selections)
12893 }
12894 };
12895
12896 let mut state = self
12897 .add_selections_state
12898 .take()
12899 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12900
12901 for selection in new_selections_to_columnarize {
12902 let range = selection.display_range(&display_map).sorted();
12903 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12904 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12905 let positions = start_x.min(end_x)..start_x.max(end_x);
12906 let mut stack = Vec::new();
12907 for row in range.start.row().0..=range.end.row().0 {
12908 if let Some(selection) = self.selections.build_columnar_selection(
12909 &display_map,
12910 DisplayRow(row),
12911 &positions,
12912 selection.reversed,
12913 &text_layout_details,
12914 ) {
12915 stack.push(selection.id);
12916 columnar_selections.push(selection);
12917 }
12918 }
12919 if !stack.is_empty() {
12920 if above {
12921 stack.reverse();
12922 }
12923 state.groups.push(AddSelectionsGroup { above, stack });
12924 }
12925 }
12926
12927 let mut final_selections = Vec::new();
12928 let end_row = if above {
12929 DisplayRow(0)
12930 } else {
12931 display_map.max_point().row()
12932 };
12933
12934 let mut last_added_item_per_group = HashMap::default();
12935 for group in state.groups.iter_mut() {
12936 if let Some(last_id) = group.stack.last() {
12937 last_added_item_per_group.insert(*last_id, group);
12938 }
12939 }
12940
12941 for selection in columnar_selections {
12942 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12943 if above == group.above {
12944 let range = selection.display_range(&display_map).sorted();
12945 debug_assert_eq!(range.start.row(), range.end.row());
12946 let mut row = range.start.row();
12947 let positions =
12948 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12949 px(start)..px(end)
12950 } else {
12951 let start_x =
12952 display_map.x_for_display_point(range.start, &text_layout_details);
12953 let end_x =
12954 display_map.x_for_display_point(range.end, &text_layout_details);
12955 start_x.min(end_x)..start_x.max(end_x)
12956 };
12957
12958 let mut maybe_new_selection = None;
12959 while row != end_row {
12960 if above {
12961 row.0 -= 1;
12962 } else {
12963 row.0 += 1;
12964 }
12965 if let Some(new_selection) = self.selections.build_columnar_selection(
12966 &display_map,
12967 row,
12968 &positions,
12969 selection.reversed,
12970 &text_layout_details,
12971 ) {
12972 maybe_new_selection = Some(new_selection);
12973 break;
12974 }
12975 }
12976
12977 if let Some(new_selection) = maybe_new_selection {
12978 group.stack.push(new_selection.id);
12979 if above {
12980 final_selections.push(new_selection);
12981 final_selections.push(selection);
12982 } else {
12983 final_selections.push(selection);
12984 final_selections.push(new_selection);
12985 }
12986 } else {
12987 final_selections.push(selection);
12988 }
12989 } else {
12990 group.stack.pop();
12991 }
12992 } else {
12993 final_selections.push(selection);
12994 }
12995 }
12996
12997 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12998 s.select(final_selections);
12999 });
13000
13001 let final_selection_ids: HashSet<_> = self
13002 .selections
13003 .all::<Point>(cx)
13004 .iter()
13005 .map(|s| s.id)
13006 .collect();
13007 state.groups.retain_mut(|group| {
13008 // selections might get merged above so we remove invalid items from stacks
13009 group.stack.retain(|id| final_selection_ids.contains(id));
13010
13011 // single selection in stack can be treated as initial state
13012 group.stack.len() > 1
13013 });
13014
13015 if !state.groups.is_empty() {
13016 self.add_selections_state = Some(state);
13017 }
13018 }
13019
13020 fn select_match_ranges(
13021 &mut self,
13022 range: Range<usize>,
13023 reversed: bool,
13024 replace_newest: bool,
13025 auto_scroll: Option<Autoscroll>,
13026 window: &mut Window,
13027 cx: &mut Context<Editor>,
13028 ) {
13029 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13030 self.change_selections(auto_scroll, window, cx, |s| {
13031 if replace_newest {
13032 s.delete(s.newest_anchor().id);
13033 }
13034 if reversed {
13035 s.insert_range(range.end..range.start);
13036 } else {
13037 s.insert_range(range);
13038 }
13039 });
13040 }
13041
13042 pub fn select_next_match_internal(
13043 &mut self,
13044 display_map: &DisplaySnapshot,
13045 replace_newest: bool,
13046 autoscroll: Option<Autoscroll>,
13047 window: &mut Window,
13048 cx: &mut Context<Self>,
13049 ) -> Result<()> {
13050 let buffer = &display_map.buffer_snapshot;
13051 let mut selections = self.selections.all::<usize>(cx);
13052 if let Some(mut select_next_state) = self.select_next_state.take() {
13053 let query = &select_next_state.query;
13054 if !select_next_state.done {
13055 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13056 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13057 let mut next_selected_range = None;
13058
13059 let bytes_after_last_selection =
13060 buffer.bytes_in_range(last_selection.end..buffer.len());
13061 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13062 let query_matches = query
13063 .stream_find_iter(bytes_after_last_selection)
13064 .map(|result| (last_selection.end, result))
13065 .chain(
13066 query
13067 .stream_find_iter(bytes_before_first_selection)
13068 .map(|result| (0, result)),
13069 );
13070
13071 for (start_offset, query_match) in query_matches {
13072 let query_match = query_match.unwrap(); // can only fail due to I/O
13073 let offset_range =
13074 start_offset + query_match.start()..start_offset + query_match.end();
13075 let display_range = offset_range.start.to_display_point(display_map)
13076 ..offset_range.end.to_display_point(display_map);
13077
13078 if !select_next_state.wordwise
13079 || (!movement::is_inside_word(display_map, display_range.start)
13080 && !movement::is_inside_word(display_map, display_range.end))
13081 {
13082 // TODO: This is n^2, because we might check all the selections
13083 if !selections
13084 .iter()
13085 .any(|selection| selection.range().overlaps(&offset_range))
13086 {
13087 next_selected_range = Some(offset_range);
13088 break;
13089 }
13090 }
13091 }
13092
13093 if let Some(next_selected_range) = next_selected_range {
13094 self.select_match_ranges(
13095 next_selected_range,
13096 last_selection.reversed,
13097 replace_newest,
13098 autoscroll,
13099 window,
13100 cx,
13101 );
13102 } else {
13103 select_next_state.done = true;
13104 }
13105 }
13106
13107 self.select_next_state = Some(select_next_state);
13108 } else {
13109 let mut only_carets = true;
13110 let mut same_text_selected = true;
13111 let mut selected_text = None;
13112
13113 let mut selections_iter = selections.iter().peekable();
13114 while let Some(selection) = selections_iter.next() {
13115 if selection.start != selection.end {
13116 only_carets = false;
13117 }
13118
13119 if same_text_selected {
13120 if selected_text.is_none() {
13121 selected_text =
13122 Some(buffer.text_for_range(selection.range()).collect::<String>());
13123 }
13124
13125 if let Some(next_selection) = selections_iter.peek() {
13126 if next_selection.range().len() == selection.range().len() {
13127 let next_selected_text = buffer
13128 .text_for_range(next_selection.range())
13129 .collect::<String>();
13130 if Some(next_selected_text) != selected_text {
13131 same_text_selected = false;
13132 selected_text = None;
13133 }
13134 } else {
13135 same_text_selected = false;
13136 selected_text = None;
13137 }
13138 }
13139 }
13140 }
13141
13142 if only_carets {
13143 for selection in &mut selections {
13144 let word_range = movement::surrounding_word(
13145 display_map,
13146 selection.start.to_display_point(display_map),
13147 );
13148 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13149 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13150 selection.goal = SelectionGoal::None;
13151 selection.reversed = false;
13152 self.select_match_ranges(
13153 selection.start..selection.end,
13154 selection.reversed,
13155 replace_newest,
13156 autoscroll,
13157 window,
13158 cx,
13159 );
13160 }
13161
13162 if selections.len() == 1 {
13163 let selection = selections
13164 .last()
13165 .expect("ensured that there's only one selection");
13166 let query = buffer
13167 .text_for_range(selection.start..selection.end)
13168 .collect::<String>();
13169 let is_empty = query.is_empty();
13170 let select_state = SelectNextState {
13171 query: AhoCorasick::new(&[query])?,
13172 wordwise: true,
13173 done: is_empty,
13174 };
13175 self.select_next_state = Some(select_state);
13176 } else {
13177 self.select_next_state = None;
13178 }
13179 } else if let Some(selected_text) = selected_text {
13180 self.select_next_state = Some(SelectNextState {
13181 query: AhoCorasick::new(&[selected_text])?,
13182 wordwise: false,
13183 done: false,
13184 });
13185 self.select_next_match_internal(
13186 display_map,
13187 replace_newest,
13188 autoscroll,
13189 window,
13190 cx,
13191 )?;
13192 }
13193 }
13194 Ok(())
13195 }
13196
13197 pub fn select_all_matches(
13198 &mut self,
13199 _action: &SelectAllMatches,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) -> Result<()> {
13203 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13204
13205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13206
13207 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13208 let Some(select_next_state) = self.select_next_state.as_mut() else {
13209 return Ok(());
13210 };
13211 if select_next_state.done {
13212 return Ok(());
13213 }
13214
13215 let mut new_selections = Vec::new();
13216
13217 let reversed = self.selections.oldest::<usize>(cx).reversed;
13218 let buffer = &display_map.buffer_snapshot;
13219 let query_matches = select_next_state
13220 .query
13221 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13222
13223 for query_match in query_matches.into_iter() {
13224 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13225 let offset_range = if reversed {
13226 query_match.end()..query_match.start()
13227 } else {
13228 query_match.start()..query_match.end()
13229 };
13230 let display_range = offset_range.start.to_display_point(&display_map)
13231 ..offset_range.end.to_display_point(&display_map);
13232
13233 if !select_next_state.wordwise
13234 || (!movement::is_inside_word(&display_map, display_range.start)
13235 && !movement::is_inside_word(&display_map, display_range.end))
13236 {
13237 new_selections.push(offset_range.start..offset_range.end);
13238 }
13239 }
13240
13241 select_next_state.done = true;
13242 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13243 self.change_selections(None, window, cx, |selections| {
13244 selections.select_ranges(new_selections)
13245 });
13246
13247 Ok(())
13248 }
13249
13250 pub fn select_next(
13251 &mut self,
13252 action: &SelectNext,
13253 window: &mut Window,
13254 cx: &mut Context<Self>,
13255 ) -> Result<()> {
13256 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13258 self.select_next_match_internal(
13259 &display_map,
13260 action.replace_newest,
13261 Some(Autoscroll::newest()),
13262 window,
13263 cx,
13264 )?;
13265 Ok(())
13266 }
13267
13268 pub fn select_previous(
13269 &mut self,
13270 action: &SelectPrevious,
13271 window: &mut Window,
13272 cx: &mut Context<Self>,
13273 ) -> Result<()> {
13274 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13275 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13276 let buffer = &display_map.buffer_snapshot;
13277 let mut selections = self.selections.all::<usize>(cx);
13278 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13279 let query = &select_prev_state.query;
13280 if !select_prev_state.done {
13281 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13282 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13283 let mut next_selected_range = None;
13284 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13285 let bytes_before_last_selection =
13286 buffer.reversed_bytes_in_range(0..last_selection.start);
13287 let bytes_after_first_selection =
13288 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13289 let query_matches = query
13290 .stream_find_iter(bytes_before_last_selection)
13291 .map(|result| (last_selection.start, result))
13292 .chain(
13293 query
13294 .stream_find_iter(bytes_after_first_selection)
13295 .map(|result| (buffer.len(), result)),
13296 );
13297 for (end_offset, query_match) in query_matches {
13298 let query_match = query_match.unwrap(); // can only fail due to I/O
13299 let offset_range =
13300 end_offset - query_match.end()..end_offset - query_match.start();
13301 let display_range = offset_range.start.to_display_point(&display_map)
13302 ..offset_range.end.to_display_point(&display_map);
13303
13304 if !select_prev_state.wordwise
13305 || (!movement::is_inside_word(&display_map, display_range.start)
13306 && !movement::is_inside_word(&display_map, display_range.end))
13307 {
13308 next_selected_range = Some(offset_range);
13309 break;
13310 }
13311 }
13312
13313 if let Some(next_selected_range) = next_selected_range {
13314 self.select_match_ranges(
13315 next_selected_range,
13316 last_selection.reversed,
13317 action.replace_newest,
13318 Some(Autoscroll::newest()),
13319 window,
13320 cx,
13321 );
13322 } else {
13323 select_prev_state.done = true;
13324 }
13325 }
13326
13327 self.select_prev_state = Some(select_prev_state);
13328 } else {
13329 let mut only_carets = true;
13330 let mut same_text_selected = true;
13331 let mut selected_text = None;
13332
13333 let mut selections_iter = selections.iter().peekable();
13334 while let Some(selection) = selections_iter.next() {
13335 if selection.start != selection.end {
13336 only_carets = false;
13337 }
13338
13339 if same_text_selected {
13340 if selected_text.is_none() {
13341 selected_text =
13342 Some(buffer.text_for_range(selection.range()).collect::<String>());
13343 }
13344
13345 if let Some(next_selection) = selections_iter.peek() {
13346 if next_selection.range().len() == selection.range().len() {
13347 let next_selected_text = buffer
13348 .text_for_range(next_selection.range())
13349 .collect::<String>();
13350 if Some(next_selected_text) != selected_text {
13351 same_text_selected = false;
13352 selected_text = None;
13353 }
13354 } else {
13355 same_text_selected = false;
13356 selected_text = None;
13357 }
13358 }
13359 }
13360 }
13361
13362 if only_carets {
13363 for selection in &mut selections {
13364 let word_range = movement::surrounding_word(
13365 &display_map,
13366 selection.start.to_display_point(&display_map),
13367 );
13368 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13369 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13370 selection.goal = SelectionGoal::None;
13371 selection.reversed = false;
13372 self.select_match_ranges(
13373 selection.start..selection.end,
13374 selection.reversed,
13375 action.replace_newest,
13376 Some(Autoscroll::newest()),
13377 window,
13378 cx,
13379 );
13380 }
13381 if selections.len() == 1 {
13382 let selection = selections
13383 .last()
13384 .expect("ensured that there's only one selection");
13385 let query = buffer
13386 .text_for_range(selection.start..selection.end)
13387 .collect::<String>();
13388 let is_empty = query.is_empty();
13389 let select_state = SelectNextState {
13390 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13391 wordwise: true,
13392 done: is_empty,
13393 };
13394 self.select_prev_state = Some(select_state);
13395 } else {
13396 self.select_prev_state = None;
13397 }
13398 } else if let Some(selected_text) = selected_text {
13399 self.select_prev_state = Some(SelectNextState {
13400 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13401 wordwise: false,
13402 done: false,
13403 });
13404 self.select_previous(action, window, cx)?;
13405 }
13406 }
13407 Ok(())
13408 }
13409
13410 pub fn find_next_match(
13411 &mut self,
13412 _: &FindNextMatch,
13413 window: &mut Window,
13414 cx: &mut Context<Self>,
13415 ) -> Result<()> {
13416 let selections = self.selections.disjoint_anchors();
13417 match selections.first() {
13418 Some(first) if selections.len() >= 2 => {
13419 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13420 s.select_ranges([first.range()]);
13421 });
13422 }
13423 _ => self.select_next(
13424 &SelectNext {
13425 replace_newest: true,
13426 },
13427 window,
13428 cx,
13429 )?,
13430 }
13431 Ok(())
13432 }
13433
13434 pub fn find_previous_match(
13435 &mut self,
13436 _: &FindPreviousMatch,
13437 window: &mut Window,
13438 cx: &mut Context<Self>,
13439 ) -> Result<()> {
13440 let selections = self.selections.disjoint_anchors();
13441 match selections.last() {
13442 Some(last) if selections.len() >= 2 => {
13443 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13444 s.select_ranges([last.range()]);
13445 });
13446 }
13447 _ => self.select_previous(
13448 &SelectPrevious {
13449 replace_newest: true,
13450 },
13451 window,
13452 cx,
13453 )?,
13454 }
13455 Ok(())
13456 }
13457
13458 pub fn toggle_comments(
13459 &mut self,
13460 action: &ToggleComments,
13461 window: &mut Window,
13462 cx: &mut Context<Self>,
13463 ) {
13464 if self.read_only(cx) {
13465 return;
13466 }
13467 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13468 let text_layout_details = &self.text_layout_details(window);
13469 self.transact(window, cx, |this, window, cx| {
13470 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13471 let mut edits = Vec::new();
13472 let mut selection_edit_ranges = Vec::new();
13473 let mut last_toggled_row = None;
13474 let snapshot = this.buffer.read(cx).read(cx);
13475 let empty_str: Arc<str> = Arc::default();
13476 let mut suffixes_inserted = Vec::new();
13477 let ignore_indent = action.ignore_indent;
13478
13479 fn comment_prefix_range(
13480 snapshot: &MultiBufferSnapshot,
13481 row: MultiBufferRow,
13482 comment_prefix: &str,
13483 comment_prefix_whitespace: &str,
13484 ignore_indent: bool,
13485 ) -> Range<Point> {
13486 let indent_size = if ignore_indent {
13487 0
13488 } else {
13489 snapshot.indent_size_for_line(row).len
13490 };
13491
13492 let start = Point::new(row.0, indent_size);
13493
13494 let mut line_bytes = snapshot
13495 .bytes_in_range(start..snapshot.max_point())
13496 .flatten()
13497 .copied();
13498
13499 // If this line currently begins with the line comment prefix, then record
13500 // the range containing the prefix.
13501 if line_bytes
13502 .by_ref()
13503 .take(comment_prefix.len())
13504 .eq(comment_prefix.bytes())
13505 {
13506 // Include any whitespace that matches the comment prefix.
13507 let matching_whitespace_len = line_bytes
13508 .zip(comment_prefix_whitespace.bytes())
13509 .take_while(|(a, b)| a == b)
13510 .count() as u32;
13511 let end = Point::new(
13512 start.row,
13513 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13514 );
13515 start..end
13516 } else {
13517 start..start
13518 }
13519 }
13520
13521 fn comment_suffix_range(
13522 snapshot: &MultiBufferSnapshot,
13523 row: MultiBufferRow,
13524 comment_suffix: &str,
13525 comment_suffix_has_leading_space: bool,
13526 ) -> Range<Point> {
13527 let end = Point::new(row.0, snapshot.line_len(row));
13528 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13529
13530 let mut line_end_bytes = snapshot
13531 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13532 .flatten()
13533 .copied();
13534
13535 let leading_space_len = if suffix_start_column > 0
13536 && line_end_bytes.next() == Some(b' ')
13537 && comment_suffix_has_leading_space
13538 {
13539 1
13540 } else {
13541 0
13542 };
13543
13544 // If this line currently begins with the line comment prefix, then record
13545 // the range containing the prefix.
13546 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13547 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13548 start..end
13549 } else {
13550 end..end
13551 }
13552 }
13553
13554 // TODO: Handle selections that cross excerpts
13555 for selection in &mut selections {
13556 let start_column = snapshot
13557 .indent_size_for_line(MultiBufferRow(selection.start.row))
13558 .len;
13559 let language = if let Some(language) =
13560 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13561 {
13562 language
13563 } else {
13564 continue;
13565 };
13566
13567 selection_edit_ranges.clear();
13568
13569 // If multiple selections contain a given row, avoid processing that
13570 // row more than once.
13571 let mut start_row = MultiBufferRow(selection.start.row);
13572 if last_toggled_row == Some(start_row) {
13573 start_row = start_row.next_row();
13574 }
13575 let end_row =
13576 if selection.end.row > selection.start.row && selection.end.column == 0 {
13577 MultiBufferRow(selection.end.row - 1)
13578 } else {
13579 MultiBufferRow(selection.end.row)
13580 };
13581 last_toggled_row = Some(end_row);
13582
13583 if start_row > end_row {
13584 continue;
13585 }
13586
13587 // If the language has line comments, toggle those.
13588 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13589
13590 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13591 if ignore_indent {
13592 full_comment_prefixes = full_comment_prefixes
13593 .into_iter()
13594 .map(|s| Arc::from(s.trim_end()))
13595 .collect();
13596 }
13597
13598 if !full_comment_prefixes.is_empty() {
13599 let first_prefix = full_comment_prefixes
13600 .first()
13601 .expect("prefixes is non-empty");
13602 let prefix_trimmed_lengths = full_comment_prefixes
13603 .iter()
13604 .map(|p| p.trim_end_matches(' ').len())
13605 .collect::<SmallVec<[usize; 4]>>();
13606
13607 let mut all_selection_lines_are_comments = true;
13608
13609 for row in start_row.0..=end_row.0 {
13610 let row = MultiBufferRow(row);
13611 if start_row < end_row && snapshot.is_line_blank(row) {
13612 continue;
13613 }
13614
13615 let prefix_range = full_comment_prefixes
13616 .iter()
13617 .zip(prefix_trimmed_lengths.iter().copied())
13618 .map(|(prefix, trimmed_prefix_len)| {
13619 comment_prefix_range(
13620 snapshot.deref(),
13621 row,
13622 &prefix[..trimmed_prefix_len],
13623 &prefix[trimmed_prefix_len..],
13624 ignore_indent,
13625 )
13626 })
13627 .max_by_key(|range| range.end.column - range.start.column)
13628 .expect("prefixes is non-empty");
13629
13630 if prefix_range.is_empty() {
13631 all_selection_lines_are_comments = false;
13632 }
13633
13634 selection_edit_ranges.push(prefix_range);
13635 }
13636
13637 if all_selection_lines_are_comments {
13638 edits.extend(
13639 selection_edit_ranges
13640 .iter()
13641 .cloned()
13642 .map(|range| (range, empty_str.clone())),
13643 );
13644 } else {
13645 let min_column = selection_edit_ranges
13646 .iter()
13647 .map(|range| range.start.column)
13648 .min()
13649 .unwrap_or(0);
13650 edits.extend(selection_edit_ranges.iter().map(|range| {
13651 let position = Point::new(range.start.row, min_column);
13652 (position..position, first_prefix.clone())
13653 }));
13654 }
13655 } else if let Some((full_comment_prefix, comment_suffix)) =
13656 language.block_comment_delimiters()
13657 {
13658 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13659 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13660 let prefix_range = comment_prefix_range(
13661 snapshot.deref(),
13662 start_row,
13663 comment_prefix,
13664 comment_prefix_whitespace,
13665 ignore_indent,
13666 );
13667 let suffix_range = comment_suffix_range(
13668 snapshot.deref(),
13669 end_row,
13670 comment_suffix.trim_start_matches(' '),
13671 comment_suffix.starts_with(' '),
13672 );
13673
13674 if prefix_range.is_empty() || suffix_range.is_empty() {
13675 edits.push((
13676 prefix_range.start..prefix_range.start,
13677 full_comment_prefix.clone(),
13678 ));
13679 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13680 suffixes_inserted.push((end_row, comment_suffix.len()));
13681 } else {
13682 edits.push((prefix_range, empty_str.clone()));
13683 edits.push((suffix_range, empty_str.clone()));
13684 }
13685 } else {
13686 continue;
13687 }
13688 }
13689
13690 drop(snapshot);
13691 this.buffer.update(cx, |buffer, cx| {
13692 buffer.edit(edits, None, cx);
13693 });
13694
13695 // Adjust selections so that they end before any comment suffixes that
13696 // were inserted.
13697 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13698 let mut selections = this.selections.all::<Point>(cx);
13699 let snapshot = this.buffer.read(cx).read(cx);
13700 for selection in &mut selections {
13701 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13702 match row.cmp(&MultiBufferRow(selection.end.row)) {
13703 Ordering::Less => {
13704 suffixes_inserted.next();
13705 continue;
13706 }
13707 Ordering::Greater => break,
13708 Ordering::Equal => {
13709 if selection.end.column == snapshot.line_len(row) {
13710 if selection.is_empty() {
13711 selection.start.column -= suffix_len as u32;
13712 }
13713 selection.end.column -= suffix_len as u32;
13714 }
13715 break;
13716 }
13717 }
13718 }
13719 }
13720
13721 drop(snapshot);
13722 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13723 s.select(selections)
13724 });
13725
13726 let selections = this.selections.all::<Point>(cx);
13727 let selections_on_single_row = selections.windows(2).all(|selections| {
13728 selections[0].start.row == selections[1].start.row
13729 && selections[0].end.row == selections[1].end.row
13730 && selections[0].start.row == selections[0].end.row
13731 });
13732 let selections_selecting = selections
13733 .iter()
13734 .any(|selection| selection.start != selection.end);
13735 let advance_downwards = action.advance_downwards
13736 && selections_on_single_row
13737 && !selections_selecting
13738 && !matches!(this.mode, EditorMode::SingleLine { .. });
13739
13740 if advance_downwards {
13741 let snapshot = this.buffer.read(cx).snapshot(cx);
13742
13743 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13744 s.move_cursors_with(|display_snapshot, display_point, _| {
13745 let mut point = display_point.to_point(display_snapshot);
13746 point.row += 1;
13747 point = snapshot.clip_point(point, Bias::Left);
13748 let display_point = point.to_display_point(display_snapshot);
13749 let goal = SelectionGoal::HorizontalPosition(
13750 display_snapshot
13751 .x_for_display_point(display_point, text_layout_details)
13752 .into(),
13753 );
13754 (display_point, goal)
13755 })
13756 });
13757 }
13758 });
13759 }
13760
13761 pub fn select_enclosing_symbol(
13762 &mut self,
13763 _: &SelectEnclosingSymbol,
13764 window: &mut Window,
13765 cx: &mut Context<Self>,
13766 ) {
13767 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13768
13769 let buffer = self.buffer.read(cx).snapshot(cx);
13770 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13771
13772 fn update_selection(
13773 selection: &Selection<usize>,
13774 buffer_snap: &MultiBufferSnapshot,
13775 ) -> Option<Selection<usize>> {
13776 let cursor = selection.head();
13777 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13778 for symbol in symbols.iter().rev() {
13779 let start = symbol.range.start.to_offset(buffer_snap);
13780 let end = symbol.range.end.to_offset(buffer_snap);
13781 let new_range = start..end;
13782 if start < selection.start || end > selection.end {
13783 return Some(Selection {
13784 id: selection.id,
13785 start: new_range.start,
13786 end: new_range.end,
13787 goal: SelectionGoal::None,
13788 reversed: selection.reversed,
13789 });
13790 }
13791 }
13792 None
13793 }
13794
13795 let mut selected_larger_symbol = false;
13796 let new_selections = old_selections
13797 .iter()
13798 .map(|selection| match update_selection(selection, &buffer) {
13799 Some(new_selection) => {
13800 if new_selection.range() != selection.range() {
13801 selected_larger_symbol = true;
13802 }
13803 new_selection
13804 }
13805 None => selection.clone(),
13806 })
13807 .collect::<Vec<_>>();
13808
13809 if selected_larger_symbol {
13810 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13811 s.select(new_selections);
13812 });
13813 }
13814 }
13815
13816 pub fn select_larger_syntax_node(
13817 &mut self,
13818 _: &SelectLargerSyntaxNode,
13819 window: &mut Window,
13820 cx: &mut Context<Self>,
13821 ) {
13822 let Some(visible_row_count) = self.visible_row_count() else {
13823 return;
13824 };
13825 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13826 if old_selections.is_empty() {
13827 return;
13828 }
13829
13830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13831
13832 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13833 let buffer = self.buffer.read(cx).snapshot(cx);
13834
13835 let mut selected_larger_node = false;
13836 let mut new_selections = old_selections
13837 .iter()
13838 .map(|selection| {
13839 let old_range = selection.start..selection.end;
13840
13841 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13842 // manually select word at selection
13843 if ["string_content", "inline"].contains(&node.kind()) {
13844 let word_range = {
13845 let display_point = buffer
13846 .offset_to_point(old_range.start)
13847 .to_display_point(&display_map);
13848 let Range { start, end } =
13849 movement::surrounding_word(&display_map, display_point);
13850 start.to_point(&display_map).to_offset(&buffer)
13851 ..end.to_point(&display_map).to_offset(&buffer)
13852 };
13853 // ignore if word is already selected
13854 if !word_range.is_empty() && old_range != word_range {
13855 let last_word_range = {
13856 let display_point = buffer
13857 .offset_to_point(old_range.end)
13858 .to_display_point(&display_map);
13859 let Range { start, end } =
13860 movement::surrounding_word(&display_map, display_point);
13861 start.to_point(&display_map).to_offset(&buffer)
13862 ..end.to_point(&display_map).to_offset(&buffer)
13863 };
13864 // only select word if start and end point belongs to same word
13865 if word_range == last_word_range {
13866 selected_larger_node = true;
13867 return Selection {
13868 id: selection.id,
13869 start: word_range.start,
13870 end: word_range.end,
13871 goal: SelectionGoal::None,
13872 reversed: selection.reversed,
13873 };
13874 }
13875 }
13876 }
13877 }
13878
13879 let mut new_range = old_range.clone();
13880 while let Some((_node, containing_range)) =
13881 buffer.syntax_ancestor(new_range.clone())
13882 {
13883 new_range = match containing_range {
13884 MultiOrSingleBufferOffsetRange::Single(_) => break,
13885 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13886 };
13887 if !display_map.intersects_fold(new_range.start)
13888 && !display_map.intersects_fold(new_range.end)
13889 {
13890 break;
13891 }
13892 }
13893
13894 selected_larger_node |= new_range != old_range;
13895 Selection {
13896 id: selection.id,
13897 start: new_range.start,
13898 end: new_range.end,
13899 goal: SelectionGoal::None,
13900 reversed: selection.reversed,
13901 }
13902 })
13903 .collect::<Vec<_>>();
13904
13905 if !selected_larger_node {
13906 return; // don't put this call in the history
13907 }
13908
13909 // scroll based on transformation done to the last selection created by the user
13910 let (last_old, last_new) = old_selections
13911 .last()
13912 .zip(new_selections.last().cloned())
13913 .expect("old_selections isn't empty");
13914
13915 // revert selection
13916 let is_selection_reversed = {
13917 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13918 new_selections.last_mut().expect("checked above").reversed =
13919 should_newest_selection_be_reversed;
13920 should_newest_selection_be_reversed
13921 };
13922
13923 if selected_larger_node {
13924 self.select_syntax_node_history.disable_clearing = true;
13925 self.change_selections(None, window, cx, |s| {
13926 s.select(new_selections.clone());
13927 });
13928 self.select_syntax_node_history.disable_clearing = false;
13929 }
13930
13931 let start_row = last_new.start.to_display_point(&display_map).row().0;
13932 let end_row = last_new.end.to_display_point(&display_map).row().0;
13933 let selection_height = end_row - start_row + 1;
13934 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13935
13936 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13937 let scroll_behavior = if fits_on_the_screen {
13938 self.request_autoscroll(Autoscroll::fit(), cx);
13939 SelectSyntaxNodeScrollBehavior::FitSelection
13940 } else if is_selection_reversed {
13941 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13942 SelectSyntaxNodeScrollBehavior::CursorTop
13943 } else {
13944 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13945 SelectSyntaxNodeScrollBehavior::CursorBottom
13946 };
13947
13948 self.select_syntax_node_history.push((
13949 old_selections,
13950 scroll_behavior,
13951 is_selection_reversed,
13952 ));
13953 }
13954
13955 pub fn select_smaller_syntax_node(
13956 &mut self,
13957 _: &SelectSmallerSyntaxNode,
13958 window: &mut Window,
13959 cx: &mut Context<Self>,
13960 ) {
13961 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13962
13963 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13964 self.select_syntax_node_history.pop()
13965 {
13966 if let Some(selection) = selections.last_mut() {
13967 selection.reversed = is_selection_reversed;
13968 }
13969
13970 self.select_syntax_node_history.disable_clearing = true;
13971 self.change_selections(None, window, cx, |s| {
13972 s.select(selections.to_vec());
13973 });
13974 self.select_syntax_node_history.disable_clearing = false;
13975
13976 match scroll_behavior {
13977 SelectSyntaxNodeScrollBehavior::CursorTop => {
13978 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13979 }
13980 SelectSyntaxNodeScrollBehavior::FitSelection => {
13981 self.request_autoscroll(Autoscroll::fit(), cx);
13982 }
13983 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13984 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13985 }
13986 }
13987 }
13988 }
13989
13990 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13991 if !EditorSettings::get_global(cx).gutter.runnables {
13992 self.clear_tasks();
13993 return Task::ready(());
13994 }
13995 let project = self.project.as_ref().map(Entity::downgrade);
13996 let task_sources = self.lsp_task_sources(cx);
13997 let multi_buffer = self.buffer.downgrade();
13998 cx.spawn_in(window, async move |editor, cx| {
13999 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14000 let Some(project) = project.and_then(|p| p.upgrade()) else {
14001 return;
14002 };
14003 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14004 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14005 }) else {
14006 return;
14007 };
14008
14009 let hide_runnables = project
14010 .update(cx, |project, cx| {
14011 // Do not display any test indicators in non-dev server remote projects.
14012 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14013 })
14014 .unwrap_or(true);
14015 if hide_runnables {
14016 return;
14017 }
14018 let new_rows =
14019 cx.background_spawn({
14020 let snapshot = display_snapshot.clone();
14021 async move {
14022 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14023 }
14024 })
14025 .await;
14026 let Ok(lsp_tasks) =
14027 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14028 else {
14029 return;
14030 };
14031 let lsp_tasks = lsp_tasks.await;
14032
14033 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14034 lsp_tasks
14035 .into_iter()
14036 .flat_map(|(kind, tasks)| {
14037 tasks.into_iter().filter_map(move |(location, task)| {
14038 Some((kind.clone(), location?, task))
14039 })
14040 })
14041 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14042 let buffer = location.target.buffer;
14043 let buffer_snapshot = buffer.read(cx).snapshot();
14044 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14045 |(excerpt_id, snapshot, _)| {
14046 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14047 display_snapshot
14048 .buffer_snapshot
14049 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14050 } else {
14051 None
14052 }
14053 },
14054 );
14055 if let Some(offset) = offset {
14056 let task_buffer_range =
14057 location.target.range.to_point(&buffer_snapshot);
14058 let context_buffer_range =
14059 task_buffer_range.to_offset(&buffer_snapshot);
14060 let context_range = BufferOffset(context_buffer_range.start)
14061 ..BufferOffset(context_buffer_range.end);
14062
14063 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14064 .or_insert_with(|| RunnableTasks {
14065 templates: Vec::new(),
14066 offset,
14067 column: task_buffer_range.start.column,
14068 extra_variables: HashMap::default(),
14069 context_range,
14070 })
14071 .templates
14072 .push((kind, task.original_task().clone()));
14073 }
14074
14075 acc
14076 })
14077 }) else {
14078 return;
14079 };
14080
14081 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14082 buffer.language_settings(cx).tasks.prefer_lsp
14083 }) else {
14084 return;
14085 };
14086
14087 let rows = Self::runnable_rows(
14088 project,
14089 display_snapshot,
14090 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14091 new_rows,
14092 cx.clone(),
14093 )
14094 .await;
14095 editor
14096 .update(cx, |editor, _| {
14097 editor.clear_tasks();
14098 for (key, mut value) in rows {
14099 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14100 value.templates.extend(lsp_tasks.templates);
14101 }
14102
14103 editor.insert_tasks(key, value);
14104 }
14105 for (key, value) in lsp_tasks_by_rows {
14106 editor.insert_tasks(key, value);
14107 }
14108 })
14109 .ok();
14110 })
14111 }
14112 fn fetch_runnable_ranges(
14113 snapshot: &DisplaySnapshot,
14114 range: Range<Anchor>,
14115 ) -> Vec<language::RunnableRange> {
14116 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14117 }
14118
14119 fn runnable_rows(
14120 project: Entity<Project>,
14121 snapshot: DisplaySnapshot,
14122 prefer_lsp: bool,
14123 runnable_ranges: Vec<RunnableRange>,
14124 cx: AsyncWindowContext,
14125 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14126 cx.spawn(async move |cx| {
14127 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14128 for mut runnable in runnable_ranges {
14129 let Some(tasks) = cx
14130 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14131 .ok()
14132 else {
14133 continue;
14134 };
14135 let mut tasks = tasks.await;
14136
14137 if prefer_lsp {
14138 tasks.retain(|(task_kind, _)| {
14139 !matches!(task_kind, TaskSourceKind::Language { .. })
14140 });
14141 }
14142 if tasks.is_empty() {
14143 continue;
14144 }
14145
14146 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14147 let Some(row) = snapshot
14148 .buffer_snapshot
14149 .buffer_line_for_row(MultiBufferRow(point.row))
14150 .map(|(_, range)| range.start.row)
14151 else {
14152 continue;
14153 };
14154
14155 let context_range =
14156 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14157 runnable_rows.push((
14158 (runnable.buffer_id, row),
14159 RunnableTasks {
14160 templates: tasks,
14161 offset: snapshot
14162 .buffer_snapshot
14163 .anchor_before(runnable.run_range.start),
14164 context_range,
14165 column: point.column,
14166 extra_variables: runnable.extra_captures,
14167 },
14168 ));
14169 }
14170 runnable_rows
14171 })
14172 }
14173
14174 fn templates_with_tags(
14175 project: &Entity<Project>,
14176 runnable: &mut Runnable,
14177 cx: &mut App,
14178 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14179 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14180 let (worktree_id, file) = project
14181 .buffer_for_id(runnable.buffer, cx)
14182 .and_then(|buffer| buffer.read(cx).file())
14183 .map(|file| (file.worktree_id(cx), file.clone()))
14184 .unzip();
14185
14186 (
14187 project.task_store().read(cx).task_inventory().cloned(),
14188 worktree_id,
14189 file,
14190 )
14191 });
14192
14193 let tags = mem::take(&mut runnable.tags);
14194 let language = runnable.language.clone();
14195 cx.spawn(async move |cx| {
14196 let mut templates_with_tags = Vec::new();
14197 if let Some(inventory) = inventory {
14198 for RunnableTag(tag) in tags {
14199 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14200 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14201 }) else {
14202 return templates_with_tags;
14203 };
14204 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14205 move |(_, template)| {
14206 template.tags.iter().any(|source_tag| source_tag == &tag)
14207 },
14208 ));
14209 }
14210 }
14211 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14212
14213 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14214 // Strongest source wins; if we have worktree tag binding, prefer that to
14215 // global and language bindings;
14216 // if we have a global binding, prefer that to language binding.
14217 let first_mismatch = templates_with_tags
14218 .iter()
14219 .position(|(tag_source, _)| tag_source != leading_tag_source);
14220 if let Some(index) = first_mismatch {
14221 templates_with_tags.truncate(index);
14222 }
14223 }
14224
14225 templates_with_tags
14226 })
14227 }
14228
14229 pub fn move_to_enclosing_bracket(
14230 &mut self,
14231 _: &MoveToEnclosingBracket,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) {
14235 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14236 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14237 s.move_offsets_with(|snapshot, selection| {
14238 let Some(enclosing_bracket_ranges) =
14239 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14240 else {
14241 return;
14242 };
14243
14244 let mut best_length = usize::MAX;
14245 let mut best_inside = false;
14246 let mut best_in_bracket_range = false;
14247 let mut best_destination = None;
14248 for (open, close) in enclosing_bracket_ranges {
14249 let close = close.to_inclusive();
14250 let length = close.end() - open.start;
14251 let inside = selection.start >= open.end && selection.end <= *close.start();
14252 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14253 || close.contains(&selection.head());
14254
14255 // If best is next to a bracket and current isn't, skip
14256 if !in_bracket_range && best_in_bracket_range {
14257 continue;
14258 }
14259
14260 // Prefer smaller lengths unless best is inside and current isn't
14261 if length > best_length && (best_inside || !inside) {
14262 continue;
14263 }
14264
14265 best_length = length;
14266 best_inside = inside;
14267 best_in_bracket_range = in_bracket_range;
14268 best_destination = Some(
14269 if close.contains(&selection.start) && close.contains(&selection.end) {
14270 if inside { open.end } else { open.start }
14271 } else if inside {
14272 *close.start()
14273 } else {
14274 *close.end()
14275 },
14276 );
14277 }
14278
14279 if let Some(destination) = best_destination {
14280 selection.collapse_to(destination, SelectionGoal::None);
14281 }
14282 })
14283 });
14284 }
14285
14286 pub fn undo_selection(
14287 &mut self,
14288 _: &UndoSelection,
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.undo_stack.pop_back() {
14294 self.selection_history.mode = SelectionHistoryMode::Undoing;
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 redo_selection(
14310 &mut self,
14311 _: &RedoSelection,
14312 window: &mut Window,
14313 cx: &mut Context<Self>,
14314 ) {
14315 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14316 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14317 self.selection_history.mode = SelectionHistoryMode::Redoing;
14318 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14319 this.end_selection(window, cx);
14320 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14321 s.select_anchors(entry.selections.to_vec())
14322 });
14323 });
14324 self.selection_history.mode = SelectionHistoryMode::Normal;
14325
14326 self.select_next_state = entry.select_next_state;
14327 self.select_prev_state = entry.select_prev_state;
14328 self.add_selections_state = entry.add_selections_state;
14329 }
14330 }
14331
14332 pub fn expand_excerpts(
14333 &mut self,
14334 action: &ExpandExcerpts,
14335 _: &mut Window,
14336 cx: &mut Context<Self>,
14337 ) {
14338 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14339 }
14340
14341 pub fn expand_excerpts_down(
14342 &mut self,
14343 action: &ExpandExcerptsDown,
14344 _: &mut Window,
14345 cx: &mut Context<Self>,
14346 ) {
14347 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14348 }
14349
14350 pub fn expand_excerpts_up(
14351 &mut self,
14352 action: &ExpandExcerptsUp,
14353 _: &mut Window,
14354 cx: &mut Context<Self>,
14355 ) {
14356 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14357 }
14358
14359 pub fn expand_excerpts_for_direction(
14360 &mut self,
14361 lines: u32,
14362 direction: ExpandExcerptDirection,
14363
14364 cx: &mut Context<Self>,
14365 ) {
14366 let selections = self.selections.disjoint_anchors();
14367
14368 let lines = if lines == 0 {
14369 EditorSettings::get_global(cx).expand_excerpt_lines
14370 } else {
14371 lines
14372 };
14373
14374 self.buffer.update(cx, |buffer, cx| {
14375 let snapshot = buffer.snapshot(cx);
14376 let mut excerpt_ids = selections
14377 .iter()
14378 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14379 .collect::<Vec<_>>();
14380 excerpt_ids.sort();
14381 excerpt_ids.dedup();
14382 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14383 })
14384 }
14385
14386 pub fn expand_excerpt(
14387 &mut self,
14388 excerpt: ExcerptId,
14389 direction: ExpandExcerptDirection,
14390 window: &mut Window,
14391 cx: &mut Context<Self>,
14392 ) {
14393 let current_scroll_position = self.scroll_position(cx);
14394 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14395 let mut should_scroll_up = false;
14396
14397 if direction == ExpandExcerptDirection::Down {
14398 let multi_buffer = self.buffer.read(cx);
14399 let snapshot = multi_buffer.snapshot(cx);
14400 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14401 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14402 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14403 let buffer_snapshot = buffer.read(cx).snapshot();
14404 let excerpt_end_row =
14405 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14406 let last_row = buffer_snapshot.max_point().row;
14407 let lines_below = last_row.saturating_sub(excerpt_end_row);
14408 should_scroll_up = lines_below >= lines_to_expand;
14409 }
14410 }
14411 }
14412 }
14413
14414 self.buffer.update(cx, |buffer, cx| {
14415 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14416 });
14417
14418 if should_scroll_up {
14419 let new_scroll_position =
14420 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14421 self.set_scroll_position(new_scroll_position, window, cx);
14422 }
14423 }
14424
14425 pub fn go_to_singleton_buffer_point(
14426 &mut self,
14427 point: Point,
14428 window: &mut Window,
14429 cx: &mut Context<Self>,
14430 ) {
14431 self.go_to_singleton_buffer_range(point..point, window, cx);
14432 }
14433
14434 pub fn go_to_singleton_buffer_range(
14435 &mut self,
14436 range: Range<Point>,
14437 window: &mut Window,
14438 cx: &mut Context<Self>,
14439 ) {
14440 let multibuffer = self.buffer().read(cx);
14441 let Some(buffer) = multibuffer.as_singleton() else {
14442 return;
14443 };
14444 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14445 return;
14446 };
14447 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14448 return;
14449 };
14450 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14451 s.select_anchor_ranges([start..end])
14452 });
14453 }
14454
14455 pub fn go_to_diagnostic(
14456 &mut self,
14457 _: &GoToDiagnostic,
14458 window: &mut Window,
14459 cx: &mut Context<Self>,
14460 ) {
14461 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14462 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14463 }
14464
14465 pub fn go_to_prev_diagnostic(
14466 &mut self,
14467 _: &GoToPreviousDiagnostic,
14468 window: &mut Window,
14469 cx: &mut Context<Self>,
14470 ) {
14471 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14472 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14473 }
14474
14475 pub fn go_to_diagnostic_impl(
14476 &mut self,
14477 direction: Direction,
14478 window: &mut Window,
14479 cx: &mut Context<Self>,
14480 ) {
14481 let buffer = self.buffer.read(cx).snapshot(cx);
14482 let selection = self.selections.newest::<usize>(cx);
14483
14484 let mut active_group_id = None;
14485 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14486 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14487 active_group_id = Some(active_group.group_id);
14488 }
14489 }
14490
14491 fn filtered(
14492 snapshot: EditorSnapshot,
14493 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14494 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14495 diagnostics
14496 .filter(|entry| entry.range.start != entry.range.end)
14497 .filter(|entry| !entry.diagnostic.is_unnecessary)
14498 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14499 }
14500
14501 let snapshot = self.snapshot(window, cx);
14502 let before = filtered(
14503 snapshot.clone(),
14504 buffer
14505 .diagnostics_in_range(0..selection.start)
14506 .filter(|entry| entry.range.start <= selection.start),
14507 );
14508 let after = filtered(
14509 snapshot,
14510 buffer
14511 .diagnostics_in_range(selection.start..buffer.len())
14512 .filter(|entry| entry.range.start >= selection.start),
14513 );
14514
14515 let mut found: Option<DiagnosticEntry<usize>> = None;
14516 if direction == Direction::Prev {
14517 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14518 {
14519 for diagnostic in prev_diagnostics.into_iter().rev() {
14520 if diagnostic.range.start != selection.start
14521 || active_group_id
14522 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14523 {
14524 found = Some(diagnostic);
14525 break 'outer;
14526 }
14527 }
14528 }
14529 } else {
14530 for diagnostic in after.chain(before) {
14531 if diagnostic.range.start != selection.start
14532 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14533 {
14534 found = Some(diagnostic);
14535 break;
14536 }
14537 }
14538 }
14539 let Some(next_diagnostic) = found else {
14540 return;
14541 };
14542
14543 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14544 return;
14545 };
14546 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14547 s.select_ranges(vec![
14548 next_diagnostic.range.start..next_diagnostic.range.start,
14549 ])
14550 });
14551 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14552 self.refresh_inline_completion(false, true, window, cx);
14553 }
14554
14555 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14556 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14557 let snapshot = self.snapshot(window, cx);
14558 let selection = self.selections.newest::<Point>(cx);
14559 self.go_to_hunk_before_or_after_position(
14560 &snapshot,
14561 selection.head(),
14562 Direction::Next,
14563 window,
14564 cx,
14565 );
14566 }
14567
14568 pub fn go_to_hunk_before_or_after_position(
14569 &mut self,
14570 snapshot: &EditorSnapshot,
14571 position: Point,
14572 direction: Direction,
14573 window: &mut Window,
14574 cx: &mut Context<Editor>,
14575 ) {
14576 let row = if direction == Direction::Next {
14577 self.hunk_after_position(snapshot, position)
14578 .map(|hunk| hunk.row_range.start)
14579 } else {
14580 self.hunk_before_position(snapshot, position)
14581 };
14582
14583 if let Some(row) = row {
14584 let destination = Point::new(row.0, 0);
14585 let autoscroll = Autoscroll::center();
14586
14587 self.unfold_ranges(&[destination..destination], false, false, cx);
14588 self.change_selections(Some(autoscroll), window, cx, |s| {
14589 s.select_ranges([destination..destination]);
14590 });
14591 }
14592 }
14593
14594 fn hunk_after_position(
14595 &mut self,
14596 snapshot: &EditorSnapshot,
14597 position: Point,
14598 ) -> Option<MultiBufferDiffHunk> {
14599 snapshot
14600 .buffer_snapshot
14601 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14602 .find(|hunk| hunk.row_range.start.0 > position.row)
14603 .or_else(|| {
14604 snapshot
14605 .buffer_snapshot
14606 .diff_hunks_in_range(Point::zero()..position)
14607 .find(|hunk| hunk.row_range.end.0 < position.row)
14608 })
14609 }
14610
14611 fn go_to_prev_hunk(
14612 &mut self,
14613 _: &GoToPreviousHunk,
14614 window: &mut Window,
14615 cx: &mut Context<Self>,
14616 ) {
14617 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14618 let snapshot = self.snapshot(window, cx);
14619 let selection = self.selections.newest::<Point>(cx);
14620 self.go_to_hunk_before_or_after_position(
14621 &snapshot,
14622 selection.head(),
14623 Direction::Prev,
14624 window,
14625 cx,
14626 );
14627 }
14628
14629 fn hunk_before_position(
14630 &mut self,
14631 snapshot: &EditorSnapshot,
14632 position: Point,
14633 ) -> Option<MultiBufferRow> {
14634 snapshot
14635 .buffer_snapshot
14636 .diff_hunk_before(position)
14637 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14638 }
14639
14640 fn go_to_next_change(
14641 &mut self,
14642 _: &GoToNextChange,
14643 window: &mut Window,
14644 cx: &mut Context<Self>,
14645 ) {
14646 if let Some(selections) = self
14647 .change_list
14648 .next_change(1, Direction::Next)
14649 .map(|s| s.to_vec())
14650 {
14651 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14652 let map = s.display_map();
14653 s.select_display_ranges(selections.iter().map(|a| {
14654 let point = a.to_display_point(&map);
14655 point..point
14656 }))
14657 })
14658 }
14659 }
14660
14661 fn go_to_previous_change(
14662 &mut self,
14663 _: &GoToPreviousChange,
14664 window: &mut Window,
14665 cx: &mut Context<Self>,
14666 ) {
14667 if let Some(selections) = self
14668 .change_list
14669 .next_change(1, Direction::Prev)
14670 .map(|s| s.to_vec())
14671 {
14672 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14673 let map = s.display_map();
14674 s.select_display_ranges(selections.iter().map(|a| {
14675 let point = a.to_display_point(&map);
14676 point..point
14677 }))
14678 })
14679 }
14680 }
14681
14682 fn go_to_line<T: 'static>(
14683 &mut self,
14684 position: Anchor,
14685 highlight_color: Option<Hsla>,
14686 window: &mut Window,
14687 cx: &mut Context<Self>,
14688 ) {
14689 let snapshot = self.snapshot(window, cx).display_snapshot;
14690 let position = position.to_point(&snapshot.buffer_snapshot);
14691 let start = snapshot
14692 .buffer_snapshot
14693 .clip_point(Point::new(position.row, 0), Bias::Left);
14694 let end = start + Point::new(1, 0);
14695 let start = snapshot.buffer_snapshot.anchor_before(start);
14696 let end = snapshot.buffer_snapshot.anchor_before(end);
14697
14698 self.highlight_rows::<T>(
14699 start..end,
14700 highlight_color
14701 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14702 Default::default(),
14703 cx,
14704 );
14705
14706 if self.buffer.read(cx).is_singleton() {
14707 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14708 }
14709 }
14710
14711 pub fn go_to_definition(
14712 &mut self,
14713 _: &GoToDefinition,
14714 window: &mut Window,
14715 cx: &mut Context<Self>,
14716 ) -> Task<Result<Navigated>> {
14717 let definition =
14718 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14719 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14720 cx.spawn_in(window, async move |editor, cx| {
14721 if definition.await? == Navigated::Yes {
14722 return Ok(Navigated::Yes);
14723 }
14724 match fallback_strategy {
14725 GoToDefinitionFallback::None => Ok(Navigated::No),
14726 GoToDefinitionFallback::FindAllReferences => {
14727 match editor.update_in(cx, |editor, window, cx| {
14728 editor.find_all_references(&FindAllReferences, window, cx)
14729 })? {
14730 Some(references) => references.await,
14731 None => Ok(Navigated::No),
14732 }
14733 }
14734 }
14735 })
14736 }
14737
14738 pub fn go_to_declaration(
14739 &mut self,
14740 _: &GoToDeclaration,
14741 window: &mut Window,
14742 cx: &mut Context<Self>,
14743 ) -> Task<Result<Navigated>> {
14744 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14745 }
14746
14747 pub fn go_to_declaration_split(
14748 &mut self,
14749 _: &GoToDeclaration,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) -> Task<Result<Navigated>> {
14753 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14754 }
14755
14756 pub fn go_to_implementation(
14757 &mut self,
14758 _: &GoToImplementation,
14759 window: &mut Window,
14760 cx: &mut Context<Self>,
14761 ) -> Task<Result<Navigated>> {
14762 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14763 }
14764
14765 pub fn go_to_implementation_split(
14766 &mut self,
14767 _: &GoToImplementationSplit,
14768 window: &mut Window,
14769 cx: &mut Context<Self>,
14770 ) -> Task<Result<Navigated>> {
14771 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14772 }
14773
14774 pub fn go_to_type_definition(
14775 &mut self,
14776 _: &GoToTypeDefinition,
14777 window: &mut Window,
14778 cx: &mut Context<Self>,
14779 ) -> Task<Result<Navigated>> {
14780 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14781 }
14782
14783 pub fn go_to_definition_split(
14784 &mut self,
14785 _: &GoToDefinitionSplit,
14786 window: &mut Window,
14787 cx: &mut Context<Self>,
14788 ) -> Task<Result<Navigated>> {
14789 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14790 }
14791
14792 pub fn go_to_type_definition_split(
14793 &mut self,
14794 _: &GoToTypeDefinitionSplit,
14795 window: &mut Window,
14796 cx: &mut Context<Self>,
14797 ) -> Task<Result<Navigated>> {
14798 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14799 }
14800
14801 fn go_to_definition_of_kind(
14802 &mut self,
14803 kind: GotoDefinitionKind,
14804 split: bool,
14805 window: &mut Window,
14806 cx: &mut Context<Self>,
14807 ) -> Task<Result<Navigated>> {
14808 let Some(provider) = self.semantics_provider.clone() else {
14809 return Task::ready(Ok(Navigated::No));
14810 };
14811 let head = self.selections.newest::<usize>(cx).head();
14812 let buffer = self.buffer.read(cx);
14813 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14814 text_anchor
14815 } else {
14816 return Task::ready(Ok(Navigated::No));
14817 };
14818
14819 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14820 return Task::ready(Ok(Navigated::No));
14821 };
14822
14823 cx.spawn_in(window, async move |editor, cx| {
14824 let definitions = definitions.await?;
14825 let navigated = editor
14826 .update_in(cx, |editor, window, cx| {
14827 editor.navigate_to_hover_links(
14828 Some(kind),
14829 definitions
14830 .into_iter()
14831 .filter(|location| {
14832 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14833 })
14834 .map(HoverLink::Text)
14835 .collect::<Vec<_>>(),
14836 split,
14837 window,
14838 cx,
14839 )
14840 })?
14841 .await?;
14842 anyhow::Ok(navigated)
14843 })
14844 }
14845
14846 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14847 let selection = self.selections.newest_anchor();
14848 let head = selection.head();
14849 let tail = selection.tail();
14850
14851 let Some((buffer, start_position)) =
14852 self.buffer.read(cx).text_anchor_for_position(head, cx)
14853 else {
14854 return;
14855 };
14856
14857 let end_position = if head != tail {
14858 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14859 return;
14860 };
14861 Some(pos)
14862 } else {
14863 None
14864 };
14865
14866 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14867 let url = if let Some(end_pos) = end_position {
14868 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14869 } else {
14870 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14871 };
14872
14873 if let Some(url) = url {
14874 editor.update(cx, |_, cx| {
14875 cx.open_url(&url);
14876 })
14877 } else {
14878 Ok(())
14879 }
14880 });
14881
14882 url_finder.detach();
14883 }
14884
14885 pub fn open_selected_filename(
14886 &mut self,
14887 _: &OpenSelectedFilename,
14888 window: &mut Window,
14889 cx: &mut Context<Self>,
14890 ) {
14891 let Some(workspace) = self.workspace() else {
14892 return;
14893 };
14894
14895 let position = self.selections.newest_anchor().head();
14896
14897 let Some((buffer, buffer_position)) =
14898 self.buffer.read(cx).text_anchor_for_position(position, cx)
14899 else {
14900 return;
14901 };
14902
14903 let project = self.project.clone();
14904
14905 cx.spawn_in(window, async move |_, cx| {
14906 let result = find_file(&buffer, project, buffer_position, cx).await;
14907
14908 if let Some((_, path)) = result {
14909 workspace
14910 .update_in(cx, |workspace, window, cx| {
14911 workspace.open_resolved_path(path, window, cx)
14912 })?
14913 .await?;
14914 }
14915 anyhow::Ok(())
14916 })
14917 .detach();
14918 }
14919
14920 pub(crate) fn navigate_to_hover_links(
14921 &mut self,
14922 kind: Option<GotoDefinitionKind>,
14923 mut definitions: Vec<HoverLink>,
14924 split: bool,
14925 window: &mut Window,
14926 cx: &mut Context<Editor>,
14927 ) -> Task<Result<Navigated>> {
14928 // If there is one definition, just open it directly
14929 if definitions.len() == 1 {
14930 let definition = definitions.pop().unwrap();
14931
14932 enum TargetTaskResult {
14933 Location(Option<Location>),
14934 AlreadyNavigated,
14935 }
14936
14937 let target_task = match definition {
14938 HoverLink::Text(link) => {
14939 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14940 }
14941 HoverLink::InlayHint(lsp_location, server_id) => {
14942 let computation =
14943 self.compute_target_location(lsp_location, server_id, window, cx);
14944 cx.background_spawn(async move {
14945 let location = computation.await?;
14946 Ok(TargetTaskResult::Location(location))
14947 })
14948 }
14949 HoverLink::Url(url) => {
14950 cx.open_url(&url);
14951 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14952 }
14953 HoverLink::File(path) => {
14954 if let Some(workspace) = self.workspace() {
14955 cx.spawn_in(window, async move |_, cx| {
14956 workspace
14957 .update_in(cx, |workspace, window, cx| {
14958 workspace.open_resolved_path(path, window, cx)
14959 })?
14960 .await
14961 .map(|_| TargetTaskResult::AlreadyNavigated)
14962 })
14963 } else {
14964 Task::ready(Ok(TargetTaskResult::Location(None)))
14965 }
14966 }
14967 };
14968 cx.spawn_in(window, async move |editor, cx| {
14969 let target = match target_task.await.context("target resolution task")? {
14970 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14971 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14972 TargetTaskResult::Location(Some(target)) => target,
14973 };
14974
14975 editor.update_in(cx, |editor, window, cx| {
14976 let Some(workspace) = editor.workspace() else {
14977 return Navigated::No;
14978 };
14979 let pane = workspace.read(cx).active_pane().clone();
14980
14981 let range = target.range.to_point(target.buffer.read(cx));
14982 let range = editor.range_for_match(&range);
14983 let range = collapse_multiline_range(range);
14984
14985 if !split
14986 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14987 {
14988 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14989 } else {
14990 window.defer(cx, move |window, cx| {
14991 let target_editor: Entity<Self> =
14992 workspace.update(cx, |workspace, cx| {
14993 let pane = if split {
14994 workspace.adjacent_pane(window, cx)
14995 } else {
14996 workspace.active_pane().clone()
14997 };
14998
14999 workspace.open_project_item(
15000 pane,
15001 target.buffer.clone(),
15002 true,
15003 true,
15004 window,
15005 cx,
15006 )
15007 });
15008 target_editor.update(cx, |target_editor, cx| {
15009 // When selecting a definition in a different buffer, disable the nav history
15010 // to avoid creating a history entry at the previous cursor location.
15011 pane.update(cx, |pane, _| pane.disable_history());
15012 target_editor.go_to_singleton_buffer_range(range, window, cx);
15013 pane.update(cx, |pane, _| pane.enable_history());
15014 });
15015 });
15016 }
15017 Navigated::Yes
15018 })
15019 })
15020 } else if !definitions.is_empty() {
15021 cx.spawn_in(window, async move |editor, cx| {
15022 let (title, location_tasks, workspace) = editor
15023 .update_in(cx, |editor, window, cx| {
15024 let tab_kind = match kind {
15025 Some(GotoDefinitionKind::Implementation) => "Implementations",
15026 _ => "Definitions",
15027 };
15028 let title = definitions
15029 .iter()
15030 .find_map(|definition| match definition {
15031 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15032 let buffer = origin.buffer.read(cx);
15033 format!(
15034 "{} for {}",
15035 tab_kind,
15036 buffer
15037 .text_for_range(origin.range.clone())
15038 .collect::<String>()
15039 )
15040 }),
15041 HoverLink::InlayHint(_, _) => None,
15042 HoverLink::Url(_) => None,
15043 HoverLink::File(_) => None,
15044 })
15045 .unwrap_or(tab_kind.to_string());
15046 let location_tasks = definitions
15047 .into_iter()
15048 .map(|definition| match definition {
15049 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15050 HoverLink::InlayHint(lsp_location, server_id) => editor
15051 .compute_target_location(lsp_location, server_id, window, cx),
15052 HoverLink::Url(_) => Task::ready(Ok(None)),
15053 HoverLink::File(_) => Task::ready(Ok(None)),
15054 })
15055 .collect::<Vec<_>>();
15056 (title, location_tasks, editor.workspace().clone())
15057 })
15058 .context("location tasks preparation")?;
15059
15060 let locations = future::join_all(location_tasks)
15061 .await
15062 .into_iter()
15063 .filter_map(|location| location.transpose())
15064 .collect::<Result<_>>()
15065 .context("location tasks")?;
15066
15067 let Some(workspace) = workspace else {
15068 return Ok(Navigated::No);
15069 };
15070 let opened = workspace
15071 .update_in(cx, |workspace, window, cx| {
15072 Self::open_locations_in_multibuffer(
15073 workspace,
15074 locations,
15075 title,
15076 split,
15077 MultibufferSelectionMode::First,
15078 window,
15079 cx,
15080 )
15081 })
15082 .ok();
15083
15084 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15085 })
15086 } else {
15087 Task::ready(Ok(Navigated::No))
15088 }
15089 }
15090
15091 fn compute_target_location(
15092 &self,
15093 lsp_location: lsp::Location,
15094 server_id: LanguageServerId,
15095 window: &mut Window,
15096 cx: &mut Context<Self>,
15097 ) -> Task<anyhow::Result<Option<Location>>> {
15098 let Some(project) = self.project.clone() else {
15099 return Task::ready(Ok(None));
15100 };
15101
15102 cx.spawn_in(window, async move |editor, cx| {
15103 let location_task = editor.update(cx, |_, cx| {
15104 project.update(cx, |project, cx| {
15105 let language_server_name = project
15106 .language_server_statuses(cx)
15107 .find(|(id, _)| server_id == *id)
15108 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15109 language_server_name.map(|language_server_name| {
15110 project.open_local_buffer_via_lsp(
15111 lsp_location.uri.clone(),
15112 server_id,
15113 language_server_name,
15114 cx,
15115 )
15116 })
15117 })
15118 })?;
15119 let location = match location_task {
15120 Some(task) => Some({
15121 let target_buffer_handle = task.await.context("open local buffer")?;
15122 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15123 let target_start = target_buffer
15124 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15125 let target_end = target_buffer
15126 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15127 target_buffer.anchor_after(target_start)
15128 ..target_buffer.anchor_before(target_end)
15129 })?;
15130 Location {
15131 buffer: target_buffer_handle,
15132 range,
15133 }
15134 }),
15135 None => None,
15136 };
15137 Ok(location)
15138 })
15139 }
15140
15141 pub fn find_all_references(
15142 &mut self,
15143 _: &FindAllReferences,
15144 window: &mut Window,
15145 cx: &mut Context<Self>,
15146 ) -> Option<Task<Result<Navigated>>> {
15147 let selection = self.selections.newest::<usize>(cx);
15148 let multi_buffer = self.buffer.read(cx);
15149 let head = selection.head();
15150
15151 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15152 let head_anchor = multi_buffer_snapshot.anchor_at(
15153 head,
15154 if head < selection.tail() {
15155 Bias::Right
15156 } else {
15157 Bias::Left
15158 },
15159 );
15160
15161 match self
15162 .find_all_references_task_sources
15163 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15164 {
15165 Ok(_) => {
15166 log::info!(
15167 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15168 );
15169 return None;
15170 }
15171 Err(i) => {
15172 self.find_all_references_task_sources.insert(i, head_anchor);
15173 }
15174 }
15175
15176 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15177 let workspace = self.workspace()?;
15178 let project = workspace.read(cx).project().clone();
15179 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15180 Some(cx.spawn_in(window, async move |editor, cx| {
15181 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15182 if let Ok(i) = editor
15183 .find_all_references_task_sources
15184 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15185 {
15186 editor.find_all_references_task_sources.remove(i);
15187 }
15188 });
15189
15190 let locations = references.await?;
15191 if locations.is_empty() {
15192 return anyhow::Ok(Navigated::No);
15193 }
15194
15195 workspace.update_in(cx, |workspace, window, cx| {
15196 let title = locations
15197 .first()
15198 .as_ref()
15199 .map(|location| {
15200 let buffer = location.buffer.read(cx);
15201 format!(
15202 "References to `{}`",
15203 buffer
15204 .text_for_range(location.range.clone())
15205 .collect::<String>()
15206 )
15207 })
15208 .unwrap();
15209 Self::open_locations_in_multibuffer(
15210 workspace,
15211 locations,
15212 title,
15213 false,
15214 MultibufferSelectionMode::First,
15215 window,
15216 cx,
15217 );
15218 Navigated::Yes
15219 })
15220 }))
15221 }
15222
15223 /// Opens a multibuffer with the given project locations in it
15224 pub fn open_locations_in_multibuffer(
15225 workspace: &mut Workspace,
15226 mut locations: Vec<Location>,
15227 title: String,
15228 split: bool,
15229 multibuffer_selection_mode: MultibufferSelectionMode,
15230 window: &mut Window,
15231 cx: &mut Context<Workspace>,
15232 ) {
15233 // If there are multiple definitions, open them in a multibuffer
15234 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15235 let mut locations = locations.into_iter().peekable();
15236 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15237 let capability = workspace.project().read(cx).capability();
15238
15239 let excerpt_buffer = cx.new(|cx| {
15240 let mut multibuffer = MultiBuffer::new(capability);
15241 while let Some(location) = locations.next() {
15242 let buffer = location.buffer.read(cx);
15243 let mut ranges_for_buffer = Vec::new();
15244 let range = location.range.to_point(buffer);
15245 ranges_for_buffer.push(range.clone());
15246
15247 while let Some(next_location) = locations.peek() {
15248 if next_location.buffer == location.buffer {
15249 ranges_for_buffer.push(next_location.range.to_point(buffer));
15250 locations.next();
15251 } else {
15252 break;
15253 }
15254 }
15255
15256 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15257 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15258 PathKey::for_buffer(&location.buffer, cx),
15259 location.buffer.clone(),
15260 ranges_for_buffer,
15261 DEFAULT_MULTIBUFFER_CONTEXT,
15262 cx,
15263 );
15264 ranges.extend(new_ranges)
15265 }
15266
15267 multibuffer.with_title(title)
15268 });
15269
15270 let editor = cx.new(|cx| {
15271 Editor::for_multibuffer(
15272 excerpt_buffer,
15273 Some(workspace.project().clone()),
15274 window,
15275 cx,
15276 )
15277 });
15278 editor.update(cx, |editor, cx| {
15279 match multibuffer_selection_mode {
15280 MultibufferSelectionMode::First => {
15281 if let Some(first_range) = ranges.first() {
15282 editor.change_selections(None, window, cx, |selections| {
15283 selections.clear_disjoint();
15284 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15285 });
15286 }
15287 editor.highlight_background::<Self>(
15288 &ranges,
15289 |theme| theme.editor_highlighted_line_background,
15290 cx,
15291 );
15292 }
15293 MultibufferSelectionMode::All => {
15294 editor.change_selections(None, window, cx, |selections| {
15295 selections.clear_disjoint();
15296 selections.select_anchor_ranges(ranges);
15297 });
15298 }
15299 }
15300 editor.register_buffers_with_language_servers(cx);
15301 });
15302
15303 let item = Box::new(editor);
15304 let item_id = item.item_id();
15305
15306 if split {
15307 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15308 } else {
15309 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15310 let (preview_item_id, preview_item_idx) =
15311 workspace.active_pane().read_with(cx, |pane, _| {
15312 (pane.preview_item_id(), pane.preview_item_idx())
15313 });
15314
15315 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15316
15317 if let Some(preview_item_id) = preview_item_id {
15318 workspace.active_pane().update(cx, |pane, cx| {
15319 pane.remove_item(preview_item_id, false, false, window, cx);
15320 });
15321 }
15322 } else {
15323 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15324 }
15325 }
15326 workspace.active_pane().update(cx, |pane, cx| {
15327 pane.set_preview_item_id(Some(item_id), cx);
15328 });
15329 }
15330
15331 pub fn rename(
15332 &mut self,
15333 _: &Rename,
15334 window: &mut Window,
15335 cx: &mut Context<Self>,
15336 ) -> Option<Task<Result<()>>> {
15337 use language::ToOffset as _;
15338
15339 let provider = self.semantics_provider.clone()?;
15340 let selection = self.selections.newest_anchor().clone();
15341 let (cursor_buffer, cursor_buffer_position) = self
15342 .buffer
15343 .read(cx)
15344 .text_anchor_for_position(selection.head(), cx)?;
15345 let (tail_buffer, cursor_buffer_position_end) = self
15346 .buffer
15347 .read(cx)
15348 .text_anchor_for_position(selection.tail(), cx)?;
15349 if tail_buffer != cursor_buffer {
15350 return None;
15351 }
15352
15353 let snapshot = cursor_buffer.read(cx).snapshot();
15354 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15355 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15356 let prepare_rename = provider
15357 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15358 .unwrap_or_else(|| Task::ready(Ok(None)));
15359 drop(snapshot);
15360
15361 Some(cx.spawn_in(window, async move |this, cx| {
15362 let rename_range = if let Some(range) = prepare_rename.await? {
15363 Some(range)
15364 } else {
15365 this.update(cx, |this, cx| {
15366 let buffer = this.buffer.read(cx).snapshot(cx);
15367 let mut buffer_highlights = this
15368 .document_highlights_for_position(selection.head(), &buffer)
15369 .filter(|highlight| {
15370 highlight.start.excerpt_id == selection.head().excerpt_id
15371 && highlight.end.excerpt_id == selection.head().excerpt_id
15372 });
15373 buffer_highlights
15374 .next()
15375 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15376 })?
15377 };
15378 if let Some(rename_range) = rename_range {
15379 this.update_in(cx, |this, window, cx| {
15380 let snapshot = cursor_buffer.read(cx).snapshot();
15381 let rename_buffer_range = rename_range.to_offset(&snapshot);
15382 let cursor_offset_in_rename_range =
15383 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15384 let cursor_offset_in_rename_range_end =
15385 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15386
15387 this.take_rename(false, window, cx);
15388 let buffer = this.buffer.read(cx).read(cx);
15389 let cursor_offset = selection.head().to_offset(&buffer);
15390 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15391 let rename_end = rename_start + rename_buffer_range.len();
15392 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15393 let mut old_highlight_id = None;
15394 let old_name: Arc<str> = buffer
15395 .chunks(rename_start..rename_end, true)
15396 .map(|chunk| {
15397 if old_highlight_id.is_none() {
15398 old_highlight_id = chunk.syntax_highlight_id;
15399 }
15400 chunk.text
15401 })
15402 .collect::<String>()
15403 .into();
15404
15405 drop(buffer);
15406
15407 // Position the selection in the rename editor so that it matches the current selection.
15408 this.show_local_selections = false;
15409 let rename_editor = cx.new(|cx| {
15410 let mut editor = Editor::single_line(window, cx);
15411 editor.buffer.update(cx, |buffer, cx| {
15412 buffer.edit([(0..0, old_name.clone())], None, cx)
15413 });
15414 let rename_selection_range = match cursor_offset_in_rename_range
15415 .cmp(&cursor_offset_in_rename_range_end)
15416 {
15417 Ordering::Equal => {
15418 editor.select_all(&SelectAll, window, cx);
15419 return editor;
15420 }
15421 Ordering::Less => {
15422 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15423 }
15424 Ordering::Greater => {
15425 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15426 }
15427 };
15428 if rename_selection_range.end > old_name.len() {
15429 editor.select_all(&SelectAll, window, cx);
15430 } else {
15431 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15432 s.select_ranges([rename_selection_range]);
15433 });
15434 }
15435 editor
15436 });
15437 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15438 if e == &EditorEvent::Focused {
15439 cx.emit(EditorEvent::FocusedIn)
15440 }
15441 })
15442 .detach();
15443
15444 let write_highlights =
15445 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15446 let read_highlights =
15447 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15448 let ranges = write_highlights
15449 .iter()
15450 .flat_map(|(_, ranges)| ranges.iter())
15451 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15452 .cloned()
15453 .collect();
15454
15455 this.highlight_text::<Rename>(
15456 ranges,
15457 HighlightStyle {
15458 fade_out: Some(0.6),
15459 ..Default::default()
15460 },
15461 cx,
15462 );
15463 let rename_focus_handle = rename_editor.focus_handle(cx);
15464 window.focus(&rename_focus_handle);
15465 let block_id = this.insert_blocks(
15466 [BlockProperties {
15467 style: BlockStyle::Flex,
15468 placement: BlockPlacement::Below(range.start),
15469 height: Some(1),
15470 render: Arc::new({
15471 let rename_editor = rename_editor.clone();
15472 move |cx: &mut BlockContext| {
15473 let mut text_style = cx.editor_style.text.clone();
15474 if let Some(highlight_style) = old_highlight_id
15475 .and_then(|h| h.style(&cx.editor_style.syntax))
15476 {
15477 text_style = text_style.highlight(highlight_style);
15478 }
15479 div()
15480 .block_mouse_except_scroll()
15481 .pl(cx.anchor_x)
15482 .child(EditorElement::new(
15483 &rename_editor,
15484 EditorStyle {
15485 background: cx.theme().system().transparent,
15486 local_player: cx.editor_style.local_player,
15487 text: text_style,
15488 scrollbar_width: cx.editor_style.scrollbar_width,
15489 syntax: cx.editor_style.syntax.clone(),
15490 status: cx.editor_style.status.clone(),
15491 inlay_hints_style: HighlightStyle {
15492 font_weight: Some(FontWeight::BOLD),
15493 ..make_inlay_hints_style(cx.app)
15494 },
15495 inline_completion_styles: make_suggestion_styles(
15496 cx.app,
15497 ),
15498 ..EditorStyle::default()
15499 },
15500 ))
15501 .into_any_element()
15502 }
15503 }),
15504 priority: 0,
15505 render_in_minimap: true,
15506 }],
15507 Some(Autoscroll::fit()),
15508 cx,
15509 )[0];
15510 this.pending_rename = Some(RenameState {
15511 range,
15512 old_name,
15513 editor: rename_editor,
15514 block_id,
15515 });
15516 })?;
15517 }
15518
15519 Ok(())
15520 }))
15521 }
15522
15523 pub fn confirm_rename(
15524 &mut self,
15525 _: &ConfirmRename,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) -> Option<Task<Result<()>>> {
15529 let rename = self.take_rename(false, window, cx)?;
15530 let workspace = self.workspace()?.downgrade();
15531 let (buffer, start) = self
15532 .buffer
15533 .read(cx)
15534 .text_anchor_for_position(rename.range.start, cx)?;
15535 let (end_buffer, _) = self
15536 .buffer
15537 .read(cx)
15538 .text_anchor_for_position(rename.range.end, cx)?;
15539 if buffer != end_buffer {
15540 return None;
15541 }
15542
15543 let old_name = rename.old_name;
15544 let new_name = rename.editor.read(cx).text(cx);
15545
15546 let rename = self.semantics_provider.as_ref()?.perform_rename(
15547 &buffer,
15548 start,
15549 new_name.clone(),
15550 cx,
15551 )?;
15552
15553 Some(cx.spawn_in(window, async move |editor, cx| {
15554 let project_transaction = rename.await?;
15555 Self::open_project_transaction(
15556 &editor,
15557 workspace,
15558 project_transaction,
15559 format!("Rename: {} → {}", old_name, new_name),
15560 cx,
15561 )
15562 .await?;
15563
15564 editor.update(cx, |editor, cx| {
15565 editor.refresh_document_highlights(cx);
15566 })?;
15567 Ok(())
15568 }))
15569 }
15570
15571 fn take_rename(
15572 &mut self,
15573 moving_cursor: bool,
15574 window: &mut Window,
15575 cx: &mut Context<Self>,
15576 ) -> Option<RenameState> {
15577 let rename = self.pending_rename.take()?;
15578 if rename.editor.focus_handle(cx).is_focused(window) {
15579 window.focus(&self.focus_handle);
15580 }
15581
15582 self.remove_blocks(
15583 [rename.block_id].into_iter().collect(),
15584 Some(Autoscroll::fit()),
15585 cx,
15586 );
15587 self.clear_highlights::<Rename>(cx);
15588 self.show_local_selections = true;
15589
15590 if moving_cursor {
15591 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15592 editor.selections.newest::<usize>(cx).head()
15593 });
15594
15595 // Update the selection to match the position of the selection inside
15596 // the rename editor.
15597 let snapshot = self.buffer.read(cx).read(cx);
15598 let rename_range = rename.range.to_offset(&snapshot);
15599 let cursor_in_editor = snapshot
15600 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15601 .min(rename_range.end);
15602 drop(snapshot);
15603
15604 self.change_selections(None, window, cx, |s| {
15605 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15606 });
15607 } else {
15608 self.refresh_document_highlights(cx);
15609 }
15610
15611 Some(rename)
15612 }
15613
15614 pub fn pending_rename(&self) -> Option<&RenameState> {
15615 self.pending_rename.as_ref()
15616 }
15617
15618 fn format(
15619 &mut self,
15620 _: &Format,
15621 window: &mut Window,
15622 cx: &mut Context<Self>,
15623 ) -> Option<Task<Result<()>>> {
15624 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15625
15626 let project = match &self.project {
15627 Some(project) => project.clone(),
15628 None => return None,
15629 };
15630
15631 Some(self.perform_format(
15632 project,
15633 FormatTrigger::Manual,
15634 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15635 window,
15636 cx,
15637 ))
15638 }
15639
15640 fn format_selections(
15641 &mut self,
15642 _: &FormatSelections,
15643 window: &mut Window,
15644 cx: &mut Context<Self>,
15645 ) -> Option<Task<Result<()>>> {
15646 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15647
15648 let project = match &self.project {
15649 Some(project) => project.clone(),
15650 None => return None,
15651 };
15652
15653 let ranges = self
15654 .selections
15655 .all_adjusted(cx)
15656 .into_iter()
15657 .map(|selection| selection.range())
15658 .collect_vec();
15659
15660 Some(self.perform_format(
15661 project,
15662 FormatTrigger::Manual,
15663 FormatTarget::Ranges(ranges),
15664 window,
15665 cx,
15666 ))
15667 }
15668
15669 fn perform_format(
15670 &mut self,
15671 project: Entity<Project>,
15672 trigger: FormatTrigger,
15673 target: FormatTarget,
15674 window: &mut Window,
15675 cx: &mut Context<Self>,
15676 ) -> Task<Result<()>> {
15677 let buffer = self.buffer.clone();
15678 let (buffers, target) = match target {
15679 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15680 FormatTarget::Ranges(selection_ranges) => {
15681 let multi_buffer = buffer.read(cx);
15682 let snapshot = multi_buffer.read(cx);
15683 let mut buffers = HashSet::default();
15684 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15685 BTreeMap::new();
15686 for selection_range in selection_ranges {
15687 for (buffer, buffer_range, _) in
15688 snapshot.range_to_buffer_ranges(selection_range)
15689 {
15690 let buffer_id = buffer.remote_id();
15691 let start = buffer.anchor_before(buffer_range.start);
15692 let end = buffer.anchor_after(buffer_range.end);
15693 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15694 buffer_id_to_ranges
15695 .entry(buffer_id)
15696 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15697 .or_insert_with(|| vec![start..end]);
15698 }
15699 }
15700 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15701 }
15702 };
15703
15704 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15705 let selections_prev = transaction_id_prev
15706 .and_then(|transaction_id_prev| {
15707 // default to selections as they were after the last edit, if we have them,
15708 // instead of how they are now.
15709 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15710 // will take you back to where you made the last edit, instead of staying where you scrolled
15711 self.selection_history
15712 .transaction(transaction_id_prev)
15713 .map(|t| t.0.clone())
15714 })
15715 .unwrap_or_else(|| {
15716 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15717 self.selections.disjoint_anchors()
15718 });
15719
15720 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15721 let format = project.update(cx, |project, cx| {
15722 project.format(buffers, target, true, trigger, cx)
15723 });
15724
15725 cx.spawn_in(window, async move |editor, cx| {
15726 let transaction = futures::select_biased! {
15727 transaction = format.log_err().fuse() => transaction,
15728 () = timeout => {
15729 log::warn!("timed out waiting for formatting");
15730 None
15731 }
15732 };
15733
15734 buffer
15735 .update(cx, |buffer, cx| {
15736 if let Some(transaction) = transaction {
15737 if !buffer.is_singleton() {
15738 buffer.push_transaction(&transaction.0, cx);
15739 }
15740 }
15741 cx.notify();
15742 })
15743 .ok();
15744
15745 if let Some(transaction_id_now) =
15746 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15747 {
15748 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15749 if has_new_transaction {
15750 _ = editor.update(cx, |editor, _| {
15751 editor
15752 .selection_history
15753 .insert_transaction(transaction_id_now, selections_prev);
15754 });
15755 }
15756 }
15757
15758 Ok(())
15759 })
15760 }
15761
15762 fn organize_imports(
15763 &mut self,
15764 _: &OrganizeImports,
15765 window: &mut Window,
15766 cx: &mut Context<Self>,
15767 ) -> Option<Task<Result<()>>> {
15768 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15769 let project = match &self.project {
15770 Some(project) => project.clone(),
15771 None => return None,
15772 };
15773 Some(self.perform_code_action_kind(
15774 project,
15775 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15776 window,
15777 cx,
15778 ))
15779 }
15780
15781 fn perform_code_action_kind(
15782 &mut self,
15783 project: Entity<Project>,
15784 kind: CodeActionKind,
15785 window: &mut Window,
15786 cx: &mut Context<Self>,
15787 ) -> Task<Result<()>> {
15788 let buffer = self.buffer.clone();
15789 let buffers = buffer.read(cx).all_buffers();
15790 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15791 let apply_action = project.update(cx, |project, cx| {
15792 project.apply_code_action_kind(buffers, kind, true, cx)
15793 });
15794 cx.spawn_in(window, async move |_, cx| {
15795 let transaction = futures::select_biased! {
15796 () = timeout => {
15797 log::warn!("timed out waiting for executing code action");
15798 None
15799 }
15800 transaction = apply_action.log_err().fuse() => transaction,
15801 };
15802 buffer
15803 .update(cx, |buffer, cx| {
15804 // check if we need this
15805 if let Some(transaction) = transaction {
15806 if !buffer.is_singleton() {
15807 buffer.push_transaction(&transaction.0, cx);
15808 }
15809 }
15810 cx.notify();
15811 })
15812 .ok();
15813 Ok(())
15814 })
15815 }
15816
15817 fn restart_language_server(
15818 &mut self,
15819 _: &RestartLanguageServer,
15820 _: &mut Window,
15821 cx: &mut Context<Self>,
15822 ) {
15823 if let Some(project) = self.project.clone() {
15824 self.buffer.update(cx, |multi_buffer, cx| {
15825 project.update(cx, |project, cx| {
15826 project.restart_language_servers_for_buffers(
15827 multi_buffer.all_buffers().into_iter().collect(),
15828 cx,
15829 );
15830 });
15831 })
15832 }
15833 }
15834
15835 fn stop_language_server(
15836 &mut self,
15837 _: &StopLanguageServer,
15838 _: &mut Window,
15839 cx: &mut Context<Self>,
15840 ) {
15841 if let Some(project) = self.project.clone() {
15842 self.buffer.update(cx, |multi_buffer, cx| {
15843 project.update(cx, |project, cx| {
15844 project.stop_language_servers_for_buffers(
15845 multi_buffer.all_buffers().into_iter().collect(),
15846 cx,
15847 );
15848 cx.emit(project::Event::RefreshInlayHints);
15849 });
15850 });
15851 }
15852 }
15853
15854 fn cancel_language_server_work(
15855 workspace: &mut Workspace,
15856 _: &actions::CancelLanguageServerWork,
15857 _: &mut Window,
15858 cx: &mut Context<Workspace>,
15859 ) {
15860 let project = workspace.project();
15861 let buffers = workspace
15862 .active_item(cx)
15863 .and_then(|item| item.act_as::<Editor>(cx))
15864 .map_or(HashSet::default(), |editor| {
15865 editor.read(cx).buffer.read(cx).all_buffers()
15866 });
15867 project.update(cx, |project, cx| {
15868 project.cancel_language_server_work_for_buffers(buffers, cx);
15869 });
15870 }
15871
15872 fn show_character_palette(
15873 &mut self,
15874 _: &ShowCharacterPalette,
15875 window: &mut Window,
15876 _: &mut Context<Self>,
15877 ) {
15878 window.show_character_palette();
15879 }
15880
15881 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15882 if self.mode.is_minimap() {
15883 return;
15884 }
15885
15886 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15887 let buffer = self.buffer.read(cx).snapshot(cx);
15888 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15889 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15890 let is_valid = buffer
15891 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15892 .any(|entry| {
15893 entry.diagnostic.is_primary
15894 && !entry.range.is_empty()
15895 && entry.range.start == primary_range_start
15896 && entry.diagnostic.message == active_diagnostics.active_message
15897 });
15898
15899 if !is_valid {
15900 self.dismiss_diagnostics(cx);
15901 }
15902 }
15903 }
15904
15905 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15906 match &self.active_diagnostics {
15907 ActiveDiagnostic::Group(group) => Some(group),
15908 _ => None,
15909 }
15910 }
15911
15912 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15913 self.dismiss_diagnostics(cx);
15914 self.active_diagnostics = ActiveDiagnostic::All;
15915 }
15916
15917 fn activate_diagnostics(
15918 &mut self,
15919 buffer_id: BufferId,
15920 diagnostic: DiagnosticEntry<usize>,
15921 window: &mut Window,
15922 cx: &mut Context<Self>,
15923 ) {
15924 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15925 return;
15926 }
15927 self.dismiss_diagnostics(cx);
15928 let snapshot = self.snapshot(window, cx);
15929 let buffer = self.buffer.read(cx).snapshot(cx);
15930 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15931 return;
15932 };
15933
15934 let diagnostic_group = buffer
15935 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15936 .collect::<Vec<_>>();
15937
15938 let blocks =
15939 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15940
15941 let blocks = self.display_map.update(cx, |display_map, cx| {
15942 display_map.insert_blocks(blocks, cx).into_iter().collect()
15943 });
15944 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15945 active_range: buffer.anchor_before(diagnostic.range.start)
15946 ..buffer.anchor_after(diagnostic.range.end),
15947 active_message: diagnostic.diagnostic.message.clone(),
15948 group_id: diagnostic.diagnostic.group_id,
15949 blocks,
15950 });
15951 cx.notify();
15952 }
15953
15954 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15955 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15956 return;
15957 };
15958
15959 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15960 if let ActiveDiagnostic::Group(group) = prev {
15961 self.display_map.update(cx, |display_map, cx| {
15962 display_map.remove_blocks(group.blocks, cx);
15963 });
15964 cx.notify();
15965 }
15966 }
15967
15968 /// Disable inline diagnostics rendering for this editor.
15969 pub fn disable_inline_diagnostics(&mut self) {
15970 self.inline_diagnostics_enabled = false;
15971 self.inline_diagnostics_update = Task::ready(());
15972 self.inline_diagnostics.clear();
15973 }
15974
15975 pub fn diagnostics_enabled(&self) -> bool {
15976 self.mode.is_full()
15977 }
15978
15979 pub fn inline_diagnostics_enabled(&self) -> bool {
15980 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15981 }
15982
15983 pub fn show_inline_diagnostics(&self) -> bool {
15984 self.show_inline_diagnostics
15985 }
15986
15987 pub fn toggle_inline_diagnostics(
15988 &mut self,
15989 _: &ToggleInlineDiagnostics,
15990 window: &mut Window,
15991 cx: &mut Context<Editor>,
15992 ) {
15993 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15994 self.refresh_inline_diagnostics(false, window, cx);
15995 }
15996
15997 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15998 self.diagnostics_max_severity = severity;
15999 self.display_map.update(cx, |display_map, _| {
16000 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16001 });
16002 }
16003
16004 pub fn toggle_diagnostics(
16005 &mut self,
16006 _: &ToggleDiagnostics,
16007 window: &mut Window,
16008 cx: &mut Context<Editor>,
16009 ) {
16010 if !self.diagnostics_enabled() {
16011 return;
16012 }
16013
16014 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16015 EditorSettings::get_global(cx)
16016 .diagnostics_max_severity
16017 .filter(|severity| severity != &DiagnosticSeverity::Off)
16018 .unwrap_or(DiagnosticSeverity::Hint)
16019 } else {
16020 DiagnosticSeverity::Off
16021 };
16022 self.set_max_diagnostics_severity(new_severity, cx);
16023 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16024 self.active_diagnostics = ActiveDiagnostic::None;
16025 self.inline_diagnostics_update = Task::ready(());
16026 self.inline_diagnostics.clear();
16027 } else {
16028 self.refresh_inline_diagnostics(false, window, cx);
16029 }
16030
16031 cx.notify();
16032 }
16033
16034 pub fn toggle_minimap(
16035 &mut self,
16036 _: &ToggleMinimap,
16037 window: &mut Window,
16038 cx: &mut Context<Editor>,
16039 ) {
16040 if self.supports_minimap(cx) {
16041 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16042 }
16043 }
16044
16045 fn refresh_inline_diagnostics(
16046 &mut self,
16047 debounce: bool,
16048 window: &mut Window,
16049 cx: &mut Context<Self>,
16050 ) {
16051 let max_severity = ProjectSettings::get_global(cx)
16052 .diagnostics
16053 .inline
16054 .max_severity
16055 .unwrap_or(self.diagnostics_max_severity);
16056
16057 if !self.inline_diagnostics_enabled()
16058 || !self.show_inline_diagnostics
16059 || max_severity == DiagnosticSeverity::Off
16060 {
16061 self.inline_diagnostics_update = Task::ready(());
16062 self.inline_diagnostics.clear();
16063 return;
16064 }
16065
16066 let debounce_ms = ProjectSettings::get_global(cx)
16067 .diagnostics
16068 .inline
16069 .update_debounce_ms;
16070 let debounce = if debounce && debounce_ms > 0 {
16071 Some(Duration::from_millis(debounce_ms))
16072 } else {
16073 None
16074 };
16075 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16076 if let Some(debounce) = debounce {
16077 cx.background_executor().timer(debounce).await;
16078 }
16079 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16080 editor
16081 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16082 .ok()
16083 }) else {
16084 return;
16085 };
16086
16087 let new_inline_diagnostics = cx
16088 .background_spawn(async move {
16089 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16090 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16091 let message = diagnostic_entry
16092 .diagnostic
16093 .message
16094 .split_once('\n')
16095 .map(|(line, _)| line)
16096 .map(SharedString::new)
16097 .unwrap_or_else(|| {
16098 SharedString::from(diagnostic_entry.diagnostic.message)
16099 });
16100 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16101 let (Ok(i) | Err(i)) = inline_diagnostics
16102 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16103 inline_diagnostics.insert(
16104 i,
16105 (
16106 start_anchor,
16107 InlineDiagnostic {
16108 message,
16109 group_id: diagnostic_entry.diagnostic.group_id,
16110 start: diagnostic_entry.range.start.to_point(&snapshot),
16111 is_primary: diagnostic_entry.diagnostic.is_primary,
16112 severity: diagnostic_entry.diagnostic.severity,
16113 },
16114 ),
16115 );
16116 }
16117 inline_diagnostics
16118 })
16119 .await;
16120
16121 editor
16122 .update(cx, |editor, cx| {
16123 editor.inline_diagnostics = new_inline_diagnostics;
16124 cx.notify();
16125 })
16126 .ok();
16127 });
16128 }
16129
16130 fn pull_diagnostics(
16131 &mut self,
16132 buffer_id: Option<BufferId>,
16133 window: &Window,
16134 cx: &mut Context<Self>,
16135 ) -> Option<()> {
16136 if !self.mode().is_full() {
16137 return None;
16138 }
16139 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16140 .diagnostics
16141 .lsp_pull_diagnostics;
16142 if !pull_diagnostics_settings.enabled {
16143 return None;
16144 }
16145 let project = self.project.as_ref()?.downgrade();
16146 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16147 let mut buffers = self.buffer.read(cx).all_buffers();
16148 if let Some(buffer_id) = buffer_id {
16149 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16150 }
16151
16152 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16153 cx.background_executor().timer(debounce).await;
16154
16155 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16156 buffers
16157 .into_iter()
16158 .flat_map(|buffer| {
16159 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16160 })
16161 .collect::<FuturesUnordered<_>>()
16162 }) else {
16163 return;
16164 };
16165
16166 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16167 match pull_task {
16168 Ok(()) => {
16169 if editor
16170 .update_in(cx, |editor, window, cx| {
16171 editor.update_diagnostics_state(window, cx);
16172 })
16173 .is_err()
16174 {
16175 return;
16176 }
16177 }
16178 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16179 }
16180 }
16181 });
16182
16183 Some(())
16184 }
16185
16186 pub fn set_selections_from_remote(
16187 &mut self,
16188 selections: Vec<Selection<Anchor>>,
16189 pending_selection: Option<Selection<Anchor>>,
16190 window: &mut Window,
16191 cx: &mut Context<Self>,
16192 ) {
16193 let old_cursor_position = self.selections.newest_anchor().head();
16194 self.selections.change_with(cx, |s| {
16195 s.select_anchors(selections);
16196 if let Some(pending_selection) = pending_selection {
16197 s.set_pending(pending_selection, SelectMode::Character);
16198 } else {
16199 s.clear_pending();
16200 }
16201 });
16202 self.selections_did_change(
16203 false,
16204 &old_cursor_position,
16205 SelectionEffects::default(),
16206 window,
16207 cx,
16208 );
16209 }
16210
16211 pub fn transact(
16212 &mut self,
16213 window: &mut Window,
16214 cx: &mut Context<Self>,
16215 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16216 ) -> Option<TransactionId> {
16217 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16218 this.start_transaction_at(Instant::now(), window, cx);
16219 update(this, window, cx);
16220 this.end_transaction_at(Instant::now(), cx)
16221 })
16222 }
16223
16224 pub fn start_transaction_at(
16225 &mut self,
16226 now: Instant,
16227 window: &mut Window,
16228 cx: &mut Context<Self>,
16229 ) {
16230 self.end_selection(window, cx);
16231 if let Some(tx_id) = self
16232 .buffer
16233 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16234 {
16235 self.selection_history
16236 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16237 cx.emit(EditorEvent::TransactionBegun {
16238 transaction_id: tx_id,
16239 })
16240 }
16241 }
16242
16243 pub fn end_transaction_at(
16244 &mut self,
16245 now: Instant,
16246 cx: &mut Context<Self>,
16247 ) -> Option<TransactionId> {
16248 if let Some(transaction_id) = self
16249 .buffer
16250 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16251 {
16252 if let Some((_, end_selections)) =
16253 self.selection_history.transaction_mut(transaction_id)
16254 {
16255 *end_selections = Some(self.selections.disjoint_anchors());
16256 } else {
16257 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16258 }
16259
16260 cx.emit(EditorEvent::Edited { transaction_id });
16261 Some(transaction_id)
16262 } else {
16263 None
16264 }
16265 }
16266
16267 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16268 if self.selection_mark_mode {
16269 self.change_selections(None, window, cx, |s| {
16270 s.move_with(|_, sel| {
16271 sel.collapse_to(sel.head(), SelectionGoal::None);
16272 });
16273 })
16274 }
16275 self.selection_mark_mode = true;
16276 cx.notify();
16277 }
16278
16279 pub fn swap_selection_ends(
16280 &mut self,
16281 _: &actions::SwapSelectionEnds,
16282 window: &mut Window,
16283 cx: &mut Context<Self>,
16284 ) {
16285 self.change_selections(None, window, cx, |s| {
16286 s.move_with(|_, sel| {
16287 if sel.start != sel.end {
16288 sel.reversed = !sel.reversed
16289 }
16290 });
16291 });
16292 self.request_autoscroll(Autoscroll::newest(), cx);
16293 cx.notify();
16294 }
16295
16296 pub fn toggle_fold(
16297 &mut self,
16298 _: &actions::ToggleFold,
16299 window: &mut Window,
16300 cx: &mut Context<Self>,
16301 ) {
16302 if self.is_singleton(cx) {
16303 let selection = self.selections.newest::<Point>(cx);
16304
16305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16306 let range = if selection.is_empty() {
16307 let point = selection.head().to_display_point(&display_map);
16308 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16309 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16310 .to_point(&display_map);
16311 start..end
16312 } else {
16313 selection.range()
16314 };
16315 if display_map.folds_in_range(range).next().is_some() {
16316 self.unfold_lines(&Default::default(), window, cx)
16317 } else {
16318 self.fold(&Default::default(), window, cx)
16319 }
16320 } else {
16321 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16322 let buffer_ids: HashSet<_> = self
16323 .selections
16324 .disjoint_anchor_ranges()
16325 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16326 .collect();
16327
16328 let should_unfold = buffer_ids
16329 .iter()
16330 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16331
16332 for buffer_id in buffer_ids {
16333 if should_unfold {
16334 self.unfold_buffer(buffer_id, cx);
16335 } else {
16336 self.fold_buffer(buffer_id, cx);
16337 }
16338 }
16339 }
16340 }
16341
16342 pub fn toggle_fold_recursive(
16343 &mut self,
16344 _: &actions::ToggleFoldRecursive,
16345 window: &mut Window,
16346 cx: &mut Context<Self>,
16347 ) {
16348 let selection = self.selections.newest::<Point>(cx);
16349
16350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16351 let range = if selection.is_empty() {
16352 let point = selection.head().to_display_point(&display_map);
16353 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16354 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16355 .to_point(&display_map);
16356 start..end
16357 } else {
16358 selection.range()
16359 };
16360 if display_map.folds_in_range(range).next().is_some() {
16361 self.unfold_recursive(&Default::default(), window, cx)
16362 } else {
16363 self.fold_recursive(&Default::default(), window, cx)
16364 }
16365 }
16366
16367 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16368 if self.is_singleton(cx) {
16369 let mut to_fold = Vec::new();
16370 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16371 let selections = self.selections.all_adjusted(cx);
16372
16373 for selection in selections {
16374 let range = selection.range().sorted();
16375 let buffer_start_row = range.start.row;
16376
16377 if range.start.row != range.end.row {
16378 let mut found = false;
16379 let mut row = range.start.row;
16380 while row <= range.end.row {
16381 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16382 {
16383 found = true;
16384 row = crease.range().end.row + 1;
16385 to_fold.push(crease);
16386 } else {
16387 row += 1
16388 }
16389 }
16390 if found {
16391 continue;
16392 }
16393 }
16394
16395 for row in (0..=range.start.row).rev() {
16396 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16397 if crease.range().end.row >= buffer_start_row {
16398 to_fold.push(crease);
16399 if row <= range.start.row {
16400 break;
16401 }
16402 }
16403 }
16404 }
16405 }
16406
16407 self.fold_creases(to_fold, true, window, cx);
16408 } else {
16409 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16410 let buffer_ids = self
16411 .selections
16412 .disjoint_anchor_ranges()
16413 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16414 .collect::<HashSet<_>>();
16415 for buffer_id in buffer_ids {
16416 self.fold_buffer(buffer_id, cx);
16417 }
16418 }
16419 }
16420
16421 fn fold_at_level(
16422 &mut self,
16423 fold_at: &FoldAtLevel,
16424 window: &mut Window,
16425 cx: &mut Context<Self>,
16426 ) {
16427 if !self.buffer.read(cx).is_singleton() {
16428 return;
16429 }
16430
16431 let fold_at_level = fold_at.0;
16432 let snapshot = self.buffer.read(cx).snapshot(cx);
16433 let mut to_fold = Vec::new();
16434 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16435
16436 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16437 while start_row < end_row {
16438 match self
16439 .snapshot(window, cx)
16440 .crease_for_buffer_row(MultiBufferRow(start_row))
16441 {
16442 Some(crease) => {
16443 let nested_start_row = crease.range().start.row + 1;
16444 let nested_end_row = crease.range().end.row;
16445
16446 if current_level < fold_at_level {
16447 stack.push((nested_start_row, nested_end_row, current_level + 1));
16448 } else if current_level == fold_at_level {
16449 to_fold.push(crease);
16450 }
16451
16452 start_row = nested_end_row + 1;
16453 }
16454 None => start_row += 1,
16455 }
16456 }
16457 }
16458
16459 self.fold_creases(to_fold, true, window, cx);
16460 }
16461
16462 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16463 if self.buffer.read(cx).is_singleton() {
16464 let mut fold_ranges = Vec::new();
16465 let snapshot = self.buffer.read(cx).snapshot(cx);
16466
16467 for row in 0..snapshot.max_row().0 {
16468 if let Some(foldable_range) = self
16469 .snapshot(window, cx)
16470 .crease_for_buffer_row(MultiBufferRow(row))
16471 {
16472 fold_ranges.push(foldable_range);
16473 }
16474 }
16475
16476 self.fold_creases(fold_ranges, true, window, cx);
16477 } else {
16478 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16479 editor
16480 .update_in(cx, |editor, _, cx| {
16481 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16482 editor.fold_buffer(buffer_id, cx);
16483 }
16484 })
16485 .ok();
16486 });
16487 }
16488 }
16489
16490 pub fn fold_function_bodies(
16491 &mut self,
16492 _: &actions::FoldFunctionBodies,
16493 window: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) {
16496 let snapshot = self.buffer.read(cx).snapshot(cx);
16497
16498 let ranges = snapshot
16499 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16500 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16501 .collect::<Vec<_>>();
16502
16503 let creases = ranges
16504 .into_iter()
16505 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16506 .collect();
16507
16508 self.fold_creases(creases, true, window, cx);
16509 }
16510
16511 pub fn fold_recursive(
16512 &mut self,
16513 _: &actions::FoldRecursive,
16514 window: &mut Window,
16515 cx: &mut Context<Self>,
16516 ) {
16517 let mut to_fold = Vec::new();
16518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16519 let selections = self.selections.all_adjusted(cx);
16520
16521 for selection in selections {
16522 let range = selection.range().sorted();
16523 let buffer_start_row = range.start.row;
16524
16525 if range.start.row != range.end.row {
16526 let mut found = false;
16527 for row in range.start.row..=range.end.row {
16528 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16529 found = true;
16530 to_fold.push(crease);
16531 }
16532 }
16533 if found {
16534 continue;
16535 }
16536 }
16537
16538 for row in (0..=range.start.row).rev() {
16539 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16540 if crease.range().end.row >= buffer_start_row {
16541 to_fold.push(crease);
16542 } else {
16543 break;
16544 }
16545 }
16546 }
16547 }
16548
16549 self.fold_creases(to_fold, true, window, cx);
16550 }
16551
16552 pub fn fold_at(
16553 &mut self,
16554 buffer_row: MultiBufferRow,
16555 window: &mut Window,
16556 cx: &mut Context<Self>,
16557 ) {
16558 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16559
16560 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16561 let autoscroll = self
16562 .selections
16563 .all::<Point>(cx)
16564 .iter()
16565 .any(|selection| crease.range().overlaps(&selection.range()));
16566
16567 self.fold_creases(vec![crease], autoscroll, window, cx);
16568 }
16569 }
16570
16571 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16572 if self.is_singleton(cx) {
16573 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16574 let buffer = &display_map.buffer_snapshot;
16575 let selections = self.selections.all::<Point>(cx);
16576 let ranges = selections
16577 .iter()
16578 .map(|s| {
16579 let range = s.display_range(&display_map).sorted();
16580 let mut start = range.start.to_point(&display_map);
16581 let mut end = range.end.to_point(&display_map);
16582 start.column = 0;
16583 end.column = buffer.line_len(MultiBufferRow(end.row));
16584 start..end
16585 })
16586 .collect::<Vec<_>>();
16587
16588 self.unfold_ranges(&ranges, true, true, cx);
16589 } else {
16590 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16591 let buffer_ids = self
16592 .selections
16593 .disjoint_anchor_ranges()
16594 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16595 .collect::<HashSet<_>>();
16596 for buffer_id in buffer_ids {
16597 self.unfold_buffer(buffer_id, cx);
16598 }
16599 }
16600 }
16601
16602 pub fn unfold_recursive(
16603 &mut self,
16604 _: &UnfoldRecursive,
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 let selections = self.selections.all::<Point>(cx);
16610 let ranges = selections
16611 .iter()
16612 .map(|s| {
16613 let mut range = s.display_range(&display_map).sorted();
16614 *range.start.column_mut() = 0;
16615 *range.end.column_mut() = display_map.line_len(range.end.row());
16616 let start = range.start.to_point(&display_map);
16617 let end = range.end.to_point(&display_map);
16618 start..end
16619 })
16620 .collect::<Vec<_>>();
16621
16622 self.unfold_ranges(&ranges, true, true, cx);
16623 }
16624
16625 pub fn unfold_at(
16626 &mut self,
16627 buffer_row: MultiBufferRow,
16628 _window: &mut Window,
16629 cx: &mut Context<Self>,
16630 ) {
16631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16632
16633 let intersection_range = Point::new(buffer_row.0, 0)
16634 ..Point::new(
16635 buffer_row.0,
16636 display_map.buffer_snapshot.line_len(buffer_row),
16637 );
16638
16639 let autoscroll = self
16640 .selections
16641 .all::<Point>(cx)
16642 .iter()
16643 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16644
16645 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16646 }
16647
16648 pub fn unfold_all(
16649 &mut self,
16650 _: &actions::UnfoldAll,
16651 _window: &mut Window,
16652 cx: &mut Context<Self>,
16653 ) {
16654 if self.buffer.read(cx).is_singleton() {
16655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16656 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16657 } else {
16658 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16659 editor
16660 .update(cx, |editor, cx| {
16661 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16662 editor.unfold_buffer(buffer_id, cx);
16663 }
16664 })
16665 .ok();
16666 });
16667 }
16668 }
16669
16670 pub fn fold_selected_ranges(
16671 &mut self,
16672 _: &FoldSelectedRanges,
16673 window: &mut Window,
16674 cx: &mut Context<Self>,
16675 ) {
16676 let selections = self.selections.all_adjusted(cx);
16677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16678 let ranges = selections
16679 .into_iter()
16680 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16681 .collect::<Vec<_>>();
16682 self.fold_creases(ranges, true, window, cx);
16683 }
16684
16685 pub fn fold_ranges<T: ToOffset + Clone>(
16686 &mut self,
16687 ranges: Vec<Range<T>>,
16688 auto_scroll: bool,
16689 window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) {
16692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16693 let ranges = ranges
16694 .into_iter()
16695 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16696 .collect::<Vec<_>>();
16697 self.fold_creases(ranges, auto_scroll, window, cx);
16698 }
16699
16700 pub fn fold_creases<T: ToOffset + Clone>(
16701 &mut self,
16702 creases: Vec<Crease<T>>,
16703 auto_scroll: bool,
16704 _window: &mut Window,
16705 cx: &mut Context<Self>,
16706 ) {
16707 if creases.is_empty() {
16708 return;
16709 }
16710
16711 let mut buffers_affected = HashSet::default();
16712 let multi_buffer = self.buffer().read(cx);
16713 for crease in &creases {
16714 if let Some((_, buffer, _)) =
16715 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16716 {
16717 buffers_affected.insert(buffer.read(cx).remote_id());
16718 };
16719 }
16720
16721 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16722
16723 if auto_scroll {
16724 self.request_autoscroll(Autoscroll::fit(), cx);
16725 }
16726
16727 cx.notify();
16728
16729 self.scrollbar_marker_state.dirty = true;
16730 self.folds_did_change(cx);
16731 }
16732
16733 /// Removes any folds whose ranges intersect any of the given ranges.
16734 pub fn unfold_ranges<T: ToOffset + Clone>(
16735 &mut self,
16736 ranges: &[Range<T>],
16737 inclusive: bool,
16738 auto_scroll: bool,
16739 cx: &mut Context<Self>,
16740 ) {
16741 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16742 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16743 });
16744 self.folds_did_change(cx);
16745 }
16746
16747 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16748 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16749 return;
16750 }
16751 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16752 self.display_map.update(cx, |display_map, cx| {
16753 display_map.fold_buffers([buffer_id], cx)
16754 });
16755 cx.emit(EditorEvent::BufferFoldToggled {
16756 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16757 folded: true,
16758 });
16759 cx.notify();
16760 }
16761
16762 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16763 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16764 return;
16765 }
16766 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16767 self.display_map.update(cx, |display_map, cx| {
16768 display_map.unfold_buffers([buffer_id], cx);
16769 });
16770 cx.emit(EditorEvent::BufferFoldToggled {
16771 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16772 folded: false,
16773 });
16774 cx.notify();
16775 }
16776
16777 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16778 self.display_map.read(cx).is_buffer_folded(buffer)
16779 }
16780
16781 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16782 self.display_map.read(cx).folded_buffers()
16783 }
16784
16785 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16786 self.display_map.update(cx, |display_map, cx| {
16787 display_map.disable_header_for_buffer(buffer_id, cx);
16788 });
16789 cx.notify();
16790 }
16791
16792 /// Removes any folds with the given ranges.
16793 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16794 &mut self,
16795 ranges: &[Range<T>],
16796 type_id: TypeId,
16797 auto_scroll: bool,
16798 cx: &mut Context<Self>,
16799 ) {
16800 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16801 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16802 });
16803 self.folds_did_change(cx);
16804 }
16805
16806 fn remove_folds_with<T: ToOffset + Clone>(
16807 &mut self,
16808 ranges: &[Range<T>],
16809 auto_scroll: bool,
16810 cx: &mut Context<Self>,
16811 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16812 ) {
16813 if ranges.is_empty() {
16814 return;
16815 }
16816
16817 let mut buffers_affected = HashSet::default();
16818 let multi_buffer = self.buffer().read(cx);
16819 for range in ranges {
16820 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16821 buffers_affected.insert(buffer.read(cx).remote_id());
16822 };
16823 }
16824
16825 self.display_map.update(cx, update);
16826
16827 if auto_scroll {
16828 self.request_autoscroll(Autoscroll::fit(), cx);
16829 }
16830
16831 cx.notify();
16832 self.scrollbar_marker_state.dirty = true;
16833 self.active_indent_guides_state.dirty = true;
16834 }
16835
16836 pub fn update_fold_widths(
16837 &mut self,
16838 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16839 cx: &mut Context<Self>,
16840 ) -> bool {
16841 self.display_map
16842 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16843 }
16844
16845 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16846 self.display_map.read(cx).fold_placeholder.clone()
16847 }
16848
16849 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16850 self.buffer.update(cx, |buffer, cx| {
16851 buffer.set_all_diff_hunks_expanded(cx);
16852 });
16853 }
16854
16855 pub fn expand_all_diff_hunks(
16856 &mut self,
16857 _: &ExpandAllDiffHunks,
16858 _window: &mut Window,
16859 cx: &mut Context<Self>,
16860 ) {
16861 self.buffer.update(cx, |buffer, cx| {
16862 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16863 });
16864 }
16865
16866 pub fn toggle_selected_diff_hunks(
16867 &mut self,
16868 _: &ToggleSelectedDiffHunks,
16869 _window: &mut Window,
16870 cx: &mut Context<Self>,
16871 ) {
16872 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16873 self.toggle_diff_hunks_in_ranges(ranges, cx);
16874 }
16875
16876 pub fn diff_hunks_in_ranges<'a>(
16877 &'a self,
16878 ranges: &'a [Range<Anchor>],
16879 buffer: &'a MultiBufferSnapshot,
16880 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16881 ranges.iter().flat_map(move |range| {
16882 let end_excerpt_id = range.end.excerpt_id;
16883 let range = range.to_point(buffer);
16884 let mut peek_end = range.end;
16885 if range.end.row < buffer.max_row().0 {
16886 peek_end = Point::new(range.end.row + 1, 0);
16887 }
16888 buffer
16889 .diff_hunks_in_range(range.start..peek_end)
16890 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16891 })
16892 }
16893
16894 pub fn has_stageable_diff_hunks_in_ranges(
16895 &self,
16896 ranges: &[Range<Anchor>],
16897 snapshot: &MultiBufferSnapshot,
16898 ) -> bool {
16899 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16900 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16901 }
16902
16903 pub fn toggle_staged_selected_diff_hunks(
16904 &mut self,
16905 _: &::git::ToggleStaged,
16906 _: &mut Window,
16907 cx: &mut Context<Self>,
16908 ) {
16909 let snapshot = self.buffer.read(cx).snapshot(cx);
16910 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16911 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16912 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16913 }
16914
16915 pub fn set_render_diff_hunk_controls(
16916 &mut self,
16917 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16918 cx: &mut Context<Self>,
16919 ) {
16920 self.render_diff_hunk_controls = render_diff_hunk_controls;
16921 cx.notify();
16922 }
16923
16924 pub fn stage_and_next(
16925 &mut self,
16926 _: &::git::StageAndNext,
16927 window: &mut Window,
16928 cx: &mut Context<Self>,
16929 ) {
16930 self.do_stage_or_unstage_and_next(true, window, cx);
16931 }
16932
16933 pub fn unstage_and_next(
16934 &mut self,
16935 _: &::git::UnstageAndNext,
16936 window: &mut Window,
16937 cx: &mut Context<Self>,
16938 ) {
16939 self.do_stage_or_unstage_and_next(false, window, cx);
16940 }
16941
16942 pub fn stage_or_unstage_diff_hunks(
16943 &mut self,
16944 stage: bool,
16945 ranges: Vec<Range<Anchor>>,
16946 cx: &mut Context<Self>,
16947 ) {
16948 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16949 cx.spawn(async move |this, cx| {
16950 task.await?;
16951 this.update(cx, |this, cx| {
16952 let snapshot = this.buffer.read(cx).snapshot(cx);
16953 let chunk_by = this
16954 .diff_hunks_in_ranges(&ranges, &snapshot)
16955 .chunk_by(|hunk| hunk.buffer_id);
16956 for (buffer_id, hunks) in &chunk_by {
16957 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16958 }
16959 })
16960 })
16961 .detach_and_log_err(cx);
16962 }
16963
16964 fn save_buffers_for_ranges_if_needed(
16965 &mut self,
16966 ranges: &[Range<Anchor>],
16967 cx: &mut Context<Editor>,
16968 ) -> Task<Result<()>> {
16969 let multibuffer = self.buffer.read(cx);
16970 let snapshot = multibuffer.read(cx);
16971 let buffer_ids: HashSet<_> = ranges
16972 .iter()
16973 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16974 .collect();
16975 drop(snapshot);
16976
16977 let mut buffers = HashSet::default();
16978 for buffer_id in buffer_ids {
16979 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16980 let buffer = buffer_entity.read(cx);
16981 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16982 {
16983 buffers.insert(buffer_entity);
16984 }
16985 }
16986 }
16987
16988 if let Some(project) = &self.project {
16989 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16990 } else {
16991 Task::ready(Ok(()))
16992 }
16993 }
16994
16995 fn do_stage_or_unstage_and_next(
16996 &mut self,
16997 stage: bool,
16998 window: &mut Window,
16999 cx: &mut Context<Self>,
17000 ) {
17001 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17002
17003 if ranges.iter().any(|range| range.start != range.end) {
17004 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17005 return;
17006 }
17007
17008 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17009 let snapshot = self.snapshot(window, cx);
17010 let position = self.selections.newest::<Point>(cx).head();
17011 let mut row = snapshot
17012 .buffer_snapshot
17013 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17014 .find(|hunk| hunk.row_range.start.0 > position.row)
17015 .map(|hunk| hunk.row_range.start);
17016
17017 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17018 // Outside of the project diff editor, wrap around to the beginning.
17019 if !all_diff_hunks_expanded {
17020 row = row.or_else(|| {
17021 snapshot
17022 .buffer_snapshot
17023 .diff_hunks_in_range(Point::zero()..position)
17024 .find(|hunk| hunk.row_range.end.0 < position.row)
17025 .map(|hunk| hunk.row_range.start)
17026 });
17027 }
17028
17029 if let Some(row) = row {
17030 let destination = Point::new(row.0, 0);
17031 let autoscroll = Autoscroll::center();
17032
17033 self.unfold_ranges(&[destination..destination], false, false, cx);
17034 self.change_selections(Some(autoscroll), window, cx, |s| {
17035 s.select_ranges([destination..destination]);
17036 });
17037 }
17038 }
17039
17040 fn do_stage_or_unstage(
17041 &self,
17042 stage: bool,
17043 buffer_id: BufferId,
17044 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17045 cx: &mut App,
17046 ) -> Option<()> {
17047 let project = self.project.as_ref()?;
17048 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17049 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17050 let buffer_snapshot = buffer.read(cx).snapshot();
17051 let file_exists = buffer_snapshot
17052 .file()
17053 .is_some_and(|file| file.disk_state().exists());
17054 diff.update(cx, |diff, cx| {
17055 diff.stage_or_unstage_hunks(
17056 stage,
17057 &hunks
17058 .map(|hunk| buffer_diff::DiffHunk {
17059 buffer_range: hunk.buffer_range,
17060 diff_base_byte_range: hunk.diff_base_byte_range,
17061 secondary_status: hunk.secondary_status,
17062 range: Point::zero()..Point::zero(), // unused
17063 })
17064 .collect::<Vec<_>>(),
17065 &buffer_snapshot,
17066 file_exists,
17067 cx,
17068 )
17069 });
17070 None
17071 }
17072
17073 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17074 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17075 self.buffer
17076 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17077 }
17078
17079 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17080 self.buffer.update(cx, |buffer, cx| {
17081 let ranges = vec![Anchor::min()..Anchor::max()];
17082 if !buffer.all_diff_hunks_expanded()
17083 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17084 {
17085 buffer.collapse_diff_hunks(ranges, cx);
17086 true
17087 } else {
17088 false
17089 }
17090 })
17091 }
17092
17093 fn toggle_diff_hunks_in_ranges(
17094 &mut self,
17095 ranges: Vec<Range<Anchor>>,
17096 cx: &mut Context<Editor>,
17097 ) {
17098 self.buffer.update(cx, |buffer, cx| {
17099 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17100 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17101 })
17102 }
17103
17104 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17105 self.buffer.update(cx, |buffer, cx| {
17106 let snapshot = buffer.snapshot(cx);
17107 let excerpt_id = range.end.excerpt_id;
17108 let point_range = range.to_point(&snapshot);
17109 let expand = !buffer.single_hunk_is_expanded(range, cx);
17110 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17111 })
17112 }
17113
17114 pub(crate) fn apply_all_diff_hunks(
17115 &mut self,
17116 _: &ApplyAllDiffHunks,
17117 window: &mut Window,
17118 cx: &mut Context<Self>,
17119 ) {
17120 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17121
17122 let buffers = self.buffer.read(cx).all_buffers();
17123 for branch_buffer in buffers {
17124 branch_buffer.update(cx, |branch_buffer, cx| {
17125 branch_buffer.merge_into_base(Vec::new(), cx);
17126 });
17127 }
17128
17129 if let Some(project) = self.project.clone() {
17130 self.save(
17131 SaveOptions {
17132 format: true,
17133 autosave: false,
17134 },
17135 project,
17136 window,
17137 cx,
17138 )
17139 .detach_and_log_err(cx);
17140 }
17141 }
17142
17143 pub(crate) fn apply_selected_diff_hunks(
17144 &mut self,
17145 _: &ApplyDiffHunk,
17146 window: &mut Window,
17147 cx: &mut Context<Self>,
17148 ) {
17149 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17150 let snapshot = self.snapshot(window, cx);
17151 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17152 let mut ranges_by_buffer = HashMap::default();
17153 self.transact(window, cx, |editor, _window, cx| {
17154 for hunk in hunks {
17155 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17156 ranges_by_buffer
17157 .entry(buffer.clone())
17158 .or_insert_with(Vec::new)
17159 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17160 }
17161 }
17162
17163 for (buffer, ranges) in ranges_by_buffer {
17164 buffer.update(cx, |buffer, cx| {
17165 buffer.merge_into_base(ranges, cx);
17166 });
17167 }
17168 });
17169
17170 if let Some(project) = self.project.clone() {
17171 self.save(
17172 SaveOptions {
17173 format: true,
17174 autosave: false,
17175 },
17176 project,
17177 window,
17178 cx,
17179 )
17180 .detach_and_log_err(cx);
17181 }
17182 }
17183
17184 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17185 if hovered != self.gutter_hovered {
17186 self.gutter_hovered = hovered;
17187 cx.notify();
17188 }
17189 }
17190
17191 pub fn insert_blocks(
17192 &mut self,
17193 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17194 autoscroll: Option<Autoscroll>,
17195 cx: &mut Context<Self>,
17196 ) -> Vec<CustomBlockId> {
17197 let blocks = self
17198 .display_map
17199 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17200 if let Some(autoscroll) = autoscroll {
17201 self.request_autoscroll(autoscroll, cx);
17202 }
17203 cx.notify();
17204 blocks
17205 }
17206
17207 pub fn resize_blocks(
17208 &mut self,
17209 heights: HashMap<CustomBlockId, u32>,
17210 autoscroll: Option<Autoscroll>,
17211 cx: &mut Context<Self>,
17212 ) {
17213 self.display_map
17214 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17215 if let Some(autoscroll) = autoscroll {
17216 self.request_autoscroll(autoscroll, cx);
17217 }
17218 cx.notify();
17219 }
17220
17221 pub fn replace_blocks(
17222 &mut self,
17223 renderers: HashMap<CustomBlockId, RenderBlock>,
17224 autoscroll: Option<Autoscroll>,
17225 cx: &mut Context<Self>,
17226 ) {
17227 self.display_map
17228 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17229 if let Some(autoscroll) = autoscroll {
17230 self.request_autoscroll(autoscroll, cx);
17231 }
17232 cx.notify();
17233 }
17234
17235 pub fn remove_blocks(
17236 &mut self,
17237 block_ids: HashSet<CustomBlockId>,
17238 autoscroll: Option<Autoscroll>,
17239 cx: &mut Context<Self>,
17240 ) {
17241 self.display_map.update(cx, |display_map, cx| {
17242 display_map.remove_blocks(block_ids, cx)
17243 });
17244 if let Some(autoscroll) = autoscroll {
17245 self.request_autoscroll(autoscroll, cx);
17246 }
17247 cx.notify();
17248 }
17249
17250 pub fn row_for_block(
17251 &self,
17252 block_id: CustomBlockId,
17253 cx: &mut Context<Self>,
17254 ) -> Option<DisplayRow> {
17255 self.display_map
17256 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17257 }
17258
17259 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17260 self.focused_block = Some(focused_block);
17261 }
17262
17263 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17264 self.focused_block.take()
17265 }
17266
17267 pub fn insert_creases(
17268 &mut self,
17269 creases: impl IntoIterator<Item = Crease<Anchor>>,
17270 cx: &mut Context<Self>,
17271 ) -> Vec<CreaseId> {
17272 self.display_map
17273 .update(cx, |map, cx| map.insert_creases(creases, cx))
17274 }
17275
17276 pub fn remove_creases(
17277 &mut self,
17278 ids: impl IntoIterator<Item = CreaseId>,
17279 cx: &mut Context<Self>,
17280 ) -> Vec<(CreaseId, Range<Anchor>)> {
17281 self.display_map
17282 .update(cx, |map, cx| map.remove_creases(ids, cx))
17283 }
17284
17285 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17286 self.display_map
17287 .update(cx, |map, cx| map.snapshot(cx))
17288 .longest_row()
17289 }
17290
17291 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17292 self.display_map
17293 .update(cx, |map, cx| map.snapshot(cx))
17294 .max_point()
17295 }
17296
17297 pub fn text(&self, cx: &App) -> String {
17298 self.buffer.read(cx).read(cx).text()
17299 }
17300
17301 pub fn is_empty(&self, cx: &App) -> bool {
17302 self.buffer.read(cx).read(cx).is_empty()
17303 }
17304
17305 pub fn text_option(&self, cx: &App) -> Option<String> {
17306 let text = self.text(cx);
17307 let text = text.trim();
17308
17309 if text.is_empty() {
17310 return None;
17311 }
17312
17313 Some(text.to_string())
17314 }
17315
17316 pub fn set_text(
17317 &mut self,
17318 text: impl Into<Arc<str>>,
17319 window: &mut Window,
17320 cx: &mut Context<Self>,
17321 ) {
17322 self.transact(window, cx, |this, _, cx| {
17323 this.buffer
17324 .read(cx)
17325 .as_singleton()
17326 .expect("you can only call set_text on editors for singleton buffers")
17327 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17328 });
17329 }
17330
17331 pub fn display_text(&self, cx: &mut App) -> String {
17332 self.display_map
17333 .update(cx, |map, cx| map.snapshot(cx))
17334 .text()
17335 }
17336
17337 fn create_minimap(
17338 &self,
17339 minimap_settings: MinimapSettings,
17340 window: &mut Window,
17341 cx: &mut Context<Self>,
17342 ) -> Option<Entity<Self>> {
17343 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17344 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17345 }
17346
17347 fn initialize_new_minimap(
17348 &self,
17349 minimap_settings: MinimapSettings,
17350 window: &mut Window,
17351 cx: &mut Context<Self>,
17352 ) -> Entity<Self> {
17353 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17354
17355 let mut minimap = Editor::new_internal(
17356 EditorMode::Minimap {
17357 parent: cx.weak_entity(),
17358 },
17359 self.buffer.clone(),
17360 self.project.clone(),
17361 Some(self.display_map.clone()),
17362 window,
17363 cx,
17364 );
17365 minimap.scroll_manager.clone_state(&self.scroll_manager);
17366 minimap.set_text_style_refinement(TextStyleRefinement {
17367 font_size: Some(MINIMAP_FONT_SIZE),
17368 font_weight: Some(MINIMAP_FONT_WEIGHT),
17369 ..Default::default()
17370 });
17371 minimap.update_minimap_configuration(minimap_settings, cx);
17372 cx.new(|_| minimap)
17373 }
17374
17375 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17376 let current_line_highlight = minimap_settings
17377 .current_line_highlight
17378 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17379 self.set_current_line_highlight(Some(current_line_highlight));
17380 }
17381
17382 pub fn minimap(&self) -> Option<&Entity<Self>> {
17383 self.minimap
17384 .as_ref()
17385 .filter(|_| self.minimap_visibility.visible())
17386 }
17387
17388 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17389 let mut wrap_guides = smallvec![];
17390
17391 if self.show_wrap_guides == Some(false) {
17392 return wrap_guides;
17393 }
17394
17395 let settings = self.buffer.read(cx).language_settings(cx);
17396 if settings.show_wrap_guides {
17397 match self.soft_wrap_mode(cx) {
17398 SoftWrap::Column(soft_wrap) => {
17399 wrap_guides.push((soft_wrap as usize, true));
17400 }
17401 SoftWrap::Bounded(soft_wrap) => {
17402 wrap_guides.push((soft_wrap as usize, true));
17403 }
17404 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17405 }
17406 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17407 }
17408
17409 wrap_guides
17410 }
17411
17412 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17413 let settings = self.buffer.read(cx).language_settings(cx);
17414 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17415 match mode {
17416 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17417 SoftWrap::None
17418 }
17419 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17420 language_settings::SoftWrap::PreferredLineLength => {
17421 SoftWrap::Column(settings.preferred_line_length)
17422 }
17423 language_settings::SoftWrap::Bounded => {
17424 SoftWrap::Bounded(settings.preferred_line_length)
17425 }
17426 }
17427 }
17428
17429 pub fn set_soft_wrap_mode(
17430 &mut self,
17431 mode: language_settings::SoftWrap,
17432
17433 cx: &mut Context<Self>,
17434 ) {
17435 self.soft_wrap_mode_override = Some(mode);
17436 cx.notify();
17437 }
17438
17439 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17440 self.hard_wrap = hard_wrap;
17441 cx.notify();
17442 }
17443
17444 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17445 self.text_style_refinement = Some(style);
17446 }
17447
17448 /// called by the Element so we know what style we were most recently rendered with.
17449 pub(crate) fn set_style(
17450 &mut self,
17451 style: EditorStyle,
17452 window: &mut Window,
17453 cx: &mut Context<Self>,
17454 ) {
17455 // We intentionally do not inform the display map about the minimap style
17456 // so that wrapping is not recalculated and stays consistent for the editor
17457 // and its linked minimap.
17458 if !self.mode.is_minimap() {
17459 let rem_size = window.rem_size();
17460 self.display_map.update(cx, |map, cx| {
17461 map.set_font(
17462 style.text.font(),
17463 style.text.font_size.to_pixels(rem_size),
17464 cx,
17465 )
17466 });
17467 }
17468 self.style = Some(style);
17469 }
17470
17471 pub fn style(&self) -> Option<&EditorStyle> {
17472 self.style.as_ref()
17473 }
17474
17475 // Called by the element. This method is not designed to be called outside of the editor
17476 // element's layout code because it does not notify when rewrapping is computed synchronously.
17477 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17478 self.display_map
17479 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17480 }
17481
17482 pub fn set_soft_wrap(&mut self) {
17483 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17484 }
17485
17486 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17487 if self.soft_wrap_mode_override.is_some() {
17488 self.soft_wrap_mode_override.take();
17489 } else {
17490 let soft_wrap = match self.soft_wrap_mode(cx) {
17491 SoftWrap::GitDiff => return,
17492 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17493 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17494 language_settings::SoftWrap::None
17495 }
17496 };
17497 self.soft_wrap_mode_override = Some(soft_wrap);
17498 }
17499 cx.notify();
17500 }
17501
17502 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17503 let Some(workspace) = self.workspace() else {
17504 return;
17505 };
17506 let fs = workspace.read(cx).app_state().fs.clone();
17507 let current_show = TabBarSettings::get_global(cx).show;
17508 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17509 setting.show = Some(!current_show);
17510 });
17511 }
17512
17513 pub fn toggle_indent_guides(
17514 &mut self,
17515 _: &ToggleIndentGuides,
17516 _: &mut Window,
17517 cx: &mut Context<Self>,
17518 ) {
17519 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17520 self.buffer
17521 .read(cx)
17522 .language_settings(cx)
17523 .indent_guides
17524 .enabled
17525 });
17526 self.show_indent_guides = Some(!currently_enabled);
17527 cx.notify();
17528 }
17529
17530 fn should_show_indent_guides(&self) -> Option<bool> {
17531 self.show_indent_guides
17532 }
17533
17534 pub fn toggle_line_numbers(
17535 &mut self,
17536 _: &ToggleLineNumbers,
17537 _: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) {
17540 let mut editor_settings = EditorSettings::get_global(cx).clone();
17541 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17542 EditorSettings::override_global(editor_settings, cx);
17543 }
17544
17545 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17546 if let Some(show_line_numbers) = self.show_line_numbers {
17547 return show_line_numbers;
17548 }
17549 EditorSettings::get_global(cx).gutter.line_numbers
17550 }
17551
17552 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17553 self.use_relative_line_numbers
17554 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17555 }
17556
17557 pub fn toggle_relative_line_numbers(
17558 &mut self,
17559 _: &ToggleRelativeLineNumbers,
17560 _: &mut Window,
17561 cx: &mut Context<Self>,
17562 ) {
17563 let is_relative = self.should_use_relative_line_numbers(cx);
17564 self.set_relative_line_number(Some(!is_relative), cx)
17565 }
17566
17567 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17568 self.use_relative_line_numbers = is_relative;
17569 cx.notify();
17570 }
17571
17572 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17573 self.show_gutter = show_gutter;
17574 cx.notify();
17575 }
17576
17577 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17578 self.show_scrollbars = ScrollbarAxes {
17579 horizontal: show,
17580 vertical: show,
17581 };
17582 cx.notify();
17583 }
17584
17585 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17586 self.show_scrollbars.vertical = show;
17587 cx.notify();
17588 }
17589
17590 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17591 self.show_scrollbars.horizontal = show;
17592 cx.notify();
17593 }
17594
17595 pub fn set_minimap_visibility(
17596 &mut self,
17597 minimap_visibility: MinimapVisibility,
17598 window: &mut Window,
17599 cx: &mut Context<Self>,
17600 ) {
17601 if self.minimap_visibility != minimap_visibility {
17602 if minimap_visibility.visible() && self.minimap.is_none() {
17603 let minimap_settings = EditorSettings::get_global(cx).minimap;
17604 self.minimap =
17605 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17606 }
17607 self.minimap_visibility = minimap_visibility;
17608 cx.notify();
17609 }
17610 }
17611
17612 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17613 self.set_show_scrollbars(false, cx);
17614 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17615 }
17616
17617 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17618 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17619 }
17620
17621 /// Normally the text in full mode and auto height editors is padded on the
17622 /// left side by roughly half a character width for improved hit testing.
17623 ///
17624 /// Use this method to disable this for cases where this is not wanted (e.g.
17625 /// if you want to align the editor text with some other text above or below)
17626 /// or if you want to add this padding to single-line editors.
17627 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17628 self.offset_content = offset_content;
17629 cx.notify();
17630 }
17631
17632 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17633 self.show_line_numbers = Some(show_line_numbers);
17634 cx.notify();
17635 }
17636
17637 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17638 self.disable_expand_excerpt_buttons = true;
17639 cx.notify();
17640 }
17641
17642 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17643 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17644 cx.notify();
17645 }
17646
17647 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17648 self.show_code_actions = Some(show_code_actions);
17649 cx.notify();
17650 }
17651
17652 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17653 self.show_runnables = Some(show_runnables);
17654 cx.notify();
17655 }
17656
17657 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17658 self.show_breakpoints = Some(show_breakpoints);
17659 cx.notify();
17660 }
17661
17662 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17663 if self.display_map.read(cx).masked != masked {
17664 self.display_map.update(cx, |map, _| map.masked = masked);
17665 }
17666 cx.notify()
17667 }
17668
17669 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17670 self.show_wrap_guides = Some(show_wrap_guides);
17671 cx.notify();
17672 }
17673
17674 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17675 self.show_indent_guides = Some(show_indent_guides);
17676 cx.notify();
17677 }
17678
17679 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17680 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17681 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17682 if let Some(dir) = file.abs_path(cx).parent() {
17683 return Some(dir.to_owned());
17684 }
17685 }
17686
17687 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17688 return Some(project_path.path.to_path_buf());
17689 }
17690 }
17691
17692 None
17693 }
17694
17695 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17696 self.active_excerpt(cx)?
17697 .1
17698 .read(cx)
17699 .file()
17700 .and_then(|f| f.as_local())
17701 }
17702
17703 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17704 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17705 let buffer = buffer.read(cx);
17706 if let Some(project_path) = buffer.project_path(cx) {
17707 let project = self.project.as_ref()?.read(cx);
17708 project.absolute_path(&project_path, cx)
17709 } else {
17710 buffer
17711 .file()
17712 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17713 }
17714 })
17715 }
17716
17717 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17718 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17719 let project_path = buffer.read(cx).project_path(cx)?;
17720 let project = self.project.as_ref()?.read(cx);
17721 let entry = project.entry_for_path(&project_path, cx)?;
17722 let path = entry.path.to_path_buf();
17723 Some(path)
17724 })
17725 }
17726
17727 pub fn reveal_in_finder(
17728 &mut self,
17729 _: &RevealInFileManager,
17730 _window: &mut Window,
17731 cx: &mut Context<Self>,
17732 ) {
17733 if let Some(target) = self.target_file(cx) {
17734 cx.reveal_path(&target.abs_path(cx));
17735 }
17736 }
17737
17738 pub fn copy_path(
17739 &mut self,
17740 _: &zed_actions::workspace::CopyPath,
17741 _window: &mut Window,
17742 cx: &mut Context<Self>,
17743 ) {
17744 if let Some(path) = self.target_file_abs_path(cx) {
17745 if let Some(path) = path.to_str() {
17746 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17747 }
17748 }
17749 }
17750
17751 pub fn copy_relative_path(
17752 &mut self,
17753 _: &zed_actions::workspace::CopyRelativePath,
17754 _window: &mut Window,
17755 cx: &mut Context<Self>,
17756 ) {
17757 if let Some(path) = self.target_file_path(cx) {
17758 if let Some(path) = path.to_str() {
17759 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17760 }
17761 }
17762 }
17763
17764 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17765 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17766 buffer.read(cx).project_path(cx)
17767 } else {
17768 None
17769 }
17770 }
17771
17772 // Returns true if the editor handled a go-to-line request
17773 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17774 maybe!({
17775 let breakpoint_store = self.breakpoint_store.as_ref()?;
17776
17777 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17778 else {
17779 self.clear_row_highlights::<ActiveDebugLine>();
17780 return None;
17781 };
17782
17783 let position = active_stack_frame.position;
17784 let buffer_id = position.buffer_id?;
17785 let snapshot = self
17786 .project
17787 .as_ref()?
17788 .read(cx)
17789 .buffer_for_id(buffer_id, cx)?
17790 .read(cx)
17791 .snapshot();
17792
17793 let mut handled = false;
17794 for (id, ExcerptRange { context, .. }) in
17795 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17796 {
17797 if context.start.cmp(&position, &snapshot).is_ge()
17798 || context.end.cmp(&position, &snapshot).is_lt()
17799 {
17800 continue;
17801 }
17802 let snapshot = self.buffer.read(cx).snapshot(cx);
17803 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17804
17805 handled = true;
17806 self.clear_row_highlights::<ActiveDebugLine>();
17807
17808 self.go_to_line::<ActiveDebugLine>(
17809 multibuffer_anchor,
17810 Some(cx.theme().colors().editor_debugger_active_line_background),
17811 window,
17812 cx,
17813 );
17814
17815 cx.notify();
17816 }
17817
17818 handled.then_some(())
17819 })
17820 .is_some()
17821 }
17822
17823 pub fn copy_file_name_without_extension(
17824 &mut self,
17825 _: &CopyFileNameWithoutExtension,
17826 _: &mut Window,
17827 cx: &mut Context<Self>,
17828 ) {
17829 if let Some(file) = self.target_file(cx) {
17830 if let Some(file_stem) = file.path().file_stem() {
17831 if let Some(name) = file_stem.to_str() {
17832 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17833 }
17834 }
17835 }
17836 }
17837
17838 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17839 if let Some(file) = self.target_file(cx) {
17840 if let Some(file_name) = file.path().file_name() {
17841 if let Some(name) = file_name.to_str() {
17842 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17843 }
17844 }
17845 }
17846 }
17847
17848 pub fn toggle_git_blame(
17849 &mut self,
17850 _: &::git::Blame,
17851 window: &mut Window,
17852 cx: &mut Context<Self>,
17853 ) {
17854 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17855
17856 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17857 self.start_git_blame(true, window, cx);
17858 }
17859
17860 cx.notify();
17861 }
17862
17863 pub fn toggle_git_blame_inline(
17864 &mut self,
17865 _: &ToggleGitBlameInline,
17866 window: &mut Window,
17867 cx: &mut Context<Self>,
17868 ) {
17869 self.toggle_git_blame_inline_internal(true, window, cx);
17870 cx.notify();
17871 }
17872
17873 pub fn open_git_blame_commit(
17874 &mut self,
17875 _: &OpenGitBlameCommit,
17876 window: &mut Window,
17877 cx: &mut Context<Self>,
17878 ) {
17879 self.open_git_blame_commit_internal(window, cx);
17880 }
17881
17882 fn open_git_blame_commit_internal(
17883 &mut self,
17884 window: &mut Window,
17885 cx: &mut Context<Self>,
17886 ) -> Option<()> {
17887 let blame = self.blame.as_ref()?;
17888 let snapshot = self.snapshot(window, cx);
17889 let cursor = self.selections.newest::<Point>(cx).head();
17890 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17891 let blame_entry = blame
17892 .update(cx, |blame, cx| {
17893 blame
17894 .blame_for_rows(
17895 &[RowInfo {
17896 buffer_id: Some(buffer.remote_id()),
17897 buffer_row: Some(point.row),
17898 ..Default::default()
17899 }],
17900 cx,
17901 )
17902 .next()
17903 })
17904 .flatten()?;
17905 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17906 let repo = blame.read(cx).repository(cx)?;
17907 let workspace = self.workspace()?.downgrade();
17908 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17909 None
17910 }
17911
17912 pub fn git_blame_inline_enabled(&self) -> bool {
17913 self.git_blame_inline_enabled
17914 }
17915
17916 pub fn toggle_selection_menu(
17917 &mut self,
17918 _: &ToggleSelectionMenu,
17919 _: &mut Window,
17920 cx: &mut Context<Self>,
17921 ) {
17922 self.show_selection_menu = self
17923 .show_selection_menu
17924 .map(|show_selections_menu| !show_selections_menu)
17925 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17926
17927 cx.notify();
17928 }
17929
17930 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17931 self.show_selection_menu
17932 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17933 }
17934
17935 fn start_git_blame(
17936 &mut self,
17937 user_triggered: bool,
17938 window: &mut Window,
17939 cx: &mut Context<Self>,
17940 ) {
17941 if let Some(project) = self.project.as_ref() {
17942 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17943 return;
17944 };
17945
17946 if buffer.read(cx).file().is_none() {
17947 return;
17948 }
17949
17950 let focused = self.focus_handle(cx).contains_focused(window, cx);
17951
17952 let project = project.clone();
17953 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17954 self.blame_subscription =
17955 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17956 self.blame = Some(blame);
17957 }
17958 }
17959
17960 fn toggle_git_blame_inline_internal(
17961 &mut self,
17962 user_triggered: bool,
17963 window: &mut Window,
17964 cx: &mut Context<Self>,
17965 ) {
17966 if self.git_blame_inline_enabled {
17967 self.git_blame_inline_enabled = false;
17968 self.show_git_blame_inline = false;
17969 self.show_git_blame_inline_delay_task.take();
17970 } else {
17971 self.git_blame_inline_enabled = true;
17972 self.start_git_blame_inline(user_triggered, window, cx);
17973 }
17974
17975 cx.notify();
17976 }
17977
17978 fn start_git_blame_inline(
17979 &mut self,
17980 user_triggered: bool,
17981 window: &mut Window,
17982 cx: &mut Context<Self>,
17983 ) {
17984 self.start_git_blame(user_triggered, window, cx);
17985
17986 if ProjectSettings::get_global(cx)
17987 .git
17988 .inline_blame_delay()
17989 .is_some()
17990 {
17991 self.start_inline_blame_timer(window, cx);
17992 } else {
17993 self.show_git_blame_inline = true
17994 }
17995 }
17996
17997 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17998 self.blame.as_ref()
17999 }
18000
18001 pub fn show_git_blame_gutter(&self) -> bool {
18002 self.show_git_blame_gutter
18003 }
18004
18005 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18006 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18007 }
18008
18009 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18010 self.show_git_blame_inline
18011 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18012 && !self.newest_selection_head_on_empty_line(cx)
18013 && self.has_blame_entries(cx)
18014 }
18015
18016 fn has_blame_entries(&self, cx: &App) -> bool {
18017 self.blame()
18018 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18019 }
18020
18021 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18022 let cursor_anchor = self.selections.newest_anchor().head();
18023
18024 let snapshot = self.buffer.read(cx).snapshot(cx);
18025 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18026
18027 snapshot.line_len(buffer_row) == 0
18028 }
18029
18030 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18031 let buffer_and_selection = maybe!({
18032 let selection = self.selections.newest::<Point>(cx);
18033 let selection_range = selection.range();
18034
18035 let multi_buffer = self.buffer().read(cx);
18036 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18037 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18038
18039 let (buffer, range, _) = if selection.reversed {
18040 buffer_ranges.first()
18041 } else {
18042 buffer_ranges.last()
18043 }?;
18044
18045 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18046 ..text::ToPoint::to_point(&range.end, &buffer).row;
18047 Some((
18048 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18049 selection,
18050 ))
18051 });
18052
18053 let Some((buffer, selection)) = buffer_and_selection else {
18054 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18055 };
18056
18057 let Some(project) = self.project.as_ref() else {
18058 return Task::ready(Err(anyhow!("editor does not have project")));
18059 };
18060
18061 project.update(cx, |project, cx| {
18062 project.get_permalink_to_line(&buffer, selection, cx)
18063 })
18064 }
18065
18066 pub fn copy_permalink_to_line(
18067 &mut self,
18068 _: &CopyPermalinkToLine,
18069 window: &mut Window,
18070 cx: &mut Context<Self>,
18071 ) {
18072 let permalink_task = self.get_permalink_to_line(cx);
18073 let workspace = self.workspace();
18074
18075 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18076 Ok(permalink) => {
18077 cx.update(|_, cx| {
18078 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18079 })
18080 .ok();
18081 }
18082 Err(err) => {
18083 let message = format!("Failed to copy permalink: {err}");
18084
18085 anyhow::Result::<()>::Err(err).log_err();
18086
18087 if let Some(workspace) = workspace {
18088 workspace
18089 .update_in(cx, |workspace, _, cx| {
18090 struct CopyPermalinkToLine;
18091
18092 workspace.show_toast(
18093 Toast::new(
18094 NotificationId::unique::<CopyPermalinkToLine>(),
18095 message,
18096 ),
18097 cx,
18098 )
18099 })
18100 .ok();
18101 }
18102 }
18103 })
18104 .detach();
18105 }
18106
18107 pub fn copy_file_location(
18108 &mut self,
18109 _: &CopyFileLocation,
18110 _: &mut Window,
18111 cx: &mut Context<Self>,
18112 ) {
18113 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18114 if let Some(file) = self.target_file(cx) {
18115 if let Some(path) = file.path().to_str() {
18116 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18117 }
18118 }
18119 }
18120
18121 pub fn open_permalink_to_line(
18122 &mut self,
18123 _: &OpenPermalinkToLine,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) {
18127 let permalink_task = self.get_permalink_to_line(cx);
18128 let workspace = self.workspace();
18129
18130 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18131 Ok(permalink) => {
18132 cx.update(|_, cx| {
18133 cx.open_url(permalink.as_ref());
18134 })
18135 .ok();
18136 }
18137 Err(err) => {
18138 let message = format!("Failed to open permalink: {err}");
18139
18140 anyhow::Result::<()>::Err(err).log_err();
18141
18142 if let Some(workspace) = workspace {
18143 workspace
18144 .update(cx, |workspace, cx| {
18145 struct OpenPermalinkToLine;
18146
18147 workspace.show_toast(
18148 Toast::new(
18149 NotificationId::unique::<OpenPermalinkToLine>(),
18150 message,
18151 ),
18152 cx,
18153 )
18154 })
18155 .ok();
18156 }
18157 }
18158 })
18159 .detach();
18160 }
18161
18162 pub fn insert_uuid_v4(
18163 &mut self,
18164 _: &InsertUuidV4,
18165 window: &mut Window,
18166 cx: &mut Context<Self>,
18167 ) {
18168 self.insert_uuid(UuidVersion::V4, window, cx);
18169 }
18170
18171 pub fn insert_uuid_v7(
18172 &mut self,
18173 _: &InsertUuidV7,
18174 window: &mut Window,
18175 cx: &mut Context<Self>,
18176 ) {
18177 self.insert_uuid(UuidVersion::V7, window, cx);
18178 }
18179
18180 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18181 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18182 self.transact(window, cx, |this, window, cx| {
18183 let edits = this
18184 .selections
18185 .all::<Point>(cx)
18186 .into_iter()
18187 .map(|selection| {
18188 let uuid = match version {
18189 UuidVersion::V4 => uuid::Uuid::new_v4(),
18190 UuidVersion::V7 => uuid::Uuid::now_v7(),
18191 };
18192
18193 (selection.range(), uuid.to_string())
18194 });
18195 this.edit(edits, cx);
18196 this.refresh_inline_completion(true, false, window, cx);
18197 });
18198 }
18199
18200 pub fn open_selections_in_multibuffer(
18201 &mut self,
18202 _: &OpenSelectionsInMultibuffer,
18203 window: &mut Window,
18204 cx: &mut Context<Self>,
18205 ) {
18206 let multibuffer = self.buffer.read(cx);
18207
18208 let Some(buffer) = multibuffer.as_singleton() else {
18209 return;
18210 };
18211
18212 let Some(workspace) = self.workspace() else {
18213 return;
18214 };
18215
18216 let locations = self
18217 .selections
18218 .disjoint_anchors()
18219 .iter()
18220 .map(|range| Location {
18221 buffer: buffer.clone(),
18222 range: range.start.text_anchor..range.end.text_anchor,
18223 })
18224 .collect::<Vec<_>>();
18225
18226 let title = multibuffer.title(cx).to_string();
18227
18228 cx.spawn_in(window, async move |_, cx| {
18229 workspace.update_in(cx, |workspace, window, cx| {
18230 Self::open_locations_in_multibuffer(
18231 workspace,
18232 locations,
18233 format!("Selections for '{title}'"),
18234 false,
18235 MultibufferSelectionMode::All,
18236 window,
18237 cx,
18238 );
18239 })
18240 })
18241 .detach();
18242 }
18243
18244 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18245 /// last highlight added will be used.
18246 ///
18247 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18248 pub fn highlight_rows<T: 'static>(
18249 &mut self,
18250 range: Range<Anchor>,
18251 color: Hsla,
18252 options: RowHighlightOptions,
18253 cx: &mut Context<Self>,
18254 ) {
18255 let snapshot = self.buffer().read(cx).snapshot(cx);
18256 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18257 let ix = row_highlights.binary_search_by(|highlight| {
18258 Ordering::Equal
18259 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18260 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18261 });
18262
18263 if let Err(mut ix) = ix {
18264 let index = post_inc(&mut self.highlight_order);
18265
18266 // If this range intersects with the preceding highlight, then merge it with
18267 // the preceding highlight. Otherwise insert a new highlight.
18268 let mut merged = false;
18269 if ix > 0 {
18270 let prev_highlight = &mut row_highlights[ix - 1];
18271 if prev_highlight
18272 .range
18273 .end
18274 .cmp(&range.start, &snapshot)
18275 .is_ge()
18276 {
18277 ix -= 1;
18278 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18279 prev_highlight.range.end = range.end;
18280 }
18281 merged = true;
18282 prev_highlight.index = index;
18283 prev_highlight.color = color;
18284 prev_highlight.options = options;
18285 }
18286 }
18287
18288 if !merged {
18289 row_highlights.insert(
18290 ix,
18291 RowHighlight {
18292 range: range.clone(),
18293 index,
18294 color,
18295 options,
18296 type_id: TypeId::of::<T>(),
18297 },
18298 );
18299 }
18300
18301 // If any of the following highlights intersect with this one, merge them.
18302 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18303 let highlight = &row_highlights[ix];
18304 if next_highlight
18305 .range
18306 .start
18307 .cmp(&highlight.range.end, &snapshot)
18308 .is_le()
18309 {
18310 if next_highlight
18311 .range
18312 .end
18313 .cmp(&highlight.range.end, &snapshot)
18314 .is_gt()
18315 {
18316 row_highlights[ix].range.end = next_highlight.range.end;
18317 }
18318 row_highlights.remove(ix + 1);
18319 } else {
18320 break;
18321 }
18322 }
18323 }
18324 }
18325
18326 /// Remove any highlighted row ranges of the given type that intersect the
18327 /// given ranges.
18328 pub fn remove_highlighted_rows<T: 'static>(
18329 &mut self,
18330 ranges_to_remove: Vec<Range<Anchor>>,
18331 cx: &mut Context<Self>,
18332 ) {
18333 let snapshot = self.buffer().read(cx).snapshot(cx);
18334 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18335 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18336 row_highlights.retain(|highlight| {
18337 while let Some(range_to_remove) = ranges_to_remove.peek() {
18338 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18339 Ordering::Less | Ordering::Equal => {
18340 ranges_to_remove.next();
18341 }
18342 Ordering::Greater => {
18343 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18344 Ordering::Less | Ordering::Equal => {
18345 return false;
18346 }
18347 Ordering::Greater => break,
18348 }
18349 }
18350 }
18351 }
18352
18353 true
18354 })
18355 }
18356
18357 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18358 pub fn clear_row_highlights<T: 'static>(&mut self) {
18359 self.highlighted_rows.remove(&TypeId::of::<T>());
18360 }
18361
18362 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18363 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18364 self.highlighted_rows
18365 .get(&TypeId::of::<T>())
18366 .map_or(&[] as &[_], |vec| vec.as_slice())
18367 .iter()
18368 .map(|highlight| (highlight.range.clone(), highlight.color))
18369 }
18370
18371 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18372 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18373 /// Allows to ignore certain kinds of highlights.
18374 pub fn highlighted_display_rows(
18375 &self,
18376 window: &mut Window,
18377 cx: &mut App,
18378 ) -> BTreeMap<DisplayRow, LineHighlight> {
18379 let snapshot = self.snapshot(window, cx);
18380 let mut used_highlight_orders = HashMap::default();
18381 self.highlighted_rows
18382 .iter()
18383 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18384 .fold(
18385 BTreeMap::<DisplayRow, LineHighlight>::new(),
18386 |mut unique_rows, highlight| {
18387 let start = highlight.range.start.to_display_point(&snapshot);
18388 let end = highlight.range.end.to_display_point(&snapshot);
18389 let start_row = start.row().0;
18390 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18391 && end.column() == 0
18392 {
18393 end.row().0.saturating_sub(1)
18394 } else {
18395 end.row().0
18396 };
18397 for row in start_row..=end_row {
18398 let used_index =
18399 used_highlight_orders.entry(row).or_insert(highlight.index);
18400 if highlight.index >= *used_index {
18401 *used_index = highlight.index;
18402 unique_rows.insert(
18403 DisplayRow(row),
18404 LineHighlight {
18405 include_gutter: highlight.options.include_gutter,
18406 border: None,
18407 background: highlight.color.into(),
18408 type_id: Some(highlight.type_id),
18409 },
18410 );
18411 }
18412 }
18413 unique_rows
18414 },
18415 )
18416 }
18417
18418 pub fn highlighted_display_row_for_autoscroll(
18419 &self,
18420 snapshot: &DisplaySnapshot,
18421 ) -> Option<DisplayRow> {
18422 self.highlighted_rows
18423 .values()
18424 .flat_map(|highlighted_rows| highlighted_rows.iter())
18425 .filter_map(|highlight| {
18426 if highlight.options.autoscroll {
18427 Some(highlight.range.start.to_display_point(snapshot).row())
18428 } else {
18429 None
18430 }
18431 })
18432 .min()
18433 }
18434
18435 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18436 self.highlight_background::<SearchWithinRange>(
18437 ranges,
18438 |colors| colors.editor_document_highlight_read_background,
18439 cx,
18440 )
18441 }
18442
18443 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18444 self.breadcrumb_header = Some(new_header);
18445 }
18446
18447 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18448 self.clear_background_highlights::<SearchWithinRange>(cx);
18449 }
18450
18451 pub fn highlight_background<T: 'static>(
18452 &mut self,
18453 ranges: &[Range<Anchor>],
18454 color_fetcher: fn(&ThemeColors) -> Hsla,
18455 cx: &mut Context<Self>,
18456 ) {
18457 self.background_highlights
18458 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18459 self.scrollbar_marker_state.dirty = true;
18460 cx.notify();
18461 }
18462
18463 pub fn clear_background_highlights<T: 'static>(
18464 &mut self,
18465 cx: &mut Context<Self>,
18466 ) -> Option<BackgroundHighlight> {
18467 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18468 if !text_highlights.1.is_empty() {
18469 self.scrollbar_marker_state.dirty = true;
18470 cx.notify();
18471 }
18472 Some(text_highlights)
18473 }
18474
18475 pub fn highlight_gutter<T: 'static>(
18476 &mut self,
18477 ranges: impl Into<Vec<Range<Anchor>>>,
18478 color_fetcher: fn(&App) -> Hsla,
18479 cx: &mut Context<Self>,
18480 ) {
18481 self.gutter_highlights
18482 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18483 cx.notify();
18484 }
18485
18486 pub fn clear_gutter_highlights<T: 'static>(
18487 &mut self,
18488 cx: &mut Context<Self>,
18489 ) -> Option<GutterHighlight> {
18490 cx.notify();
18491 self.gutter_highlights.remove(&TypeId::of::<T>())
18492 }
18493
18494 pub fn insert_gutter_highlight<T: 'static>(
18495 &mut self,
18496 range: Range<Anchor>,
18497 color_fetcher: fn(&App) -> Hsla,
18498 cx: &mut Context<Self>,
18499 ) {
18500 let snapshot = self.buffer().read(cx).snapshot(cx);
18501 let mut highlights = self
18502 .gutter_highlights
18503 .remove(&TypeId::of::<T>())
18504 .map(|(_, highlights)| highlights)
18505 .unwrap_or_default();
18506 let ix = highlights.binary_search_by(|highlight| {
18507 Ordering::Equal
18508 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18509 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18510 });
18511 if let Err(ix) = ix {
18512 highlights.insert(ix, range);
18513 }
18514 self.gutter_highlights
18515 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18516 }
18517
18518 pub fn remove_gutter_highlights<T: 'static>(
18519 &mut self,
18520 ranges_to_remove: Vec<Range<Anchor>>,
18521 cx: &mut Context<Self>,
18522 ) {
18523 let snapshot = self.buffer().read(cx).snapshot(cx);
18524 let Some((color_fetcher, mut gutter_highlights)) =
18525 self.gutter_highlights.remove(&TypeId::of::<T>())
18526 else {
18527 return;
18528 };
18529 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18530 gutter_highlights.retain(|highlight| {
18531 while let Some(range_to_remove) = ranges_to_remove.peek() {
18532 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18533 Ordering::Less | Ordering::Equal => {
18534 ranges_to_remove.next();
18535 }
18536 Ordering::Greater => {
18537 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18538 Ordering::Less | Ordering::Equal => {
18539 return false;
18540 }
18541 Ordering::Greater => break,
18542 }
18543 }
18544 }
18545 }
18546
18547 true
18548 });
18549 self.gutter_highlights
18550 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18551 }
18552
18553 #[cfg(feature = "test-support")]
18554 pub fn all_text_background_highlights(
18555 &self,
18556 window: &mut Window,
18557 cx: &mut Context<Self>,
18558 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18559 let snapshot = self.snapshot(window, cx);
18560 let buffer = &snapshot.buffer_snapshot;
18561 let start = buffer.anchor_before(0);
18562 let end = buffer.anchor_after(buffer.len());
18563 let theme = cx.theme().colors();
18564 self.background_highlights_in_range(start..end, &snapshot, theme)
18565 }
18566
18567 #[cfg(feature = "test-support")]
18568 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18569 let snapshot = self.buffer().read(cx).snapshot(cx);
18570
18571 let highlights = self
18572 .background_highlights
18573 .get(&TypeId::of::<items::BufferSearchHighlights>());
18574
18575 if let Some((_color, ranges)) = highlights {
18576 ranges
18577 .iter()
18578 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18579 .collect_vec()
18580 } else {
18581 vec![]
18582 }
18583 }
18584
18585 fn document_highlights_for_position<'a>(
18586 &'a self,
18587 position: Anchor,
18588 buffer: &'a MultiBufferSnapshot,
18589 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18590 let read_highlights = self
18591 .background_highlights
18592 .get(&TypeId::of::<DocumentHighlightRead>())
18593 .map(|h| &h.1);
18594 let write_highlights = self
18595 .background_highlights
18596 .get(&TypeId::of::<DocumentHighlightWrite>())
18597 .map(|h| &h.1);
18598 let left_position = position.bias_left(buffer);
18599 let right_position = position.bias_right(buffer);
18600 read_highlights
18601 .into_iter()
18602 .chain(write_highlights)
18603 .flat_map(move |ranges| {
18604 let start_ix = match ranges.binary_search_by(|probe| {
18605 let cmp = probe.end.cmp(&left_position, buffer);
18606 if cmp.is_ge() {
18607 Ordering::Greater
18608 } else {
18609 Ordering::Less
18610 }
18611 }) {
18612 Ok(i) | Err(i) => i,
18613 };
18614
18615 ranges[start_ix..]
18616 .iter()
18617 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18618 })
18619 }
18620
18621 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18622 self.background_highlights
18623 .get(&TypeId::of::<T>())
18624 .map_or(false, |(_, highlights)| !highlights.is_empty())
18625 }
18626
18627 pub fn background_highlights_in_range(
18628 &self,
18629 search_range: Range<Anchor>,
18630 display_snapshot: &DisplaySnapshot,
18631 theme: &ThemeColors,
18632 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18633 let mut results = Vec::new();
18634 for (color_fetcher, ranges) in self.background_highlights.values() {
18635 let color = color_fetcher(theme);
18636 let start_ix = match ranges.binary_search_by(|probe| {
18637 let cmp = probe
18638 .end
18639 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18640 if cmp.is_gt() {
18641 Ordering::Greater
18642 } else {
18643 Ordering::Less
18644 }
18645 }) {
18646 Ok(i) | Err(i) => i,
18647 };
18648 for range in &ranges[start_ix..] {
18649 if range
18650 .start
18651 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18652 .is_ge()
18653 {
18654 break;
18655 }
18656
18657 let start = range.start.to_display_point(display_snapshot);
18658 let end = range.end.to_display_point(display_snapshot);
18659 results.push((start..end, color))
18660 }
18661 }
18662 results
18663 }
18664
18665 pub fn background_highlight_row_ranges<T: 'static>(
18666 &self,
18667 search_range: Range<Anchor>,
18668 display_snapshot: &DisplaySnapshot,
18669 count: usize,
18670 ) -> Vec<RangeInclusive<DisplayPoint>> {
18671 let mut results = Vec::new();
18672 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18673 return vec![];
18674 };
18675
18676 let start_ix = match ranges.binary_search_by(|probe| {
18677 let cmp = probe
18678 .end
18679 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18680 if cmp.is_gt() {
18681 Ordering::Greater
18682 } else {
18683 Ordering::Less
18684 }
18685 }) {
18686 Ok(i) | Err(i) => i,
18687 };
18688 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18689 if let (Some(start_display), Some(end_display)) = (start, end) {
18690 results.push(
18691 start_display.to_display_point(display_snapshot)
18692 ..=end_display.to_display_point(display_snapshot),
18693 );
18694 }
18695 };
18696 let mut start_row: Option<Point> = None;
18697 let mut end_row: Option<Point> = None;
18698 if ranges.len() > count {
18699 return Vec::new();
18700 }
18701 for range in &ranges[start_ix..] {
18702 if range
18703 .start
18704 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18705 .is_ge()
18706 {
18707 break;
18708 }
18709 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18710 if let Some(current_row) = &end_row {
18711 if end.row == current_row.row {
18712 continue;
18713 }
18714 }
18715 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18716 if start_row.is_none() {
18717 assert_eq!(end_row, None);
18718 start_row = Some(start);
18719 end_row = Some(end);
18720 continue;
18721 }
18722 if let Some(current_end) = end_row.as_mut() {
18723 if start.row > current_end.row + 1 {
18724 push_region(start_row, end_row);
18725 start_row = Some(start);
18726 end_row = Some(end);
18727 } else {
18728 // Merge two hunks.
18729 *current_end = end;
18730 }
18731 } else {
18732 unreachable!();
18733 }
18734 }
18735 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18736 push_region(start_row, end_row);
18737 results
18738 }
18739
18740 pub fn gutter_highlights_in_range(
18741 &self,
18742 search_range: Range<Anchor>,
18743 display_snapshot: &DisplaySnapshot,
18744 cx: &App,
18745 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18746 let mut results = Vec::new();
18747 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18748 let color = color_fetcher(cx);
18749 let start_ix = match ranges.binary_search_by(|probe| {
18750 let cmp = probe
18751 .end
18752 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18753 if cmp.is_gt() {
18754 Ordering::Greater
18755 } else {
18756 Ordering::Less
18757 }
18758 }) {
18759 Ok(i) | Err(i) => i,
18760 };
18761 for range in &ranges[start_ix..] {
18762 if range
18763 .start
18764 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18765 .is_ge()
18766 {
18767 break;
18768 }
18769
18770 let start = range.start.to_display_point(display_snapshot);
18771 let end = range.end.to_display_point(display_snapshot);
18772 results.push((start..end, color))
18773 }
18774 }
18775 results
18776 }
18777
18778 /// Get the text ranges corresponding to the redaction query
18779 pub fn redacted_ranges(
18780 &self,
18781 search_range: Range<Anchor>,
18782 display_snapshot: &DisplaySnapshot,
18783 cx: &App,
18784 ) -> Vec<Range<DisplayPoint>> {
18785 display_snapshot
18786 .buffer_snapshot
18787 .redacted_ranges(search_range, |file| {
18788 if let Some(file) = file {
18789 file.is_private()
18790 && EditorSettings::get(
18791 Some(SettingsLocation {
18792 worktree_id: file.worktree_id(cx),
18793 path: file.path().as_ref(),
18794 }),
18795 cx,
18796 )
18797 .redact_private_values
18798 } else {
18799 false
18800 }
18801 })
18802 .map(|range| {
18803 range.start.to_display_point(display_snapshot)
18804 ..range.end.to_display_point(display_snapshot)
18805 })
18806 .collect()
18807 }
18808
18809 pub fn highlight_text<T: 'static>(
18810 &mut self,
18811 ranges: Vec<Range<Anchor>>,
18812 style: HighlightStyle,
18813 cx: &mut Context<Self>,
18814 ) {
18815 self.display_map.update(cx, |map, _| {
18816 map.highlight_text(TypeId::of::<T>(), ranges, style)
18817 });
18818 cx.notify();
18819 }
18820
18821 pub(crate) fn highlight_inlays<T: 'static>(
18822 &mut self,
18823 highlights: Vec<InlayHighlight>,
18824 style: HighlightStyle,
18825 cx: &mut Context<Self>,
18826 ) {
18827 self.display_map.update(cx, |map, _| {
18828 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18829 });
18830 cx.notify();
18831 }
18832
18833 pub fn text_highlights<'a, T: 'static>(
18834 &'a self,
18835 cx: &'a App,
18836 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18837 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18838 }
18839
18840 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18841 let cleared = self
18842 .display_map
18843 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18844 if cleared {
18845 cx.notify();
18846 }
18847 }
18848
18849 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18850 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18851 && self.focus_handle.is_focused(window)
18852 }
18853
18854 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18855 self.show_cursor_when_unfocused = is_enabled;
18856 cx.notify();
18857 }
18858
18859 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18860 cx.notify();
18861 }
18862
18863 fn on_debug_session_event(
18864 &mut self,
18865 _session: Entity<Session>,
18866 event: &SessionEvent,
18867 cx: &mut Context<Self>,
18868 ) {
18869 match event {
18870 SessionEvent::InvalidateInlineValue => {
18871 self.refresh_inline_values(cx);
18872 }
18873 _ => {}
18874 }
18875 }
18876
18877 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18878 let Some(project) = self.project.clone() else {
18879 return;
18880 };
18881
18882 if !self.inline_value_cache.enabled {
18883 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18884 self.splice_inlays(&inlays, Vec::new(), cx);
18885 return;
18886 }
18887
18888 let current_execution_position = self
18889 .highlighted_rows
18890 .get(&TypeId::of::<ActiveDebugLine>())
18891 .and_then(|lines| lines.last().map(|line| line.range.start));
18892
18893 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18894 let inline_values = editor
18895 .update(cx, |editor, cx| {
18896 let Some(current_execution_position) = current_execution_position else {
18897 return Some(Task::ready(Ok(Vec::new())));
18898 };
18899
18900 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18901 let snapshot = buffer.snapshot(cx);
18902
18903 let excerpt = snapshot.excerpt_containing(
18904 current_execution_position..current_execution_position,
18905 )?;
18906
18907 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18908 })?;
18909
18910 let range =
18911 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18912
18913 project.inline_values(buffer, range, cx)
18914 })
18915 .ok()
18916 .flatten()?
18917 .await
18918 .context("refreshing debugger inlays")
18919 .log_err()?;
18920
18921 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18922
18923 for (buffer_id, inline_value) in inline_values
18924 .into_iter()
18925 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18926 {
18927 buffer_inline_values
18928 .entry(buffer_id)
18929 .or_default()
18930 .push(inline_value);
18931 }
18932
18933 editor
18934 .update(cx, |editor, cx| {
18935 let snapshot = editor.buffer.read(cx).snapshot(cx);
18936 let mut new_inlays = Vec::default();
18937
18938 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18939 let buffer_id = buffer_snapshot.remote_id();
18940 buffer_inline_values
18941 .get(&buffer_id)
18942 .into_iter()
18943 .flatten()
18944 .for_each(|hint| {
18945 let inlay = Inlay::debugger_hint(
18946 post_inc(&mut editor.next_inlay_id),
18947 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18948 hint.text(),
18949 );
18950
18951 new_inlays.push(inlay);
18952 });
18953 }
18954
18955 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18956 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18957
18958 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18959 })
18960 .ok()?;
18961 Some(())
18962 });
18963 }
18964
18965 fn on_buffer_event(
18966 &mut self,
18967 multibuffer: &Entity<MultiBuffer>,
18968 event: &multi_buffer::Event,
18969 window: &mut Window,
18970 cx: &mut Context<Self>,
18971 ) {
18972 match event {
18973 multi_buffer::Event::Edited {
18974 singleton_buffer_edited,
18975 edited_buffer,
18976 } => {
18977 self.scrollbar_marker_state.dirty = true;
18978 self.active_indent_guides_state.dirty = true;
18979 self.refresh_active_diagnostics(cx);
18980 self.refresh_code_actions(window, cx);
18981 self.refresh_selected_text_highlights(true, window, cx);
18982 refresh_matching_bracket_highlights(self, window, cx);
18983 if self.has_active_inline_completion() {
18984 self.update_visible_inline_completion(window, cx);
18985 }
18986 if let Some(project) = self.project.as_ref() {
18987 if let Some(edited_buffer) = edited_buffer {
18988 project.update(cx, |project, cx| {
18989 self.registered_buffers
18990 .entry(edited_buffer.read(cx).remote_id())
18991 .or_insert_with(|| {
18992 project
18993 .register_buffer_with_language_servers(&edited_buffer, cx)
18994 });
18995 });
18996 if edited_buffer.read(cx).file().is_some() {
18997 self.pull_diagnostics(
18998 Some(edited_buffer.read(cx).remote_id()),
18999 window,
19000 cx,
19001 );
19002 }
19003 }
19004 }
19005 cx.emit(EditorEvent::BufferEdited);
19006 cx.emit(SearchEvent::MatchesInvalidated);
19007 if *singleton_buffer_edited {
19008 if let Some(buffer) = edited_buffer {
19009 if buffer.read(cx).file().is_none() {
19010 cx.emit(EditorEvent::TitleChanged);
19011 }
19012 }
19013 if let Some(project) = &self.project {
19014 #[allow(clippy::mutable_key_type)]
19015 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19016 multibuffer
19017 .all_buffers()
19018 .into_iter()
19019 .filter_map(|buffer| {
19020 buffer.update(cx, |buffer, cx| {
19021 let language = buffer.language()?;
19022 let should_discard = project.update(cx, |project, cx| {
19023 project.is_local()
19024 && !project.has_language_servers_for(buffer, cx)
19025 });
19026 should_discard.not().then_some(language.clone())
19027 })
19028 })
19029 .collect::<HashSet<_>>()
19030 });
19031 if !languages_affected.is_empty() {
19032 self.refresh_inlay_hints(
19033 InlayHintRefreshReason::BufferEdited(languages_affected),
19034 cx,
19035 );
19036 }
19037 }
19038 }
19039
19040 let Some(project) = &self.project else { return };
19041 let (telemetry, is_via_ssh) = {
19042 let project = project.read(cx);
19043 let telemetry = project.client().telemetry().clone();
19044 let is_via_ssh = project.is_via_ssh();
19045 (telemetry, is_via_ssh)
19046 };
19047 refresh_linked_ranges(self, window, cx);
19048 telemetry.log_edit_event("editor", is_via_ssh);
19049 }
19050 multi_buffer::Event::ExcerptsAdded {
19051 buffer,
19052 predecessor,
19053 excerpts,
19054 } => {
19055 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19056 let buffer_id = buffer.read(cx).remote_id();
19057 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19058 if let Some(project) = &self.project {
19059 update_uncommitted_diff_for_buffer(
19060 cx.entity(),
19061 project,
19062 [buffer.clone()],
19063 self.buffer.clone(),
19064 cx,
19065 )
19066 .detach();
19067 }
19068 }
19069 cx.emit(EditorEvent::ExcerptsAdded {
19070 buffer: buffer.clone(),
19071 predecessor: *predecessor,
19072 excerpts: excerpts.clone(),
19073 });
19074 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19075 }
19076 multi_buffer::Event::ExcerptsRemoved {
19077 ids,
19078 removed_buffer_ids,
19079 } => {
19080 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19081 let buffer = self.buffer.read(cx);
19082 self.registered_buffers
19083 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19084 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19085 cx.emit(EditorEvent::ExcerptsRemoved {
19086 ids: ids.clone(),
19087 removed_buffer_ids: removed_buffer_ids.clone(),
19088 })
19089 }
19090 multi_buffer::Event::ExcerptsEdited {
19091 excerpt_ids,
19092 buffer_ids,
19093 } => {
19094 self.display_map.update(cx, |map, cx| {
19095 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19096 });
19097 cx.emit(EditorEvent::ExcerptsEdited {
19098 ids: excerpt_ids.clone(),
19099 })
19100 }
19101 multi_buffer::Event::ExcerptsExpanded { ids } => {
19102 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19103 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19104 }
19105 multi_buffer::Event::Reparsed(buffer_id) => {
19106 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19107 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19108
19109 cx.emit(EditorEvent::Reparsed(*buffer_id));
19110 }
19111 multi_buffer::Event::DiffHunksToggled => {
19112 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19113 }
19114 multi_buffer::Event::LanguageChanged(buffer_id) => {
19115 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19116 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19117 cx.emit(EditorEvent::Reparsed(*buffer_id));
19118 cx.notify();
19119 }
19120 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19121 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19122 multi_buffer::Event::FileHandleChanged
19123 | multi_buffer::Event::Reloaded
19124 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19125 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19126 multi_buffer::Event::DiagnosticsUpdated => {
19127 self.update_diagnostics_state(window, cx);
19128 }
19129 _ => {}
19130 };
19131 }
19132
19133 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19134 self.refresh_active_diagnostics(cx);
19135 self.refresh_inline_diagnostics(true, window, cx);
19136 self.scrollbar_marker_state.dirty = true;
19137 cx.notify();
19138 }
19139
19140 pub fn start_temporary_diff_override(&mut self) {
19141 self.load_diff_task.take();
19142 self.temporary_diff_override = true;
19143 }
19144
19145 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19146 self.temporary_diff_override = false;
19147 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19148 self.buffer.update(cx, |buffer, cx| {
19149 buffer.set_all_diff_hunks_collapsed(cx);
19150 });
19151
19152 if let Some(project) = self.project.clone() {
19153 self.load_diff_task = Some(
19154 update_uncommitted_diff_for_buffer(
19155 cx.entity(),
19156 &project,
19157 self.buffer.read(cx).all_buffers(),
19158 self.buffer.clone(),
19159 cx,
19160 )
19161 .shared(),
19162 );
19163 }
19164 }
19165
19166 fn on_display_map_changed(
19167 &mut self,
19168 _: Entity<DisplayMap>,
19169 _: &mut Window,
19170 cx: &mut Context<Self>,
19171 ) {
19172 cx.notify();
19173 }
19174
19175 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19176 let new_severity = if self.diagnostics_enabled() {
19177 EditorSettings::get_global(cx)
19178 .diagnostics_max_severity
19179 .unwrap_or(DiagnosticSeverity::Hint)
19180 } else {
19181 DiagnosticSeverity::Off
19182 };
19183 self.set_max_diagnostics_severity(new_severity, cx);
19184 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19185 self.update_edit_prediction_settings(cx);
19186 self.refresh_inline_completion(true, false, window, cx);
19187 self.refresh_inlay_hints(
19188 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19189 self.selections.newest_anchor().head(),
19190 &self.buffer.read(cx).snapshot(cx),
19191 cx,
19192 )),
19193 cx,
19194 );
19195
19196 let old_cursor_shape = self.cursor_shape;
19197
19198 {
19199 let editor_settings = EditorSettings::get_global(cx);
19200 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19201 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19202 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19203 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19204 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19205 }
19206
19207 if old_cursor_shape != self.cursor_shape {
19208 cx.emit(EditorEvent::CursorShapeChanged);
19209 }
19210
19211 let project_settings = ProjectSettings::get_global(cx);
19212 self.serialize_dirty_buffers =
19213 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19214
19215 if self.mode.is_full() {
19216 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19217 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19218 if self.show_inline_diagnostics != show_inline_diagnostics {
19219 self.show_inline_diagnostics = show_inline_diagnostics;
19220 self.refresh_inline_diagnostics(false, window, cx);
19221 }
19222
19223 if self.git_blame_inline_enabled != inline_blame_enabled {
19224 self.toggle_git_blame_inline_internal(false, window, cx);
19225 }
19226
19227 let minimap_settings = EditorSettings::get_global(cx).minimap;
19228 if self.minimap_visibility != MinimapVisibility::Disabled {
19229 if self.minimap_visibility.settings_visibility()
19230 != minimap_settings.minimap_enabled()
19231 {
19232 self.set_minimap_visibility(
19233 MinimapVisibility::for_mode(self.mode(), cx),
19234 window,
19235 cx,
19236 );
19237 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19238 minimap_entity.update(cx, |minimap_editor, cx| {
19239 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19240 })
19241 }
19242 }
19243 }
19244
19245 cx.notify();
19246 }
19247
19248 pub fn set_searchable(&mut self, searchable: bool) {
19249 self.searchable = searchable;
19250 }
19251
19252 pub fn searchable(&self) -> bool {
19253 self.searchable
19254 }
19255
19256 fn open_proposed_changes_editor(
19257 &mut self,
19258 _: &OpenProposedChangesEditor,
19259 window: &mut Window,
19260 cx: &mut Context<Self>,
19261 ) {
19262 let Some(workspace) = self.workspace() else {
19263 cx.propagate();
19264 return;
19265 };
19266
19267 let selections = self.selections.all::<usize>(cx);
19268 let multi_buffer = self.buffer.read(cx);
19269 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19270 let mut new_selections_by_buffer = HashMap::default();
19271 for selection in selections {
19272 for (buffer, range, _) in
19273 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19274 {
19275 let mut range = range.to_point(buffer);
19276 range.start.column = 0;
19277 range.end.column = buffer.line_len(range.end.row);
19278 new_selections_by_buffer
19279 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19280 .or_insert(Vec::new())
19281 .push(range)
19282 }
19283 }
19284
19285 let proposed_changes_buffers = new_selections_by_buffer
19286 .into_iter()
19287 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19288 .collect::<Vec<_>>();
19289 let proposed_changes_editor = cx.new(|cx| {
19290 ProposedChangesEditor::new(
19291 "Proposed changes",
19292 proposed_changes_buffers,
19293 self.project.clone(),
19294 window,
19295 cx,
19296 )
19297 });
19298
19299 window.defer(cx, move |window, cx| {
19300 workspace.update(cx, |workspace, cx| {
19301 workspace.active_pane().update(cx, |pane, cx| {
19302 pane.add_item(
19303 Box::new(proposed_changes_editor),
19304 true,
19305 true,
19306 None,
19307 window,
19308 cx,
19309 );
19310 });
19311 });
19312 });
19313 }
19314
19315 pub fn open_excerpts_in_split(
19316 &mut self,
19317 _: &OpenExcerptsSplit,
19318 window: &mut Window,
19319 cx: &mut Context<Self>,
19320 ) {
19321 self.open_excerpts_common(None, true, window, cx)
19322 }
19323
19324 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19325 self.open_excerpts_common(None, false, window, cx)
19326 }
19327
19328 fn open_excerpts_common(
19329 &mut self,
19330 jump_data: Option<JumpData>,
19331 split: bool,
19332 window: &mut Window,
19333 cx: &mut Context<Self>,
19334 ) {
19335 let Some(workspace) = self.workspace() else {
19336 cx.propagate();
19337 return;
19338 };
19339
19340 if self.buffer.read(cx).is_singleton() {
19341 cx.propagate();
19342 return;
19343 }
19344
19345 let mut new_selections_by_buffer = HashMap::default();
19346 match &jump_data {
19347 Some(JumpData::MultiBufferPoint {
19348 excerpt_id,
19349 position,
19350 anchor,
19351 line_offset_from_top,
19352 }) => {
19353 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19354 if let Some(buffer) = multi_buffer_snapshot
19355 .buffer_id_for_excerpt(*excerpt_id)
19356 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19357 {
19358 let buffer_snapshot = buffer.read(cx).snapshot();
19359 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19360 language::ToPoint::to_point(anchor, &buffer_snapshot)
19361 } else {
19362 buffer_snapshot.clip_point(*position, Bias::Left)
19363 };
19364 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19365 new_selections_by_buffer.insert(
19366 buffer,
19367 (
19368 vec![jump_to_offset..jump_to_offset],
19369 Some(*line_offset_from_top),
19370 ),
19371 );
19372 }
19373 }
19374 Some(JumpData::MultiBufferRow {
19375 row,
19376 line_offset_from_top,
19377 }) => {
19378 let point = MultiBufferPoint::new(row.0, 0);
19379 if let Some((buffer, buffer_point, _)) =
19380 self.buffer.read(cx).point_to_buffer_point(point, cx)
19381 {
19382 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19383 new_selections_by_buffer
19384 .entry(buffer)
19385 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19386 .0
19387 .push(buffer_offset..buffer_offset)
19388 }
19389 }
19390 None => {
19391 let selections = self.selections.all::<usize>(cx);
19392 let multi_buffer = self.buffer.read(cx);
19393 for selection in selections {
19394 for (snapshot, range, _, anchor) in multi_buffer
19395 .snapshot(cx)
19396 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19397 {
19398 if let Some(anchor) = anchor {
19399 // selection is in a deleted hunk
19400 let Some(buffer_id) = anchor.buffer_id else {
19401 continue;
19402 };
19403 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19404 continue;
19405 };
19406 let offset = text::ToOffset::to_offset(
19407 &anchor.text_anchor,
19408 &buffer_handle.read(cx).snapshot(),
19409 );
19410 let range = offset..offset;
19411 new_selections_by_buffer
19412 .entry(buffer_handle)
19413 .or_insert((Vec::new(), None))
19414 .0
19415 .push(range)
19416 } else {
19417 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19418 else {
19419 continue;
19420 };
19421 new_selections_by_buffer
19422 .entry(buffer_handle)
19423 .or_insert((Vec::new(), None))
19424 .0
19425 .push(range)
19426 }
19427 }
19428 }
19429 }
19430 }
19431
19432 new_selections_by_buffer
19433 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19434
19435 if new_selections_by_buffer.is_empty() {
19436 return;
19437 }
19438
19439 // We defer the pane interaction because we ourselves are a workspace item
19440 // and activating a new item causes the pane to call a method on us reentrantly,
19441 // which panics if we're on the stack.
19442 window.defer(cx, move |window, cx| {
19443 workspace.update(cx, |workspace, cx| {
19444 let pane = if split {
19445 workspace.adjacent_pane(window, cx)
19446 } else {
19447 workspace.active_pane().clone()
19448 };
19449
19450 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19451 let editor = buffer
19452 .read(cx)
19453 .file()
19454 .is_none()
19455 .then(|| {
19456 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19457 // so `workspace.open_project_item` will never find them, always opening a new editor.
19458 // Instead, we try to activate the existing editor in the pane first.
19459 let (editor, pane_item_index) =
19460 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19461 let editor = item.downcast::<Editor>()?;
19462 let singleton_buffer =
19463 editor.read(cx).buffer().read(cx).as_singleton()?;
19464 if singleton_buffer == buffer {
19465 Some((editor, i))
19466 } else {
19467 None
19468 }
19469 })?;
19470 pane.update(cx, |pane, cx| {
19471 pane.activate_item(pane_item_index, true, true, window, cx)
19472 });
19473 Some(editor)
19474 })
19475 .flatten()
19476 .unwrap_or_else(|| {
19477 workspace.open_project_item::<Self>(
19478 pane.clone(),
19479 buffer,
19480 true,
19481 true,
19482 window,
19483 cx,
19484 )
19485 });
19486
19487 editor.update(cx, |editor, cx| {
19488 let autoscroll = match scroll_offset {
19489 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19490 None => Autoscroll::newest(),
19491 };
19492 let nav_history = editor.nav_history.take();
19493 editor.change_selections(Some(autoscroll), window, cx, |s| {
19494 s.select_ranges(ranges);
19495 });
19496 editor.nav_history = nav_history;
19497 });
19498 }
19499 })
19500 });
19501 }
19502
19503 // For now, don't allow opening excerpts in buffers that aren't backed by
19504 // regular project files.
19505 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19506 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19507 }
19508
19509 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19510 let snapshot = self.buffer.read(cx).read(cx);
19511 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19512 Some(
19513 ranges
19514 .iter()
19515 .map(move |range| {
19516 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19517 })
19518 .collect(),
19519 )
19520 }
19521
19522 fn selection_replacement_ranges(
19523 &self,
19524 range: Range<OffsetUtf16>,
19525 cx: &mut App,
19526 ) -> Vec<Range<OffsetUtf16>> {
19527 let selections = self.selections.all::<OffsetUtf16>(cx);
19528 let newest_selection = selections
19529 .iter()
19530 .max_by_key(|selection| selection.id)
19531 .unwrap();
19532 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19533 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19534 let snapshot = self.buffer.read(cx).read(cx);
19535 selections
19536 .into_iter()
19537 .map(|mut selection| {
19538 selection.start.0 =
19539 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19540 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19541 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19542 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19543 })
19544 .collect()
19545 }
19546
19547 fn report_editor_event(
19548 &self,
19549 event_type: &'static str,
19550 file_extension: Option<String>,
19551 cx: &App,
19552 ) {
19553 if cfg!(any(test, feature = "test-support")) {
19554 return;
19555 }
19556
19557 let Some(project) = &self.project else { return };
19558
19559 // If None, we are in a file without an extension
19560 let file = self
19561 .buffer
19562 .read(cx)
19563 .as_singleton()
19564 .and_then(|b| b.read(cx).file());
19565 let file_extension = file_extension.or(file
19566 .as_ref()
19567 .and_then(|file| Path::new(file.file_name(cx)).extension())
19568 .and_then(|e| e.to_str())
19569 .map(|a| a.to_string()));
19570
19571 let vim_mode = vim_enabled(cx);
19572
19573 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19574 let copilot_enabled = edit_predictions_provider
19575 == language::language_settings::EditPredictionProvider::Copilot;
19576 let copilot_enabled_for_language = self
19577 .buffer
19578 .read(cx)
19579 .language_settings(cx)
19580 .show_edit_predictions;
19581
19582 let project = project.read(cx);
19583 telemetry::event!(
19584 event_type,
19585 file_extension,
19586 vim_mode,
19587 copilot_enabled,
19588 copilot_enabled_for_language,
19589 edit_predictions_provider,
19590 is_via_ssh = project.is_via_ssh(),
19591 );
19592 }
19593
19594 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19595 /// with each line being an array of {text, highlight} objects.
19596 fn copy_highlight_json(
19597 &mut self,
19598 _: &CopyHighlightJson,
19599 window: &mut Window,
19600 cx: &mut Context<Self>,
19601 ) {
19602 #[derive(Serialize)]
19603 struct Chunk<'a> {
19604 text: String,
19605 highlight: Option<&'a str>,
19606 }
19607
19608 let snapshot = self.buffer.read(cx).snapshot(cx);
19609 let range = self
19610 .selected_text_range(false, window, cx)
19611 .and_then(|selection| {
19612 if selection.range.is_empty() {
19613 None
19614 } else {
19615 Some(selection.range)
19616 }
19617 })
19618 .unwrap_or_else(|| 0..snapshot.len());
19619
19620 let chunks = snapshot.chunks(range, true);
19621 let mut lines = Vec::new();
19622 let mut line: VecDeque<Chunk> = VecDeque::new();
19623
19624 let Some(style) = self.style.as_ref() else {
19625 return;
19626 };
19627
19628 for chunk in chunks {
19629 let highlight = chunk
19630 .syntax_highlight_id
19631 .and_then(|id| id.name(&style.syntax));
19632 let mut chunk_lines = chunk.text.split('\n').peekable();
19633 while let Some(text) = chunk_lines.next() {
19634 let mut merged_with_last_token = false;
19635 if let Some(last_token) = line.back_mut() {
19636 if last_token.highlight == highlight {
19637 last_token.text.push_str(text);
19638 merged_with_last_token = true;
19639 }
19640 }
19641
19642 if !merged_with_last_token {
19643 line.push_back(Chunk {
19644 text: text.into(),
19645 highlight,
19646 });
19647 }
19648
19649 if chunk_lines.peek().is_some() {
19650 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19651 line.pop_front();
19652 }
19653 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19654 line.pop_back();
19655 }
19656
19657 lines.push(mem::take(&mut line));
19658 }
19659 }
19660 }
19661
19662 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19663 return;
19664 };
19665 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19666 }
19667
19668 pub fn open_context_menu(
19669 &mut self,
19670 _: &OpenContextMenu,
19671 window: &mut Window,
19672 cx: &mut Context<Self>,
19673 ) {
19674 self.request_autoscroll(Autoscroll::newest(), cx);
19675 let position = self.selections.newest_display(cx).start;
19676 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19677 }
19678
19679 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19680 &self.inlay_hint_cache
19681 }
19682
19683 pub fn replay_insert_event(
19684 &mut self,
19685 text: &str,
19686 relative_utf16_range: Option<Range<isize>>,
19687 window: &mut Window,
19688 cx: &mut Context<Self>,
19689 ) {
19690 if !self.input_enabled {
19691 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19692 return;
19693 }
19694 if let Some(relative_utf16_range) = relative_utf16_range {
19695 let selections = self.selections.all::<OffsetUtf16>(cx);
19696 self.change_selections(None, window, cx, |s| {
19697 let new_ranges = selections.into_iter().map(|range| {
19698 let start = OffsetUtf16(
19699 range
19700 .head()
19701 .0
19702 .saturating_add_signed(relative_utf16_range.start),
19703 );
19704 let end = OffsetUtf16(
19705 range
19706 .head()
19707 .0
19708 .saturating_add_signed(relative_utf16_range.end),
19709 );
19710 start..end
19711 });
19712 s.select_ranges(new_ranges);
19713 });
19714 }
19715
19716 self.handle_input(text, window, cx);
19717 }
19718
19719 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19720 let Some(provider) = self.semantics_provider.as_ref() else {
19721 return false;
19722 };
19723
19724 let mut supports = false;
19725 self.buffer().update(cx, |this, cx| {
19726 this.for_each_buffer(|buffer| {
19727 supports |= provider.supports_inlay_hints(buffer, cx);
19728 });
19729 });
19730
19731 supports
19732 }
19733
19734 pub fn is_focused(&self, window: &Window) -> bool {
19735 self.focus_handle.is_focused(window)
19736 }
19737
19738 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19739 cx.emit(EditorEvent::Focused);
19740
19741 if let Some(descendant) = self
19742 .last_focused_descendant
19743 .take()
19744 .and_then(|descendant| descendant.upgrade())
19745 {
19746 window.focus(&descendant);
19747 } else {
19748 if let Some(blame) = self.blame.as_ref() {
19749 blame.update(cx, GitBlame::focus)
19750 }
19751
19752 self.blink_manager.update(cx, BlinkManager::enable);
19753 self.show_cursor_names(window, cx);
19754 self.buffer.update(cx, |buffer, cx| {
19755 buffer.finalize_last_transaction(cx);
19756 if self.leader_id.is_none() {
19757 buffer.set_active_selections(
19758 &self.selections.disjoint_anchors(),
19759 self.selections.line_mode,
19760 self.cursor_shape,
19761 cx,
19762 );
19763 }
19764 });
19765 }
19766 }
19767
19768 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19769 cx.emit(EditorEvent::FocusedIn)
19770 }
19771
19772 fn handle_focus_out(
19773 &mut self,
19774 event: FocusOutEvent,
19775 _window: &mut Window,
19776 cx: &mut Context<Self>,
19777 ) {
19778 if event.blurred != self.focus_handle {
19779 self.last_focused_descendant = Some(event.blurred);
19780 }
19781 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19782 }
19783
19784 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19785 self.blink_manager.update(cx, BlinkManager::disable);
19786 self.buffer
19787 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19788
19789 if let Some(blame) = self.blame.as_ref() {
19790 blame.update(cx, GitBlame::blur)
19791 }
19792 if !self.hover_state.focused(window, cx) {
19793 hide_hover(self, cx);
19794 }
19795 if !self
19796 .context_menu
19797 .borrow()
19798 .as_ref()
19799 .is_some_and(|context_menu| context_menu.focused(window, cx))
19800 {
19801 self.hide_context_menu(window, cx);
19802 }
19803 self.discard_inline_completion(false, cx);
19804 cx.emit(EditorEvent::Blurred);
19805 cx.notify();
19806 }
19807
19808 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19809 let mut pending: String = window
19810 .pending_input_keystrokes()
19811 .into_iter()
19812 .flatten()
19813 .filter_map(|keystroke| {
19814 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19815 keystroke.key_char.clone()
19816 } else {
19817 None
19818 }
19819 })
19820 .collect();
19821
19822 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19823 pending = "".to_string();
19824 }
19825
19826 let existing_pending = self
19827 .text_highlights::<PendingInput>(cx)
19828 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19829 if existing_pending.is_none() && pending.is_empty() {
19830 return;
19831 }
19832 let transaction =
19833 self.transact(window, cx, |this, window, cx| {
19834 let selections = this.selections.all::<usize>(cx);
19835 let edits = selections
19836 .iter()
19837 .map(|selection| (selection.end..selection.end, pending.clone()));
19838 this.edit(edits, cx);
19839 this.change_selections(None, window, cx, |s| {
19840 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19841 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19842 }));
19843 });
19844 if let Some(existing_ranges) = existing_pending {
19845 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19846 this.edit(edits, cx);
19847 }
19848 });
19849
19850 let snapshot = self.snapshot(window, cx);
19851 let ranges = self
19852 .selections
19853 .all::<usize>(cx)
19854 .into_iter()
19855 .map(|selection| {
19856 snapshot.buffer_snapshot.anchor_after(selection.end)
19857 ..snapshot
19858 .buffer_snapshot
19859 .anchor_before(selection.end + pending.len())
19860 })
19861 .collect();
19862
19863 if pending.is_empty() {
19864 self.clear_highlights::<PendingInput>(cx);
19865 } else {
19866 self.highlight_text::<PendingInput>(
19867 ranges,
19868 HighlightStyle {
19869 underline: Some(UnderlineStyle {
19870 thickness: px(1.),
19871 color: None,
19872 wavy: false,
19873 }),
19874 ..Default::default()
19875 },
19876 cx,
19877 );
19878 }
19879
19880 self.ime_transaction = self.ime_transaction.or(transaction);
19881 if let Some(transaction) = self.ime_transaction {
19882 self.buffer.update(cx, |buffer, cx| {
19883 buffer.group_until_transaction(transaction, cx);
19884 });
19885 }
19886
19887 if self.text_highlights::<PendingInput>(cx).is_none() {
19888 self.ime_transaction.take();
19889 }
19890 }
19891
19892 pub fn register_action_renderer(
19893 &mut self,
19894 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19895 ) -> Subscription {
19896 let id = self.next_editor_action_id.post_inc();
19897 self.editor_actions
19898 .borrow_mut()
19899 .insert(id, Box::new(listener));
19900
19901 let editor_actions = self.editor_actions.clone();
19902 Subscription::new(move || {
19903 editor_actions.borrow_mut().remove(&id);
19904 })
19905 }
19906
19907 pub fn register_action<A: Action>(
19908 &mut self,
19909 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19910 ) -> Subscription {
19911 let id = self.next_editor_action_id.post_inc();
19912 let listener = Arc::new(listener);
19913 self.editor_actions.borrow_mut().insert(
19914 id,
19915 Box::new(move |_, window, _| {
19916 let listener = listener.clone();
19917 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19918 let action = action.downcast_ref().unwrap();
19919 if phase == DispatchPhase::Bubble {
19920 listener(action, window, cx)
19921 }
19922 })
19923 }),
19924 );
19925
19926 let editor_actions = self.editor_actions.clone();
19927 Subscription::new(move || {
19928 editor_actions.borrow_mut().remove(&id);
19929 })
19930 }
19931
19932 pub fn file_header_size(&self) -> u32 {
19933 FILE_HEADER_HEIGHT
19934 }
19935
19936 pub fn restore(
19937 &mut self,
19938 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19939 window: &mut Window,
19940 cx: &mut Context<Self>,
19941 ) {
19942 let workspace = self.workspace();
19943 let project = self.project.as_ref();
19944 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19945 let mut tasks = Vec::new();
19946 for (buffer_id, changes) in revert_changes {
19947 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19948 buffer.update(cx, |buffer, cx| {
19949 buffer.edit(
19950 changes
19951 .into_iter()
19952 .map(|(range, text)| (range, text.to_string())),
19953 None,
19954 cx,
19955 );
19956 });
19957
19958 if let Some(project) =
19959 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19960 {
19961 project.update(cx, |project, cx| {
19962 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19963 })
19964 }
19965 }
19966 }
19967 tasks
19968 });
19969 cx.spawn_in(window, async move |_, cx| {
19970 for (buffer, task) in save_tasks {
19971 let result = task.await;
19972 if result.is_err() {
19973 let Some(path) = buffer
19974 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19975 .ok()
19976 else {
19977 continue;
19978 };
19979 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19980 let Some(task) = cx
19981 .update_window_entity(&workspace, |workspace, window, cx| {
19982 workspace
19983 .open_path_preview(path, None, false, false, false, window, cx)
19984 })
19985 .ok()
19986 else {
19987 continue;
19988 };
19989 task.await.log_err();
19990 }
19991 }
19992 }
19993 })
19994 .detach();
19995 self.change_selections(None, window, cx, |selections| selections.refresh());
19996 }
19997
19998 pub fn to_pixel_point(
19999 &self,
20000 source: multi_buffer::Anchor,
20001 editor_snapshot: &EditorSnapshot,
20002 window: &mut Window,
20003 ) -> Option<gpui::Point<Pixels>> {
20004 let source_point = source.to_display_point(editor_snapshot);
20005 self.display_to_pixel_point(source_point, editor_snapshot, window)
20006 }
20007
20008 pub fn display_to_pixel_point(
20009 &self,
20010 source: DisplayPoint,
20011 editor_snapshot: &EditorSnapshot,
20012 window: &mut Window,
20013 ) -> Option<gpui::Point<Pixels>> {
20014 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20015 let text_layout_details = self.text_layout_details(window);
20016 let scroll_top = text_layout_details
20017 .scroll_anchor
20018 .scroll_position(editor_snapshot)
20019 .y;
20020
20021 if source.row().as_f32() < scroll_top.floor() {
20022 return None;
20023 }
20024 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20025 let source_y = line_height * (source.row().as_f32() - scroll_top);
20026 Some(gpui::Point::new(source_x, source_y))
20027 }
20028
20029 pub fn has_visible_completions_menu(&self) -> bool {
20030 !self.edit_prediction_preview_is_active()
20031 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20032 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20033 })
20034 }
20035
20036 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20037 if self.mode.is_minimap() {
20038 return;
20039 }
20040 self.addons
20041 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20042 }
20043
20044 pub fn unregister_addon<T: Addon>(&mut self) {
20045 self.addons.remove(&std::any::TypeId::of::<T>());
20046 }
20047
20048 pub fn addon<T: Addon>(&self) -> Option<&T> {
20049 let type_id = std::any::TypeId::of::<T>();
20050 self.addons
20051 .get(&type_id)
20052 .and_then(|item| item.to_any().downcast_ref::<T>())
20053 }
20054
20055 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20056 let type_id = std::any::TypeId::of::<T>();
20057 self.addons
20058 .get_mut(&type_id)
20059 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20060 }
20061
20062 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20063 let text_layout_details = self.text_layout_details(window);
20064 let style = &text_layout_details.editor_style;
20065 let font_id = window.text_system().resolve_font(&style.text.font());
20066 let font_size = style.text.font_size.to_pixels(window.rem_size());
20067 let line_height = style.text.line_height_in_pixels(window.rem_size());
20068 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20069
20070 gpui::Size::new(em_width, line_height)
20071 }
20072
20073 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20074 self.load_diff_task.clone()
20075 }
20076
20077 fn read_metadata_from_db(
20078 &mut self,
20079 item_id: u64,
20080 workspace_id: WorkspaceId,
20081 window: &mut Window,
20082 cx: &mut Context<Editor>,
20083 ) {
20084 if self.is_singleton(cx)
20085 && !self.mode.is_minimap()
20086 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20087 {
20088 let buffer_snapshot = OnceCell::new();
20089
20090 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20091 if !folds.is_empty() {
20092 let snapshot =
20093 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20094 self.fold_ranges(
20095 folds
20096 .into_iter()
20097 .map(|(start, end)| {
20098 snapshot.clip_offset(start, Bias::Left)
20099 ..snapshot.clip_offset(end, Bias::Right)
20100 })
20101 .collect(),
20102 false,
20103 window,
20104 cx,
20105 );
20106 }
20107 }
20108
20109 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20110 if !selections.is_empty() {
20111 let snapshot =
20112 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20113 // skip adding the initial selection to selection history
20114 self.selection_history.mode = SelectionHistoryMode::Skipping;
20115 self.change_selections(None, window, cx, |s| {
20116 s.select_ranges(selections.into_iter().map(|(start, end)| {
20117 snapshot.clip_offset(start, Bias::Left)
20118 ..snapshot.clip_offset(end, Bias::Right)
20119 }));
20120 });
20121 self.selection_history.mode = SelectionHistoryMode::Normal;
20122 }
20123 };
20124 }
20125
20126 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20127 }
20128}
20129
20130fn vim_enabled(cx: &App) -> bool {
20131 cx.global::<SettingsStore>()
20132 .raw_user_settings()
20133 .get("vim_mode")
20134 == Some(&serde_json::Value::Bool(true))
20135}
20136
20137fn process_completion_for_edit(
20138 completion: &Completion,
20139 intent: CompletionIntent,
20140 buffer: &Entity<Buffer>,
20141 cursor_position: &text::Anchor,
20142 cx: &mut Context<Editor>,
20143) -> CompletionEdit {
20144 let buffer = buffer.read(cx);
20145 let buffer_snapshot = buffer.snapshot();
20146 let (snippet, new_text) = if completion.is_snippet() {
20147 // Workaround for typescript language server issues so that methods don't expand within
20148 // strings and functions with type expressions. The previous point is used because the query
20149 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20150 let mut snippet_source = completion.new_text.clone();
20151 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20152 previous_point.column = previous_point.column.saturating_sub(1);
20153 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20154 if scope.prefers_label_for_snippet_in_completion() {
20155 if let Some(label) = completion.label() {
20156 if matches!(
20157 completion.kind(),
20158 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20159 ) {
20160 snippet_source = label;
20161 }
20162 }
20163 }
20164 }
20165 match Snippet::parse(&snippet_source).log_err() {
20166 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20167 None => (None, completion.new_text.clone()),
20168 }
20169 } else {
20170 (None, completion.new_text.clone())
20171 };
20172
20173 let mut range_to_replace = {
20174 let replace_range = &completion.replace_range;
20175 if let CompletionSource::Lsp {
20176 insert_range: Some(insert_range),
20177 ..
20178 } = &completion.source
20179 {
20180 debug_assert_eq!(
20181 insert_range.start, replace_range.start,
20182 "insert_range and replace_range should start at the same position"
20183 );
20184 debug_assert!(
20185 insert_range
20186 .start
20187 .cmp(&cursor_position, &buffer_snapshot)
20188 .is_le(),
20189 "insert_range should start before or at cursor position"
20190 );
20191 debug_assert!(
20192 replace_range
20193 .start
20194 .cmp(&cursor_position, &buffer_snapshot)
20195 .is_le(),
20196 "replace_range should start before or at cursor position"
20197 );
20198 debug_assert!(
20199 insert_range
20200 .end
20201 .cmp(&cursor_position, &buffer_snapshot)
20202 .is_le(),
20203 "insert_range should end before or at cursor position"
20204 );
20205
20206 let should_replace = match intent {
20207 CompletionIntent::CompleteWithInsert => false,
20208 CompletionIntent::CompleteWithReplace => true,
20209 CompletionIntent::Complete | CompletionIntent::Compose => {
20210 let insert_mode =
20211 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20212 .completions
20213 .lsp_insert_mode;
20214 match insert_mode {
20215 LspInsertMode::Insert => false,
20216 LspInsertMode::Replace => true,
20217 LspInsertMode::ReplaceSubsequence => {
20218 let mut text_to_replace = buffer.chars_for_range(
20219 buffer.anchor_before(replace_range.start)
20220 ..buffer.anchor_after(replace_range.end),
20221 );
20222 let mut current_needle = text_to_replace.next();
20223 for haystack_ch in completion.label.text.chars() {
20224 if let Some(needle_ch) = current_needle {
20225 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20226 current_needle = text_to_replace.next();
20227 }
20228 }
20229 }
20230 current_needle.is_none()
20231 }
20232 LspInsertMode::ReplaceSuffix => {
20233 if replace_range
20234 .end
20235 .cmp(&cursor_position, &buffer_snapshot)
20236 .is_gt()
20237 {
20238 let range_after_cursor = *cursor_position..replace_range.end;
20239 let text_after_cursor = buffer
20240 .text_for_range(
20241 buffer.anchor_before(range_after_cursor.start)
20242 ..buffer.anchor_after(range_after_cursor.end),
20243 )
20244 .collect::<String>()
20245 .to_ascii_lowercase();
20246 completion
20247 .label
20248 .text
20249 .to_ascii_lowercase()
20250 .ends_with(&text_after_cursor)
20251 } else {
20252 true
20253 }
20254 }
20255 }
20256 }
20257 };
20258
20259 if should_replace {
20260 replace_range.clone()
20261 } else {
20262 insert_range.clone()
20263 }
20264 } else {
20265 replace_range.clone()
20266 }
20267 };
20268
20269 if range_to_replace
20270 .end
20271 .cmp(&cursor_position, &buffer_snapshot)
20272 .is_lt()
20273 {
20274 range_to_replace.end = *cursor_position;
20275 }
20276
20277 CompletionEdit {
20278 new_text,
20279 replace_range: range_to_replace.to_offset(&buffer),
20280 snippet,
20281 }
20282}
20283
20284struct CompletionEdit {
20285 new_text: String,
20286 replace_range: Range<usize>,
20287 snippet: Option<Snippet>,
20288}
20289
20290fn insert_extra_newline_brackets(
20291 buffer: &MultiBufferSnapshot,
20292 range: Range<usize>,
20293 language: &language::LanguageScope,
20294) -> bool {
20295 let leading_whitespace_len = buffer
20296 .reversed_chars_at(range.start)
20297 .take_while(|c| c.is_whitespace() && *c != '\n')
20298 .map(|c| c.len_utf8())
20299 .sum::<usize>();
20300 let trailing_whitespace_len = buffer
20301 .chars_at(range.end)
20302 .take_while(|c| c.is_whitespace() && *c != '\n')
20303 .map(|c| c.len_utf8())
20304 .sum::<usize>();
20305 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20306
20307 language.brackets().any(|(pair, enabled)| {
20308 let pair_start = pair.start.trim_end();
20309 let pair_end = pair.end.trim_start();
20310
20311 enabled
20312 && pair.newline
20313 && buffer.contains_str_at(range.end, pair_end)
20314 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20315 })
20316}
20317
20318fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20319 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20320 [(buffer, range, _)] => (*buffer, range.clone()),
20321 _ => return false,
20322 };
20323 let pair = {
20324 let mut result: Option<BracketMatch> = None;
20325
20326 for pair in buffer
20327 .all_bracket_ranges(range.clone())
20328 .filter(move |pair| {
20329 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20330 })
20331 {
20332 let len = pair.close_range.end - pair.open_range.start;
20333
20334 if let Some(existing) = &result {
20335 let existing_len = existing.close_range.end - existing.open_range.start;
20336 if len > existing_len {
20337 continue;
20338 }
20339 }
20340
20341 result = Some(pair);
20342 }
20343
20344 result
20345 };
20346 let Some(pair) = pair else {
20347 return false;
20348 };
20349 pair.newline_only
20350 && buffer
20351 .chars_for_range(pair.open_range.end..range.start)
20352 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20353 .all(|c| c.is_whitespace() && c != '\n')
20354}
20355
20356fn update_uncommitted_diff_for_buffer(
20357 editor: Entity<Editor>,
20358 project: &Entity<Project>,
20359 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20360 buffer: Entity<MultiBuffer>,
20361 cx: &mut App,
20362) -> Task<()> {
20363 let mut tasks = Vec::new();
20364 project.update(cx, |project, cx| {
20365 for buffer in buffers {
20366 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20367 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20368 }
20369 }
20370 });
20371 cx.spawn(async move |cx| {
20372 let diffs = future::join_all(tasks).await;
20373 if editor
20374 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20375 .unwrap_or(false)
20376 {
20377 return;
20378 }
20379
20380 buffer
20381 .update(cx, |buffer, cx| {
20382 for diff in diffs.into_iter().flatten() {
20383 buffer.add_diff(diff, cx);
20384 }
20385 })
20386 .ok();
20387 })
20388}
20389
20390fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20391 let tab_size = tab_size.get() as usize;
20392 let mut width = offset;
20393
20394 for ch in text.chars() {
20395 width += if ch == '\t' {
20396 tab_size - (width % tab_size)
20397 } else {
20398 1
20399 };
20400 }
20401
20402 width - offset
20403}
20404
20405#[cfg(test)]
20406mod tests {
20407 use super::*;
20408
20409 #[test]
20410 fn test_string_size_with_expanded_tabs() {
20411 let nz = |val| NonZeroU32::new(val).unwrap();
20412 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20413 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20414 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20415 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20416 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20417 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20418 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20419 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20420 }
20421}
20422
20423/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20424struct WordBreakingTokenizer<'a> {
20425 input: &'a str,
20426}
20427
20428impl<'a> WordBreakingTokenizer<'a> {
20429 fn new(input: &'a str) -> Self {
20430 Self { input }
20431 }
20432}
20433
20434fn is_char_ideographic(ch: char) -> bool {
20435 use unicode_script::Script::*;
20436 use unicode_script::UnicodeScript;
20437 matches!(ch.script(), Han | Tangut | Yi)
20438}
20439
20440fn is_grapheme_ideographic(text: &str) -> bool {
20441 text.chars().any(is_char_ideographic)
20442}
20443
20444fn is_grapheme_whitespace(text: &str) -> bool {
20445 text.chars().any(|x| x.is_whitespace())
20446}
20447
20448fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20449 text.chars().next().map_or(false, |ch| {
20450 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20451 })
20452}
20453
20454#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20455enum WordBreakToken<'a> {
20456 Word { token: &'a str, grapheme_len: usize },
20457 InlineWhitespace { token: &'a str, grapheme_len: usize },
20458 Newline,
20459}
20460
20461impl<'a> Iterator for WordBreakingTokenizer<'a> {
20462 /// Yields a span, the count of graphemes in the token, and whether it was
20463 /// whitespace. Note that it also breaks at word boundaries.
20464 type Item = WordBreakToken<'a>;
20465
20466 fn next(&mut self) -> Option<Self::Item> {
20467 use unicode_segmentation::UnicodeSegmentation;
20468 if self.input.is_empty() {
20469 return None;
20470 }
20471
20472 let mut iter = self.input.graphemes(true).peekable();
20473 let mut offset = 0;
20474 let mut grapheme_len = 0;
20475 if let Some(first_grapheme) = iter.next() {
20476 let is_newline = first_grapheme == "\n";
20477 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20478 offset += first_grapheme.len();
20479 grapheme_len += 1;
20480 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20481 if let Some(grapheme) = iter.peek().copied() {
20482 if should_stay_with_preceding_ideograph(grapheme) {
20483 offset += grapheme.len();
20484 grapheme_len += 1;
20485 }
20486 }
20487 } else {
20488 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20489 let mut next_word_bound = words.peek().copied();
20490 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20491 next_word_bound = words.next();
20492 }
20493 while let Some(grapheme) = iter.peek().copied() {
20494 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20495 break;
20496 };
20497 if is_grapheme_whitespace(grapheme) != is_whitespace
20498 || (grapheme == "\n") != is_newline
20499 {
20500 break;
20501 };
20502 offset += grapheme.len();
20503 grapheme_len += 1;
20504 iter.next();
20505 }
20506 }
20507 let token = &self.input[..offset];
20508 self.input = &self.input[offset..];
20509 if token == "\n" {
20510 Some(WordBreakToken::Newline)
20511 } else if is_whitespace {
20512 Some(WordBreakToken::InlineWhitespace {
20513 token,
20514 grapheme_len,
20515 })
20516 } else {
20517 Some(WordBreakToken::Word {
20518 token,
20519 grapheme_len,
20520 })
20521 }
20522 } else {
20523 None
20524 }
20525 }
20526}
20527
20528#[test]
20529fn test_word_breaking_tokenizer() {
20530 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20531 ("", &[]),
20532 (" ", &[whitespace(" ", 2)]),
20533 ("Ʒ", &[word("Ʒ", 1)]),
20534 ("Ǽ", &[word("Ǽ", 1)]),
20535 ("⋑", &[word("⋑", 1)]),
20536 ("⋑⋑", &[word("⋑⋑", 2)]),
20537 (
20538 "原理,进而",
20539 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20540 ),
20541 (
20542 "hello world",
20543 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20544 ),
20545 (
20546 "hello, world",
20547 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20548 ),
20549 (
20550 " hello world",
20551 &[
20552 whitespace(" ", 2),
20553 word("hello", 5),
20554 whitespace(" ", 1),
20555 word("world", 5),
20556 ],
20557 ),
20558 (
20559 "这是什么 \n 钢笔",
20560 &[
20561 word("这", 1),
20562 word("是", 1),
20563 word("什", 1),
20564 word("么", 1),
20565 whitespace(" ", 1),
20566 newline(),
20567 whitespace(" ", 1),
20568 word("钢", 1),
20569 word("笔", 1),
20570 ],
20571 ),
20572 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20573 ];
20574
20575 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20576 WordBreakToken::Word {
20577 token,
20578 grapheme_len,
20579 }
20580 }
20581
20582 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20583 WordBreakToken::InlineWhitespace {
20584 token,
20585 grapheme_len,
20586 }
20587 }
20588
20589 fn newline() -> WordBreakToken<'static> {
20590 WordBreakToken::Newline
20591 }
20592
20593 for (input, result) in tests {
20594 assert_eq!(
20595 WordBreakingTokenizer::new(input)
20596 .collect::<Vec<_>>()
20597 .as_slice(),
20598 *result,
20599 );
20600 }
20601}
20602
20603fn wrap_with_prefix(
20604 line_prefix: String,
20605 unwrapped_text: String,
20606 wrap_column: usize,
20607 tab_size: NonZeroU32,
20608 preserve_existing_whitespace: bool,
20609) -> String {
20610 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20611 let mut wrapped_text = String::new();
20612 let mut current_line = line_prefix.clone();
20613
20614 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20615 let mut current_line_len = line_prefix_len;
20616 let mut in_whitespace = false;
20617 for token in tokenizer {
20618 let have_preceding_whitespace = in_whitespace;
20619 match token {
20620 WordBreakToken::Word {
20621 token,
20622 grapheme_len,
20623 } => {
20624 in_whitespace = false;
20625 if current_line_len + grapheme_len > wrap_column
20626 && 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 }
20633 current_line.push_str(token);
20634 current_line_len += grapheme_len;
20635 }
20636 WordBreakToken::InlineWhitespace {
20637 mut token,
20638 mut grapheme_len,
20639 } => {
20640 in_whitespace = true;
20641 if have_preceding_whitespace && !preserve_existing_whitespace {
20642 continue;
20643 }
20644 if !preserve_existing_whitespace {
20645 token = " ";
20646 grapheme_len = 1;
20647 }
20648 if current_line_len + grapheme_len > wrap_column {
20649 wrapped_text.push_str(current_line.trim_end());
20650 wrapped_text.push('\n');
20651 current_line.truncate(line_prefix.len());
20652 current_line_len = line_prefix_len;
20653 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20654 current_line.push_str(token);
20655 current_line_len += grapheme_len;
20656 }
20657 }
20658 WordBreakToken::Newline => {
20659 in_whitespace = true;
20660 if preserve_existing_whitespace {
20661 wrapped_text.push_str(current_line.trim_end());
20662 wrapped_text.push('\n');
20663 current_line.truncate(line_prefix.len());
20664 current_line_len = line_prefix_len;
20665 } else if have_preceding_whitespace {
20666 continue;
20667 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20668 {
20669 wrapped_text.push_str(current_line.trim_end());
20670 wrapped_text.push('\n');
20671 current_line.truncate(line_prefix.len());
20672 current_line_len = line_prefix_len;
20673 } else if current_line_len != line_prefix_len {
20674 current_line.push(' ');
20675 current_line_len += 1;
20676 }
20677 }
20678 }
20679 }
20680
20681 if !current_line.is_empty() {
20682 wrapped_text.push_str(¤t_line);
20683 }
20684 wrapped_text
20685}
20686
20687#[test]
20688fn test_wrap_with_prefix() {
20689 assert_eq!(
20690 wrap_with_prefix(
20691 "# ".to_string(),
20692 "abcdefg".to_string(),
20693 4,
20694 NonZeroU32::new(4).unwrap(),
20695 false,
20696 ),
20697 "# abcdefg"
20698 );
20699 assert_eq!(
20700 wrap_with_prefix(
20701 "".to_string(),
20702 "\thello world".to_string(),
20703 8,
20704 NonZeroU32::new(4).unwrap(),
20705 false,
20706 ),
20707 "hello\nworld"
20708 );
20709 assert_eq!(
20710 wrap_with_prefix(
20711 "// ".to_string(),
20712 "xx \nyy zz aa bb cc".to_string(),
20713 12,
20714 NonZeroU32::new(4).unwrap(),
20715 false,
20716 ),
20717 "// xx yy zz\n// aa bb cc"
20718 );
20719 assert_eq!(
20720 wrap_with_prefix(
20721 String::new(),
20722 "这是什么 \n 钢笔".to_string(),
20723 3,
20724 NonZeroU32::new(4).unwrap(),
20725 false,
20726 ),
20727 "这是什\n么 钢\n笔"
20728 );
20729}
20730
20731pub trait CollaborationHub {
20732 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20733 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20734 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20735}
20736
20737impl CollaborationHub for Entity<Project> {
20738 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20739 self.read(cx).collaborators()
20740 }
20741
20742 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20743 self.read(cx).user_store().read(cx).participant_indices()
20744 }
20745
20746 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20747 let this = self.read(cx);
20748 let user_ids = this.collaborators().values().map(|c| c.user_id);
20749 this.user_store().read(cx).participant_names(user_ids, cx)
20750 }
20751}
20752
20753pub trait SemanticsProvider {
20754 fn hover(
20755 &self,
20756 buffer: &Entity<Buffer>,
20757 position: text::Anchor,
20758 cx: &mut App,
20759 ) -> Option<Task<Vec<project::Hover>>>;
20760
20761 fn inline_values(
20762 &self,
20763 buffer_handle: Entity<Buffer>,
20764 range: Range<text::Anchor>,
20765 cx: &mut App,
20766 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20767
20768 fn inlay_hints(
20769 &self,
20770 buffer_handle: Entity<Buffer>,
20771 range: Range<text::Anchor>,
20772 cx: &mut App,
20773 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20774
20775 fn resolve_inlay_hint(
20776 &self,
20777 hint: InlayHint,
20778 buffer_handle: Entity<Buffer>,
20779 server_id: LanguageServerId,
20780 cx: &mut App,
20781 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20782
20783 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20784
20785 fn document_highlights(
20786 &self,
20787 buffer: &Entity<Buffer>,
20788 position: text::Anchor,
20789 cx: &mut App,
20790 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20791
20792 fn definitions(
20793 &self,
20794 buffer: &Entity<Buffer>,
20795 position: text::Anchor,
20796 kind: GotoDefinitionKind,
20797 cx: &mut App,
20798 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20799
20800 fn range_for_rename(
20801 &self,
20802 buffer: &Entity<Buffer>,
20803 position: text::Anchor,
20804 cx: &mut App,
20805 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20806
20807 fn perform_rename(
20808 &self,
20809 buffer: &Entity<Buffer>,
20810 position: text::Anchor,
20811 new_name: String,
20812 cx: &mut App,
20813 ) -> Option<Task<Result<ProjectTransaction>>>;
20814
20815 fn pull_diagnostics_for_buffer(
20816 &self,
20817 buffer: Entity<Buffer>,
20818 cx: &mut App,
20819 ) -> Task<anyhow::Result<()>>;
20820}
20821
20822pub trait CompletionProvider {
20823 fn completions(
20824 &self,
20825 excerpt_id: ExcerptId,
20826 buffer: &Entity<Buffer>,
20827 buffer_position: text::Anchor,
20828 trigger: CompletionContext,
20829 window: &mut Window,
20830 cx: &mut Context<Editor>,
20831 ) -> Task<Result<Vec<CompletionResponse>>>;
20832
20833 fn resolve_completions(
20834 &self,
20835 _buffer: Entity<Buffer>,
20836 _completion_indices: Vec<usize>,
20837 _completions: Rc<RefCell<Box<[Completion]>>>,
20838 _cx: &mut Context<Editor>,
20839 ) -> Task<Result<bool>> {
20840 Task::ready(Ok(false))
20841 }
20842
20843 fn apply_additional_edits_for_completion(
20844 &self,
20845 _buffer: Entity<Buffer>,
20846 _completions: Rc<RefCell<Box<[Completion]>>>,
20847 _completion_index: usize,
20848 _push_to_history: bool,
20849 _cx: &mut Context<Editor>,
20850 ) -> Task<Result<Option<language::Transaction>>> {
20851 Task::ready(Ok(None))
20852 }
20853
20854 fn is_completion_trigger(
20855 &self,
20856 buffer: &Entity<Buffer>,
20857 position: language::Anchor,
20858 text: &str,
20859 trigger_in_words: bool,
20860 menu_is_open: bool,
20861 cx: &mut Context<Editor>,
20862 ) -> bool;
20863
20864 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20865
20866 fn sort_completions(&self) -> bool {
20867 true
20868 }
20869
20870 fn filter_completions(&self) -> bool {
20871 true
20872 }
20873}
20874
20875pub trait CodeActionProvider {
20876 fn id(&self) -> Arc<str>;
20877
20878 fn code_actions(
20879 &self,
20880 buffer: &Entity<Buffer>,
20881 range: Range<text::Anchor>,
20882 window: &mut Window,
20883 cx: &mut App,
20884 ) -> Task<Result<Vec<CodeAction>>>;
20885
20886 fn apply_code_action(
20887 &self,
20888 buffer_handle: Entity<Buffer>,
20889 action: CodeAction,
20890 excerpt_id: ExcerptId,
20891 push_to_history: bool,
20892 window: &mut Window,
20893 cx: &mut App,
20894 ) -> Task<Result<ProjectTransaction>>;
20895}
20896
20897impl CodeActionProvider for Entity<Project> {
20898 fn id(&self) -> Arc<str> {
20899 "project".into()
20900 }
20901
20902 fn code_actions(
20903 &self,
20904 buffer: &Entity<Buffer>,
20905 range: Range<text::Anchor>,
20906 _window: &mut Window,
20907 cx: &mut App,
20908 ) -> Task<Result<Vec<CodeAction>>> {
20909 self.update(cx, |project, cx| {
20910 let code_lens = project.code_lens(buffer, range.clone(), cx);
20911 let code_actions = project.code_actions(buffer, range, None, cx);
20912 cx.background_spawn(async move {
20913 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20914 Ok(code_lens
20915 .context("code lens fetch")?
20916 .into_iter()
20917 .chain(code_actions.context("code action fetch")?)
20918 .collect())
20919 })
20920 })
20921 }
20922
20923 fn apply_code_action(
20924 &self,
20925 buffer_handle: Entity<Buffer>,
20926 action: CodeAction,
20927 _excerpt_id: ExcerptId,
20928 push_to_history: bool,
20929 _window: &mut Window,
20930 cx: &mut App,
20931 ) -> Task<Result<ProjectTransaction>> {
20932 self.update(cx, |project, cx| {
20933 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20934 })
20935 }
20936}
20937
20938fn snippet_completions(
20939 project: &Project,
20940 buffer: &Entity<Buffer>,
20941 buffer_position: text::Anchor,
20942 cx: &mut App,
20943) -> Task<Result<CompletionResponse>> {
20944 let languages = buffer.read(cx).languages_at(buffer_position);
20945 let snippet_store = project.snippets().read(cx);
20946
20947 let scopes: Vec<_> = languages
20948 .iter()
20949 .filter_map(|language| {
20950 let language_name = language.lsp_id();
20951 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20952
20953 if snippets.is_empty() {
20954 None
20955 } else {
20956 Some((language.default_scope(), snippets))
20957 }
20958 })
20959 .collect();
20960
20961 if scopes.is_empty() {
20962 return Task::ready(Ok(CompletionResponse {
20963 completions: vec![],
20964 is_incomplete: false,
20965 }));
20966 }
20967
20968 let snapshot = buffer.read(cx).text_snapshot();
20969 let chars: String = snapshot
20970 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20971 .collect();
20972 let executor = cx.background_executor().clone();
20973
20974 cx.background_spawn(async move {
20975 let mut is_incomplete = false;
20976 let mut completions: Vec<Completion> = Vec::new();
20977 for (scope, snippets) in scopes.into_iter() {
20978 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20979 let mut last_word = chars
20980 .chars()
20981 .take_while(|c| classifier.is_word(*c))
20982 .collect::<String>();
20983 last_word = last_word.chars().rev().collect();
20984
20985 if last_word.is_empty() {
20986 return Ok(CompletionResponse {
20987 completions: vec![],
20988 is_incomplete: true,
20989 });
20990 }
20991
20992 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20993 let to_lsp = |point: &text::Anchor| {
20994 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20995 point_to_lsp(end)
20996 };
20997 let lsp_end = to_lsp(&buffer_position);
20998
20999 let candidates = snippets
21000 .iter()
21001 .enumerate()
21002 .flat_map(|(ix, snippet)| {
21003 snippet
21004 .prefix
21005 .iter()
21006 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21007 })
21008 .collect::<Vec<StringMatchCandidate>>();
21009
21010 const MAX_RESULTS: usize = 100;
21011 let mut matches = fuzzy::match_strings(
21012 &candidates,
21013 &last_word,
21014 last_word.chars().any(|c| c.is_uppercase()),
21015 MAX_RESULTS,
21016 &Default::default(),
21017 executor.clone(),
21018 )
21019 .await;
21020
21021 if matches.len() >= MAX_RESULTS {
21022 is_incomplete = true;
21023 }
21024
21025 // Remove all candidates where the query's start does not match the start of any word in the candidate
21026 if let Some(query_start) = last_word.chars().next() {
21027 matches.retain(|string_match| {
21028 split_words(&string_match.string).any(|word| {
21029 // Check that the first codepoint of the word as lowercase matches the first
21030 // codepoint of the query as lowercase
21031 word.chars()
21032 .flat_map(|codepoint| codepoint.to_lowercase())
21033 .zip(query_start.to_lowercase())
21034 .all(|(word_cp, query_cp)| word_cp == query_cp)
21035 })
21036 });
21037 }
21038
21039 let matched_strings = matches
21040 .into_iter()
21041 .map(|m| m.string)
21042 .collect::<HashSet<_>>();
21043
21044 completions.extend(snippets.iter().filter_map(|snippet| {
21045 let matching_prefix = snippet
21046 .prefix
21047 .iter()
21048 .find(|prefix| matched_strings.contains(*prefix))?;
21049 let start = as_offset - last_word.len();
21050 let start = snapshot.anchor_before(start);
21051 let range = start..buffer_position;
21052 let lsp_start = to_lsp(&start);
21053 let lsp_range = lsp::Range {
21054 start: lsp_start,
21055 end: lsp_end,
21056 };
21057 Some(Completion {
21058 replace_range: range,
21059 new_text: snippet.body.clone(),
21060 source: CompletionSource::Lsp {
21061 insert_range: None,
21062 server_id: LanguageServerId(usize::MAX),
21063 resolved: true,
21064 lsp_completion: Box::new(lsp::CompletionItem {
21065 label: snippet.prefix.first().unwrap().clone(),
21066 kind: Some(CompletionItemKind::SNIPPET),
21067 label_details: snippet.description.as_ref().map(|description| {
21068 lsp::CompletionItemLabelDetails {
21069 detail: Some(description.clone()),
21070 description: None,
21071 }
21072 }),
21073 insert_text_format: Some(InsertTextFormat::SNIPPET),
21074 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21075 lsp::InsertReplaceEdit {
21076 new_text: snippet.body.clone(),
21077 insert: lsp_range,
21078 replace: lsp_range,
21079 },
21080 )),
21081 filter_text: Some(snippet.body.clone()),
21082 sort_text: Some(char::MAX.to_string()),
21083 ..lsp::CompletionItem::default()
21084 }),
21085 lsp_defaults: None,
21086 },
21087 label: CodeLabel {
21088 text: matching_prefix.clone(),
21089 runs: Vec::new(),
21090 filter_range: 0..matching_prefix.len(),
21091 },
21092 icon_path: None,
21093 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21094 single_line: snippet.name.clone().into(),
21095 plain_text: snippet
21096 .description
21097 .clone()
21098 .map(|description| description.into()),
21099 }),
21100 insert_text_mode: None,
21101 confirm: None,
21102 })
21103 }))
21104 }
21105
21106 Ok(CompletionResponse {
21107 completions,
21108 is_incomplete,
21109 })
21110 })
21111}
21112
21113impl CompletionProvider for Entity<Project> {
21114 fn completions(
21115 &self,
21116 _excerpt_id: ExcerptId,
21117 buffer: &Entity<Buffer>,
21118 buffer_position: text::Anchor,
21119 options: CompletionContext,
21120 _window: &mut Window,
21121 cx: &mut Context<Editor>,
21122 ) -> Task<Result<Vec<CompletionResponse>>> {
21123 self.update(cx, |project, cx| {
21124 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21125 let project_completions = project.completions(buffer, buffer_position, options, cx);
21126 cx.background_spawn(async move {
21127 let mut responses = project_completions.await?;
21128 let snippets = snippets.await?;
21129 if !snippets.completions.is_empty() {
21130 responses.push(snippets);
21131 }
21132 Ok(responses)
21133 })
21134 })
21135 }
21136
21137 fn resolve_completions(
21138 &self,
21139 buffer: Entity<Buffer>,
21140 completion_indices: Vec<usize>,
21141 completions: Rc<RefCell<Box<[Completion]>>>,
21142 cx: &mut Context<Editor>,
21143 ) -> Task<Result<bool>> {
21144 self.update(cx, |project, cx| {
21145 project.lsp_store().update(cx, |lsp_store, cx| {
21146 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21147 })
21148 })
21149 }
21150
21151 fn apply_additional_edits_for_completion(
21152 &self,
21153 buffer: Entity<Buffer>,
21154 completions: Rc<RefCell<Box<[Completion]>>>,
21155 completion_index: usize,
21156 push_to_history: bool,
21157 cx: &mut Context<Editor>,
21158 ) -> Task<Result<Option<language::Transaction>>> {
21159 self.update(cx, |project, cx| {
21160 project.lsp_store().update(cx, |lsp_store, cx| {
21161 lsp_store.apply_additional_edits_for_completion(
21162 buffer,
21163 completions,
21164 completion_index,
21165 push_to_history,
21166 cx,
21167 )
21168 })
21169 })
21170 }
21171
21172 fn is_completion_trigger(
21173 &self,
21174 buffer: &Entity<Buffer>,
21175 position: language::Anchor,
21176 text: &str,
21177 trigger_in_words: bool,
21178 menu_is_open: bool,
21179 cx: &mut Context<Editor>,
21180 ) -> bool {
21181 let mut chars = text.chars();
21182 let char = if let Some(char) = chars.next() {
21183 char
21184 } else {
21185 return false;
21186 };
21187 if chars.next().is_some() {
21188 return false;
21189 }
21190
21191 let buffer = buffer.read(cx);
21192 let snapshot = buffer.snapshot();
21193 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21194 return false;
21195 }
21196 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21197 if trigger_in_words && classifier.is_word(char) {
21198 return true;
21199 }
21200
21201 buffer.completion_triggers().contains(text)
21202 }
21203}
21204
21205impl SemanticsProvider for Entity<Project> {
21206 fn hover(
21207 &self,
21208 buffer: &Entity<Buffer>,
21209 position: text::Anchor,
21210 cx: &mut App,
21211 ) -> Option<Task<Vec<project::Hover>>> {
21212 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21213 }
21214
21215 fn document_highlights(
21216 &self,
21217 buffer: &Entity<Buffer>,
21218 position: text::Anchor,
21219 cx: &mut App,
21220 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21221 Some(self.update(cx, |project, cx| {
21222 project.document_highlights(buffer, position, cx)
21223 }))
21224 }
21225
21226 fn definitions(
21227 &self,
21228 buffer: &Entity<Buffer>,
21229 position: text::Anchor,
21230 kind: GotoDefinitionKind,
21231 cx: &mut App,
21232 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21233 Some(self.update(cx, |project, cx| match kind {
21234 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21235 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21236 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21237 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21238 }))
21239 }
21240
21241 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21242 // TODO: make this work for remote projects
21243 self.update(cx, |project, cx| {
21244 if project
21245 .active_debug_session(cx)
21246 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21247 {
21248 return true;
21249 }
21250
21251 buffer.update(cx, |buffer, cx| {
21252 project.any_language_server_supports_inlay_hints(buffer, cx)
21253 })
21254 })
21255 }
21256
21257 fn inline_values(
21258 &self,
21259 buffer_handle: Entity<Buffer>,
21260
21261 range: Range<text::Anchor>,
21262 cx: &mut App,
21263 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21264 self.update(cx, |project, cx| {
21265 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21266
21267 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21268 })
21269 }
21270
21271 fn inlay_hints(
21272 &self,
21273 buffer_handle: Entity<Buffer>,
21274 range: Range<text::Anchor>,
21275 cx: &mut App,
21276 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21277 Some(self.update(cx, |project, cx| {
21278 project.inlay_hints(buffer_handle, range, cx)
21279 }))
21280 }
21281
21282 fn resolve_inlay_hint(
21283 &self,
21284 hint: InlayHint,
21285 buffer_handle: Entity<Buffer>,
21286 server_id: LanguageServerId,
21287 cx: &mut App,
21288 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21289 Some(self.update(cx, |project, cx| {
21290 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21291 }))
21292 }
21293
21294 fn range_for_rename(
21295 &self,
21296 buffer: &Entity<Buffer>,
21297 position: text::Anchor,
21298 cx: &mut App,
21299 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21300 Some(self.update(cx, |project, cx| {
21301 let buffer = buffer.clone();
21302 let task = project.prepare_rename(buffer.clone(), position, cx);
21303 cx.spawn(async move |_, cx| {
21304 Ok(match task.await? {
21305 PrepareRenameResponse::Success(range) => Some(range),
21306 PrepareRenameResponse::InvalidPosition => None,
21307 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21308 // Fallback on using TreeSitter info to determine identifier range
21309 buffer.read_with(cx, |buffer, _| {
21310 let snapshot = buffer.snapshot();
21311 let (range, kind) = snapshot.surrounding_word(position);
21312 if kind != Some(CharKind::Word) {
21313 return None;
21314 }
21315 Some(
21316 snapshot.anchor_before(range.start)
21317 ..snapshot.anchor_after(range.end),
21318 )
21319 })?
21320 }
21321 })
21322 })
21323 }))
21324 }
21325
21326 fn perform_rename(
21327 &self,
21328 buffer: &Entity<Buffer>,
21329 position: text::Anchor,
21330 new_name: String,
21331 cx: &mut App,
21332 ) -> Option<Task<Result<ProjectTransaction>>> {
21333 Some(self.update(cx, |project, cx| {
21334 project.perform_rename(buffer.clone(), position, new_name, cx)
21335 }))
21336 }
21337
21338 fn pull_diagnostics_for_buffer(
21339 &self,
21340 buffer: Entity<Buffer>,
21341 cx: &mut App,
21342 ) -> Task<anyhow::Result<()>> {
21343 let diagnostics = self.update(cx, |project, cx| {
21344 project
21345 .lsp_store()
21346 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21347 });
21348 let project = self.clone();
21349 cx.spawn(async move |cx| {
21350 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21351 project.update(cx, |project, cx| {
21352 project.lsp_store().update(cx, |lsp_store, cx| {
21353 for diagnostics_set in diagnostics {
21354 let LspPullDiagnostics::Response {
21355 server_id,
21356 uri,
21357 diagnostics,
21358 } = diagnostics_set
21359 else {
21360 continue;
21361 };
21362
21363 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21364 let disk_based_sources = adapter
21365 .as_ref()
21366 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21367 .unwrap_or(&[]);
21368 match diagnostics {
21369 PulledDiagnostics::Unchanged { result_id } => {
21370 lsp_store
21371 .merge_diagnostics(
21372 server_id,
21373 lsp::PublishDiagnosticsParams {
21374 uri: uri.clone(),
21375 diagnostics: Vec::new(),
21376 version: None,
21377 },
21378 Some(result_id),
21379 DiagnosticSourceKind::Pulled,
21380 disk_based_sources,
21381 |_, _| true,
21382 cx,
21383 )
21384 .log_err();
21385 }
21386 PulledDiagnostics::Changed {
21387 diagnostics,
21388 result_id,
21389 } => {
21390 lsp_store
21391 .merge_diagnostics(
21392 server_id,
21393 lsp::PublishDiagnosticsParams {
21394 uri: uri.clone(),
21395 diagnostics,
21396 version: None,
21397 },
21398 result_id,
21399 DiagnosticSourceKind::Pulled,
21400 disk_based_sources,
21401 |old_diagnostic, _| match old_diagnostic.source_kind {
21402 DiagnosticSourceKind::Pulled => false,
21403 DiagnosticSourceKind::Other
21404 | DiagnosticSourceKind::Pushed => true,
21405 },
21406 cx,
21407 )
21408 .log_err();
21409 }
21410 }
21411 }
21412 })
21413 })
21414 })
21415 }
21416}
21417
21418fn inlay_hint_settings(
21419 location: Anchor,
21420 snapshot: &MultiBufferSnapshot,
21421 cx: &mut Context<Editor>,
21422) -> InlayHintSettings {
21423 let file = snapshot.file_at(location);
21424 let language = snapshot.language_at(location).map(|l| l.name());
21425 language_settings(language, file, cx).inlay_hints
21426}
21427
21428fn consume_contiguous_rows(
21429 contiguous_row_selections: &mut Vec<Selection<Point>>,
21430 selection: &Selection<Point>,
21431 display_map: &DisplaySnapshot,
21432 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21433) -> (MultiBufferRow, MultiBufferRow) {
21434 contiguous_row_selections.push(selection.clone());
21435 let start_row = MultiBufferRow(selection.start.row);
21436 let mut end_row = ending_row(selection, display_map);
21437
21438 while let Some(next_selection) = selections.peek() {
21439 if next_selection.start.row <= end_row.0 {
21440 end_row = ending_row(next_selection, display_map);
21441 contiguous_row_selections.push(selections.next().unwrap().clone());
21442 } else {
21443 break;
21444 }
21445 }
21446 (start_row, end_row)
21447}
21448
21449fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21450 if next_selection.end.column > 0 || next_selection.is_empty() {
21451 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21452 } else {
21453 MultiBufferRow(next_selection.end.row)
21454 }
21455}
21456
21457impl EditorSnapshot {
21458 pub fn remote_selections_in_range<'a>(
21459 &'a self,
21460 range: &'a Range<Anchor>,
21461 collaboration_hub: &dyn CollaborationHub,
21462 cx: &'a App,
21463 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21464 let participant_names = collaboration_hub.user_names(cx);
21465 let participant_indices = collaboration_hub.user_participant_indices(cx);
21466 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21467 let collaborators_by_replica_id = collaborators_by_peer_id
21468 .values()
21469 .map(|collaborator| (collaborator.replica_id, collaborator))
21470 .collect::<HashMap<_, _>>();
21471 self.buffer_snapshot
21472 .selections_in_range(range, false)
21473 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21474 if replica_id == AGENT_REPLICA_ID {
21475 Some(RemoteSelection {
21476 replica_id,
21477 selection,
21478 cursor_shape,
21479 line_mode,
21480 collaborator_id: CollaboratorId::Agent,
21481 user_name: Some("Agent".into()),
21482 color: cx.theme().players().agent(),
21483 })
21484 } else {
21485 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21486 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21487 let user_name = participant_names.get(&collaborator.user_id).cloned();
21488 Some(RemoteSelection {
21489 replica_id,
21490 selection,
21491 cursor_shape,
21492 line_mode,
21493 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21494 user_name,
21495 color: if let Some(index) = participant_index {
21496 cx.theme().players().color_for_participant(index.0)
21497 } else {
21498 cx.theme().players().absent()
21499 },
21500 })
21501 }
21502 })
21503 }
21504
21505 pub fn hunks_for_ranges(
21506 &self,
21507 ranges: impl IntoIterator<Item = Range<Point>>,
21508 ) -> Vec<MultiBufferDiffHunk> {
21509 let mut hunks = Vec::new();
21510 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21511 HashMap::default();
21512 for query_range in ranges {
21513 let query_rows =
21514 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21515 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21516 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21517 ) {
21518 // Include deleted hunks that are adjacent to the query range, because
21519 // otherwise they would be missed.
21520 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21521 if hunk.status().is_deleted() {
21522 intersects_range |= hunk.row_range.start == query_rows.end;
21523 intersects_range |= hunk.row_range.end == query_rows.start;
21524 }
21525 if intersects_range {
21526 if !processed_buffer_rows
21527 .entry(hunk.buffer_id)
21528 .or_default()
21529 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21530 {
21531 continue;
21532 }
21533 hunks.push(hunk);
21534 }
21535 }
21536 }
21537
21538 hunks
21539 }
21540
21541 fn display_diff_hunks_for_rows<'a>(
21542 &'a self,
21543 display_rows: Range<DisplayRow>,
21544 folded_buffers: &'a HashSet<BufferId>,
21545 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21546 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21547 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21548
21549 self.buffer_snapshot
21550 .diff_hunks_in_range(buffer_start..buffer_end)
21551 .filter_map(|hunk| {
21552 if folded_buffers.contains(&hunk.buffer_id) {
21553 return None;
21554 }
21555
21556 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21557 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21558
21559 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21560 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21561
21562 let display_hunk = if hunk_display_start.column() != 0 {
21563 DisplayDiffHunk::Folded {
21564 display_row: hunk_display_start.row(),
21565 }
21566 } else {
21567 let mut end_row = hunk_display_end.row();
21568 if hunk_display_end.column() > 0 {
21569 end_row.0 += 1;
21570 }
21571 let is_created_file = hunk.is_created_file();
21572 DisplayDiffHunk::Unfolded {
21573 status: hunk.status(),
21574 diff_base_byte_range: hunk.diff_base_byte_range,
21575 display_row_range: hunk_display_start.row()..end_row,
21576 multi_buffer_range: Anchor::range_in_buffer(
21577 hunk.excerpt_id,
21578 hunk.buffer_id,
21579 hunk.buffer_range,
21580 ),
21581 is_created_file,
21582 }
21583 };
21584
21585 Some(display_hunk)
21586 })
21587 }
21588
21589 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21590 self.display_snapshot.buffer_snapshot.language_at(position)
21591 }
21592
21593 pub fn is_focused(&self) -> bool {
21594 self.is_focused
21595 }
21596
21597 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21598 self.placeholder_text.as_ref()
21599 }
21600
21601 pub fn scroll_position(&self) -> gpui::Point<f32> {
21602 self.scroll_anchor.scroll_position(&self.display_snapshot)
21603 }
21604
21605 fn gutter_dimensions(
21606 &self,
21607 font_id: FontId,
21608 font_size: Pixels,
21609 max_line_number_width: Pixels,
21610 cx: &App,
21611 ) -> Option<GutterDimensions> {
21612 if !self.show_gutter {
21613 return None;
21614 }
21615
21616 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21617 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21618
21619 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21620 matches!(
21621 ProjectSettings::get_global(cx).git.git_gutter,
21622 Some(GitGutterSetting::TrackedFiles)
21623 )
21624 });
21625 let gutter_settings = EditorSettings::get_global(cx).gutter;
21626 let show_line_numbers = self
21627 .show_line_numbers
21628 .unwrap_or(gutter_settings.line_numbers);
21629 let line_gutter_width = if show_line_numbers {
21630 // Avoid flicker-like gutter resizes when the line number gains another digit by
21631 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21632 let min_width_for_number_on_gutter =
21633 ch_advance * gutter_settings.min_line_number_digits as f32;
21634 max_line_number_width.max(min_width_for_number_on_gutter)
21635 } else {
21636 0.0.into()
21637 };
21638
21639 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21640 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21641
21642 let git_blame_entries_width =
21643 self.git_blame_gutter_max_author_length
21644 .map(|max_author_length| {
21645 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21646 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21647
21648 /// The number of characters to dedicate to gaps and margins.
21649 const SPACING_WIDTH: usize = 4;
21650
21651 let max_char_count = max_author_length.min(renderer.max_author_length())
21652 + ::git::SHORT_SHA_LENGTH
21653 + MAX_RELATIVE_TIMESTAMP.len()
21654 + SPACING_WIDTH;
21655
21656 ch_advance * max_char_count
21657 });
21658
21659 let is_singleton = self.buffer_snapshot.is_singleton();
21660
21661 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21662 left_padding += if !is_singleton {
21663 ch_width * 4.0
21664 } else if show_runnables || show_breakpoints {
21665 ch_width * 3.0
21666 } else if show_git_gutter && show_line_numbers {
21667 ch_width * 2.0
21668 } else if show_git_gutter || show_line_numbers {
21669 ch_width
21670 } else {
21671 px(0.)
21672 };
21673
21674 let shows_folds = is_singleton && gutter_settings.folds;
21675
21676 let right_padding = if shows_folds && show_line_numbers {
21677 ch_width * 4.0
21678 } else if shows_folds || (!is_singleton && show_line_numbers) {
21679 ch_width * 3.0
21680 } else if show_line_numbers {
21681 ch_width
21682 } else {
21683 px(0.)
21684 };
21685
21686 Some(GutterDimensions {
21687 left_padding,
21688 right_padding,
21689 width: line_gutter_width + left_padding + right_padding,
21690 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21691 git_blame_entries_width,
21692 })
21693 }
21694
21695 pub fn render_crease_toggle(
21696 &self,
21697 buffer_row: MultiBufferRow,
21698 row_contains_cursor: bool,
21699 editor: Entity<Editor>,
21700 window: &mut Window,
21701 cx: &mut App,
21702 ) -> Option<AnyElement> {
21703 let folded = self.is_line_folded(buffer_row);
21704 let mut is_foldable = false;
21705
21706 if let Some(crease) = self
21707 .crease_snapshot
21708 .query_row(buffer_row, &self.buffer_snapshot)
21709 {
21710 is_foldable = true;
21711 match crease {
21712 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21713 if let Some(render_toggle) = render_toggle {
21714 let toggle_callback =
21715 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21716 if folded {
21717 editor.update(cx, |editor, cx| {
21718 editor.fold_at(buffer_row, window, cx)
21719 });
21720 } else {
21721 editor.update(cx, |editor, cx| {
21722 editor.unfold_at(buffer_row, window, cx)
21723 });
21724 }
21725 });
21726 return Some((render_toggle)(
21727 buffer_row,
21728 folded,
21729 toggle_callback,
21730 window,
21731 cx,
21732 ));
21733 }
21734 }
21735 }
21736 }
21737
21738 is_foldable |= self.starts_indent(buffer_row);
21739
21740 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21741 Some(
21742 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21743 .toggle_state(folded)
21744 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21745 if folded {
21746 this.unfold_at(buffer_row, window, cx);
21747 } else {
21748 this.fold_at(buffer_row, window, cx);
21749 }
21750 }))
21751 .into_any_element(),
21752 )
21753 } else {
21754 None
21755 }
21756 }
21757
21758 pub fn render_crease_trailer(
21759 &self,
21760 buffer_row: MultiBufferRow,
21761 window: &mut Window,
21762 cx: &mut App,
21763 ) -> Option<AnyElement> {
21764 let folded = self.is_line_folded(buffer_row);
21765 if let Crease::Inline { render_trailer, .. } = self
21766 .crease_snapshot
21767 .query_row(buffer_row, &self.buffer_snapshot)?
21768 {
21769 let render_trailer = render_trailer.as_ref()?;
21770 Some(render_trailer(buffer_row, folded, window, cx))
21771 } else {
21772 None
21773 }
21774 }
21775}
21776
21777impl Deref for EditorSnapshot {
21778 type Target = DisplaySnapshot;
21779
21780 fn deref(&self) -> &Self::Target {
21781 &self.display_snapshot
21782 }
21783}
21784
21785#[derive(Clone, Debug, PartialEq, Eq)]
21786pub enum EditorEvent {
21787 InputIgnored {
21788 text: Arc<str>,
21789 },
21790 InputHandled {
21791 utf16_range_to_replace: Option<Range<isize>>,
21792 text: Arc<str>,
21793 },
21794 ExcerptsAdded {
21795 buffer: Entity<Buffer>,
21796 predecessor: ExcerptId,
21797 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21798 },
21799 ExcerptsRemoved {
21800 ids: Vec<ExcerptId>,
21801 removed_buffer_ids: Vec<BufferId>,
21802 },
21803 BufferFoldToggled {
21804 ids: Vec<ExcerptId>,
21805 folded: bool,
21806 },
21807 ExcerptsEdited {
21808 ids: Vec<ExcerptId>,
21809 },
21810 ExcerptsExpanded {
21811 ids: Vec<ExcerptId>,
21812 },
21813 BufferEdited,
21814 Edited {
21815 transaction_id: clock::Lamport,
21816 },
21817 Reparsed(BufferId),
21818 Focused,
21819 FocusedIn,
21820 Blurred,
21821 DirtyChanged,
21822 Saved,
21823 TitleChanged,
21824 DiffBaseChanged,
21825 SelectionsChanged {
21826 local: bool,
21827 },
21828 ScrollPositionChanged {
21829 local: bool,
21830 autoscroll: bool,
21831 },
21832 Closed,
21833 TransactionUndone {
21834 transaction_id: clock::Lamport,
21835 },
21836 TransactionBegun {
21837 transaction_id: clock::Lamport,
21838 },
21839 Reloaded,
21840 CursorShapeChanged,
21841 PushedToNavHistory {
21842 anchor: Anchor,
21843 is_deactivate: bool,
21844 },
21845}
21846
21847impl EventEmitter<EditorEvent> for Editor {}
21848
21849impl Focusable for Editor {
21850 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21851 self.focus_handle.clone()
21852 }
21853}
21854
21855impl Render for Editor {
21856 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21857 let settings = ThemeSettings::get_global(cx);
21858
21859 let mut text_style = match self.mode {
21860 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21861 color: cx.theme().colors().editor_foreground,
21862 font_family: settings.ui_font.family.clone(),
21863 font_features: settings.ui_font.features.clone(),
21864 font_fallbacks: settings.ui_font.fallbacks.clone(),
21865 font_size: rems(0.875).into(),
21866 font_weight: settings.ui_font.weight,
21867 line_height: relative(settings.buffer_line_height.value()),
21868 ..Default::default()
21869 },
21870 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21871 color: cx.theme().colors().editor_foreground,
21872 font_family: settings.buffer_font.family.clone(),
21873 font_features: settings.buffer_font.features.clone(),
21874 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21875 font_size: settings.buffer_font_size(cx).into(),
21876 font_weight: settings.buffer_font.weight,
21877 line_height: relative(settings.buffer_line_height.value()),
21878 ..Default::default()
21879 },
21880 };
21881 if let Some(text_style_refinement) = &self.text_style_refinement {
21882 text_style.refine(text_style_refinement)
21883 }
21884
21885 let background = match self.mode {
21886 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21887 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21888 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21889 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21890 };
21891
21892 EditorElement::new(
21893 &cx.entity(),
21894 EditorStyle {
21895 background,
21896 local_player: cx.theme().players().local(),
21897 text: text_style,
21898 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21899 syntax: cx.theme().syntax().clone(),
21900 status: cx.theme().status().clone(),
21901 inlay_hints_style: make_inlay_hints_style(cx),
21902 inline_completion_styles: make_suggestion_styles(cx),
21903 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21904 show_underlines: !self.mode.is_minimap(),
21905 },
21906 )
21907 }
21908}
21909
21910impl EntityInputHandler for Editor {
21911 fn text_for_range(
21912 &mut self,
21913 range_utf16: Range<usize>,
21914 adjusted_range: &mut Option<Range<usize>>,
21915 _: &mut Window,
21916 cx: &mut Context<Self>,
21917 ) -> Option<String> {
21918 let snapshot = self.buffer.read(cx).read(cx);
21919 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21920 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21921 if (start.0..end.0) != range_utf16 {
21922 adjusted_range.replace(start.0..end.0);
21923 }
21924 Some(snapshot.text_for_range(start..end).collect())
21925 }
21926
21927 fn selected_text_range(
21928 &mut self,
21929 ignore_disabled_input: bool,
21930 _: &mut Window,
21931 cx: &mut Context<Self>,
21932 ) -> Option<UTF16Selection> {
21933 // Prevent the IME menu from appearing when holding down an alphabetic key
21934 // while input is disabled.
21935 if !ignore_disabled_input && !self.input_enabled {
21936 return None;
21937 }
21938
21939 let selection = self.selections.newest::<OffsetUtf16>(cx);
21940 let range = selection.range();
21941
21942 Some(UTF16Selection {
21943 range: range.start.0..range.end.0,
21944 reversed: selection.reversed,
21945 })
21946 }
21947
21948 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21949 let snapshot = self.buffer.read(cx).read(cx);
21950 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21951 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21952 }
21953
21954 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21955 self.clear_highlights::<InputComposition>(cx);
21956 self.ime_transaction.take();
21957 }
21958
21959 fn replace_text_in_range(
21960 &mut self,
21961 range_utf16: Option<Range<usize>>,
21962 text: &str,
21963 window: &mut Window,
21964 cx: &mut Context<Self>,
21965 ) {
21966 if !self.input_enabled {
21967 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21968 return;
21969 }
21970
21971 self.transact(window, cx, |this, window, cx| {
21972 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21973 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21974 Some(this.selection_replacement_ranges(range_utf16, cx))
21975 } else {
21976 this.marked_text_ranges(cx)
21977 };
21978
21979 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21980 let newest_selection_id = this.selections.newest_anchor().id;
21981 this.selections
21982 .all::<OffsetUtf16>(cx)
21983 .iter()
21984 .zip(ranges_to_replace.iter())
21985 .find_map(|(selection, range)| {
21986 if selection.id == newest_selection_id {
21987 Some(
21988 (range.start.0 as isize - selection.head().0 as isize)
21989 ..(range.end.0 as isize - selection.head().0 as isize),
21990 )
21991 } else {
21992 None
21993 }
21994 })
21995 });
21996
21997 cx.emit(EditorEvent::InputHandled {
21998 utf16_range_to_replace: range_to_replace,
21999 text: text.into(),
22000 });
22001
22002 if let Some(new_selected_ranges) = new_selected_ranges {
22003 this.change_selections(None, window, cx, |selections| {
22004 selections.select_ranges(new_selected_ranges)
22005 });
22006 this.backspace(&Default::default(), window, cx);
22007 }
22008
22009 this.handle_input(text, window, cx);
22010 });
22011
22012 if let Some(transaction) = self.ime_transaction {
22013 self.buffer.update(cx, |buffer, cx| {
22014 buffer.group_until_transaction(transaction, cx);
22015 });
22016 }
22017
22018 self.unmark_text(window, cx);
22019 }
22020
22021 fn replace_and_mark_text_in_range(
22022 &mut self,
22023 range_utf16: Option<Range<usize>>,
22024 text: &str,
22025 new_selected_range_utf16: Option<Range<usize>>,
22026 window: &mut Window,
22027 cx: &mut Context<Self>,
22028 ) {
22029 if !self.input_enabled {
22030 return;
22031 }
22032
22033 let transaction = self.transact(window, cx, |this, window, cx| {
22034 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22035 let snapshot = this.buffer.read(cx).read(cx);
22036 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22037 for marked_range in &mut marked_ranges {
22038 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22039 marked_range.start.0 += relative_range_utf16.start;
22040 marked_range.start =
22041 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22042 marked_range.end =
22043 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22044 }
22045 }
22046 Some(marked_ranges)
22047 } else if let Some(range_utf16) = range_utf16 {
22048 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22049 Some(this.selection_replacement_ranges(range_utf16, cx))
22050 } else {
22051 None
22052 };
22053
22054 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22055 let newest_selection_id = this.selections.newest_anchor().id;
22056 this.selections
22057 .all::<OffsetUtf16>(cx)
22058 .iter()
22059 .zip(ranges_to_replace.iter())
22060 .find_map(|(selection, range)| {
22061 if selection.id == newest_selection_id {
22062 Some(
22063 (range.start.0 as isize - selection.head().0 as isize)
22064 ..(range.end.0 as isize - selection.head().0 as isize),
22065 )
22066 } else {
22067 None
22068 }
22069 })
22070 });
22071
22072 cx.emit(EditorEvent::InputHandled {
22073 utf16_range_to_replace: range_to_replace,
22074 text: text.into(),
22075 });
22076
22077 if let Some(ranges) = ranges_to_replace {
22078 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22079 }
22080
22081 let marked_ranges = {
22082 let snapshot = this.buffer.read(cx).read(cx);
22083 this.selections
22084 .disjoint_anchors()
22085 .iter()
22086 .map(|selection| {
22087 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22088 })
22089 .collect::<Vec<_>>()
22090 };
22091
22092 if text.is_empty() {
22093 this.unmark_text(window, cx);
22094 } else {
22095 this.highlight_text::<InputComposition>(
22096 marked_ranges.clone(),
22097 HighlightStyle {
22098 underline: Some(UnderlineStyle {
22099 thickness: px(1.),
22100 color: None,
22101 wavy: false,
22102 }),
22103 ..Default::default()
22104 },
22105 cx,
22106 );
22107 }
22108
22109 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22110 let use_autoclose = this.use_autoclose;
22111 let use_auto_surround = this.use_auto_surround;
22112 this.set_use_autoclose(false);
22113 this.set_use_auto_surround(false);
22114 this.handle_input(text, window, cx);
22115 this.set_use_autoclose(use_autoclose);
22116 this.set_use_auto_surround(use_auto_surround);
22117
22118 if let Some(new_selected_range) = new_selected_range_utf16 {
22119 let snapshot = this.buffer.read(cx).read(cx);
22120 let new_selected_ranges = marked_ranges
22121 .into_iter()
22122 .map(|marked_range| {
22123 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22124 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22125 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22126 snapshot.clip_offset_utf16(new_start, Bias::Left)
22127 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22128 })
22129 .collect::<Vec<_>>();
22130
22131 drop(snapshot);
22132 this.change_selections(None, window, cx, |selections| {
22133 selections.select_ranges(new_selected_ranges)
22134 });
22135 }
22136 });
22137
22138 self.ime_transaction = self.ime_transaction.or(transaction);
22139 if let Some(transaction) = self.ime_transaction {
22140 self.buffer.update(cx, |buffer, cx| {
22141 buffer.group_until_transaction(transaction, cx);
22142 });
22143 }
22144
22145 if self.text_highlights::<InputComposition>(cx).is_none() {
22146 self.ime_transaction.take();
22147 }
22148 }
22149
22150 fn bounds_for_range(
22151 &mut self,
22152 range_utf16: Range<usize>,
22153 element_bounds: gpui::Bounds<Pixels>,
22154 window: &mut Window,
22155 cx: &mut Context<Self>,
22156 ) -> Option<gpui::Bounds<Pixels>> {
22157 let text_layout_details = self.text_layout_details(window);
22158 let gpui::Size {
22159 width: em_width,
22160 height: line_height,
22161 } = self.character_size(window);
22162
22163 let snapshot = self.snapshot(window, cx);
22164 let scroll_position = snapshot.scroll_position();
22165 let scroll_left = scroll_position.x * em_width;
22166
22167 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22168 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22169 + self.gutter_dimensions.width
22170 + self.gutter_dimensions.margin;
22171 let y = line_height * (start.row().as_f32() - scroll_position.y);
22172
22173 Some(Bounds {
22174 origin: element_bounds.origin + point(x, y),
22175 size: size(em_width, line_height),
22176 })
22177 }
22178
22179 fn character_index_for_point(
22180 &mut self,
22181 point: gpui::Point<Pixels>,
22182 _window: &mut Window,
22183 _cx: &mut Context<Self>,
22184 ) -> Option<usize> {
22185 let position_map = self.last_position_map.as_ref()?;
22186 if !position_map.text_hitbox.contains(&point) {
22187 return None;
22188 }
22189 let display_point = position_map.point_for_position(point).previous_valid;
22190 let anchor = position_map
22191 .snapshot
22192 .display_point_to_anchor(display_point, Bias::Left);
22193 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22194 Some(utf16_offset.0)
22195 }
22196}
22197
22198trait SelectionExt {
22199 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22200 fn spanned_rows(
22201 &self,
22202 include_end_if_at_line_start: bool,
22203 map: &DisplaySnapshot,
22204 ) -> Range<MultiBufferRow>;
22205}
22206
22207impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22208 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22209 let start = self
22210 .start
22211 .to_point(&map.buffer_snapshot)
22212 .to_display_point(map);
22213 let end = self
22214 .end
22215 .to_point(&map.buffer_snapshot)
22216 .to_display_point(map);
22217 if self.reversed {
22218 end..start
22219 } else {
22220 start..end
22221 }
22222 }
22223
22224 fn spanned_rows(
22225 &self,
22226 include_end_if_at_line_start: bool,
22227 map: &DisplaySnapshot,
22228 ) -> Range<MultiBufferRow> {
22229 let start = self.start.to_point(&map.buffer_snapshot);
22230 let mut end = self.end.to_point(&map.buffer_snapshot);
22231 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22232 end.row -= 1;
22233 }
22234
22235 let buffer_start = map.prev_line_boundary(start).0;
22236 let buffer_end = map.next_line_boundary(end).0;
22237 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22238 }
22239}
22240
22241impl<T: InvalidationRegion> InvalidationStack<T> {
22242 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22243 where
22244 S: Clone + ToOffset,
22245 {
22246 while let Some(region) = self.last() {
22247 let all_selections_inside_invalidation_ranges =
22248 if selections.len() == region.ranges().len() {
22249 selections
22250 .iter()
22251 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22252 .all(|(selection, invalidation_range)| {
22253 let head = selection.head().to_offset(buffer);
22254 invalidation_range.start <= head && invalidation_range.end >= head
22255 })
22256 } else {
22257 false
22258 };
22259
22260 if all_selections_inside_invalidation_ranges {
22261 break;
22262 } else {
22263 self.pop();
22264 }
22265 }
22266 }
22267}
22268
22269impl<T> Default for InvalidationStack<T> {
22270 fn default() -> Self {
22271 Self(Default::default())
22272 }
22273}
22274
22275impl<T> Deref for InvalidationStack<T> {
22276 type Target = Vec<T>;
22277
22278 fn deref(&self) -> &Self::Target {
22279 &self.0
22280 }
22281}
22282
22283impl<T> DerefMut for InvalidationStack<T> {
22284 fn deref_mut(&mut self) -> &mut Self::Target {
22285 &mut self.0
22286 }
22287}
22288
22289impl InvalidationRegion for SnippetState {
22290 fn ranges(&self) -> &[Range<Anchor>] {
22291 &self.ranges[self.active_index]
22292 }
22293}
22294
22295fn inline_completion_edit_text(
22296 current_snapshot: &BufferSnapshot,
22297 edits: &[(Range<Anchor>, String)],
22298 edit_preview: &EditPreview,
22299 include_deletions: bool,
22300 cx: &App,
22301) -> HighlightedText {
22302 let edits = edits
22303 .iter()
22304 .map(|(anchor, text)| {
22305 (
22306 anchor.start.text_anchor..anchor.end.text_anchor,
22307 text.clone(),
22308 )
22309 })
22310 .collect::<Vec<_>>();
22311
22312 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22313}
22314
22315pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22316 match severity {
22317 lsp::DiagnosticSeverity::ERROR => colors.error,
22318 lsp::DiagnosticSeverity::WARNING => colors.warning,
22319 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22320 lsp::DiagnosticSeverity::HINT => colors.info,
22321 _ => colors.ignored,
22322 }
22323}
22324
22325pub fn styled_runs_for_code_label<'a>(
22326 label: &'a CodeLabel,
22327 syntax_theme: &'a theme::SyntaxTheme,
22328) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22329 let fade_out = HighlightStyle {
22330 fade_out: Some(0.35),
22331 ..Default::default()
22332 };
22333
22334 let mut prev_end = label.filter_range.end;
22335 label
22336 .runs
22337 .iter()
22338 .enumerate()
22339 .flat_map(move |(ix, (range, highlight_id))| {
22340 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22341 style
22342 } else {
22343 return Default::default();
22344 };
22345 let mut muted_style = style;
22346 muted_style.highlight(fade_out);
22347
22348 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22349 if range.start >= label.filter_range.end {
22350 if range.start > prev_end {
22351 runs.push((prev_end..range.start, fade_out));
22352 }
22353 runs.push((range.clone(), muted_style));
22354 } else if range.end <= label.filter_range.end {
22355 runs.push((range.clone(), style));
22356 } else {
22357 runs.push((range.start..label.filter_range.end, style));
22358 runs.push((label.filter_range.end..range.end, muted_style));
22359 }
22360 prev_end = cmp::max(prev_end, range.end);
22361
22362 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22363 runs.push((prev_end..label.text.len(), fade_out));
22364 }
22365
22366 runs
22367 })
22368}
22369
22370pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22371 let mut prev_index = 0;
22372 let mut prev_codepoint: Option<char> = None;
22373 text.char_indices()
22374 .chain([(text.len(), '\0')])
22375 .filter_map(move |(index, codepoint)| {
22376 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22377 let is_boundary = index == text.len()
22378 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22379 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22380 if is_boundary {
22381 let chunk = &text[prev_index..index];
22382 prev_index = index;
22383 Some(chunk)
22384 } else {
22385 None
22386 }
22387 })
22388}
22389
22390pub trait RangeToAnchorExt: Sized {
22391 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22392
22393 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22394 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22395 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22396 }
22397}
22398
22399impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22400 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22401 let start_offset = self.start.to_offset(snapshot);
22402 let end_offset = self.end.to_offset(snapshot);
22403 if start_offset == end_offset {
22404 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22405 } else {
22406 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22407 }
22408 }
22409}
22410
22411pub trait RowExt {
22412 fn as_f32(&self) -> f32;
22413
22414 fn next_row(&self) -> Self;
22415
22416 fn previous_row(&self) -> Self;
22417
22418 fn minus(&self, other: Self) -> u32;
22419}
22420
22421impl RowExt for DisplayRow {
22422 fn as_f32(&self) -> f32 {
22423 self.0 as f32
22424 }
22425
22426 fn next_row(&self) -> Self {
22427 Self(self.0 + 1)
22428 }
22429
22430 fn previous_row(&self) -> Self {
22431 Self(self.0.saturating_sub(1))
22432 }
22433
22434 fn minus(&self, other: Self) -> u32 {
22435 self.0 - other.0
22436 }
22437}
22438
22439impl RowExt for MultiBufferRow {
22440 fn as_f32(&self) -> f32 {
22441 self.0 as f32
22442 }
22443
22444 fn next_row(&self) -> Self {
22445 Self(self.0 + 1)
22446 }
22447
22448 fn previous_row(&self) -> Self {
22449 Self(self.0.saturating_sub(1))
22450 }
22451
22452 fn minus(&self, other: Self) -> u32 {
22453 self.0 - other.0
22454 }
22455}
22456
22457trait RowRangeExt {
22458 type Row;
22459
22460 fn len(&self) -> usize;
22461
22462 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22463}
22464
22465impl RowRangeExt for Range<MultiBufferRow> {
22466 type Row = MultiBufferRow;
22467
22468 fn len(&self) -> usize {
22469 (self.end.0 - self.start.0) as usize
22470 }
22471
22472 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22473 (self.start.0..self.end.0).map(MultiBufferRow)
22474 }
22475}
22476
22477impl RowRangeExt for Range<DisplayRow> {
22478 type Row = DisplayRow;
22479
22480 fn len(&self) -> usize {
22481 (self.end.0 - self.start.0) as usize
22482 }
22483
22484 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22485 (self.start.0..self.end.0).map(DisplayRow)
22486 }
22487}
22488
22489/// If select range has more than one line, we
22490/// just point the cursor to range.start.
22491fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22492 if range.start.row == range.end.row {
22493 range
22494 } else {
22495 range.start..range.start
22496 }
22497}
22498pub struct KillRing(ClipboardItem);
22499impl Global for KillRing {}
22500
22501const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22502
22503enum BreakpointPromptEditAction {
22504 Log,
22505 Condition,
22506 HitCondition,
22507}
22508
22509struct BreakpointPromptEditor {
22510 pub(crate) prompt: Entity<Editor>,
22511 editor: WeakEntity<Editor>,
22512 breakpoint_anchor: Anchor,
22513 breakpoint: Breakpoint,
22514 edit_action: BreakpointPromptEditAction,
22515 block_ids: HashSet<CustomBlockId>,
22516 editor_margins: Arc<Mutex<EditorMargins>>,
22517 _subscriptions: Vec<Subscription>,
22518}
22519
22520impl BreakpointPromptEditor {
22521 const MAX_LINES: u8 = 4;
22522
22523 fn new(
22524 editor: WeakEntity<Editor>,
22525 breakpoint_anchor: Anchor,
22526 breakpoint: Breakpoint,
22527 edit_action: BreakpointPromptEditAction,
22528 window: &mut Window,
22529 cx: &mut Context<Self>,
22530 ) -> Self {
22531 let base_text = match edit_action {
22532 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22533 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22534 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22535 }
22536 .map(|msg| msg.to_string())
22537 .unwrap_or_default();
22538
22539 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22540 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22541
22542 let prompt = cx.new(|cx| {
22543 let mut prompt = Editor::new(
22544 EditorMode::AutoHeight {
22545 max_lines: Self::MAX_LINES as usize,
22546 },
22547 buffer,
22548 None,
22549 window,
22550 cx,
22551 );
22552 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22553 prompt.set_show_cursor_when_unfocused(false, cx);
22554 prompt.set_placeholder_text(
22555 match edit_action {
22556 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22557 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22558 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22559 },
22560 cx,
22561 );
22562
22563 prompt
22564 });
22565
22566 Self {
22567 prompt,
22568 editor,
22569 breakpoint_anchor,
22570 breakpoint,
22571 edit_action,
22572 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22573 block_ids: Default::default(),
22574 _subscriptions: vec![],
22575 }
22576 }
22577
22578 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22579 self.block_ids.extend(block_ids)
22580 }
22581
22582 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22583 if let Some(editor) = self.editor.upgrade() {
22584 let message = self
22585 .prompt
22586 .read(cx)
22587 .buffer
22588 .read(cx)
22589 .as_singleton()
22590 .expect("A multi buffer in breakpoint prompt isn't possible")
22591 .read(cx)
22592 .as_rope()
22593 .to_string();
22594
22595 editor.update(cx, |editor, cx| {
22596 editor.edit_breakpoint_at_anchor(
22597 self.breakpoint_anchor,
22598 self.breakpoint.clone(),
22599 match self.edit_action {
22600 BreakpointPromptEditAction::Log => {
22601 BreakpointEditAction::EditLogMessage(message.into())
22602 }
22603 BreakpointPromptEditAction::Condition => {
22604 BreakpointEditAction::EditCondition(message.into())
22605 }
22606 BreakpointPromptEditAction::HitCondition => {
22607 BreakpointEditAction::EditHitCondition(message.into())
22608 }
22609 },
22610 cx,
22611 );
22612
22613 editor.remove_blocks(self.block_ids.clone(), None, cx);
22614 cx.focus_self(window);
22615 });
22616 }
22617 }
22618
22619 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22620 self.editor
22621 .update(cx, |editor, cx| {
22622 editor.remove_blocks(self.block_ids.clone(), None, cx);
22623 window.focus(&editor.focus_handle);
22624 })
22625 .log_err();
22626 }
22627
22628 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22629 let settings = ThemeSettings::get_global(cx);
22630 let text_style = TextStyle {
22631 color: if self.prompt.read(cx).read_only(cx) {
22632 cx.theme().colors().text_disabled
22633 } else {
22634 cx.theme().colors().text
22635 },
22636 font_family: settings.buffer_font.family.clone(),
22637 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22638 font_size: settings.buffer_font_size(cx).into(),
22639 font_weight: settings.buffer_font.weight,
22640 line_height: relative(settings.buffer_line_height.value()),
22641 ..Default::default()
22642 };
22643 EditorElement::new(
22644 &self.prompt,
22645 EditorStyle {
22646 background: cx.theme().colors().editor_background,
22647 local_player: cx.theme().players().local(),
22648 text: text_style,
22649 ..Default::default()
22650 },
22651 )
22652 }
22653}
22654
22655impl Render for BreakpointPromptEditor {
22656 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22657 let editor_margins = *self.editor_margins.lock();
22658 let gutter_dimensions = editor_margins.gutter;
22659 h_flex()
22660 .key_context("Editor")
22661 .bg(cx.theme().colors().editor_background)
22662 .border_y_1()
22663 .border_color(cx.theme().status().info_border)
22664 .size_full()
22665 .py(window.line_height() / 2.5)
22666 .on_action(cx.listener(Self::confirm))
22667 .on_action(cx.listener(Self::cancel))
22668 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22669 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22670 }
22671}
22672
22673impl Focusable for BreakpointPromptEditor {
22674 fn focus_handle(&self, cx: &App) -> FocusHandle {
22675 self.prompt.focus_handle(cx)
22676 }
22677}
22678
22679fn all_edits_insertions_or_deletions(
22680 edits: &Vec<(Range<Anchor>, String)>,
22681 snapshot: &MultiBufferSnapshot,
22682) -> bool {
22683 let mut all_insertions = true;
22684 let mut all_deletions = true;
22685
22686 for (range, new_text) in edits.iter() {
22687 let range_is_empty = range.to_offset(&snapshot).is_empty();
22688 let text_is_empty = new_text.is_empty();
22689
22690 if range_is_empty != text_is_empty {
22691 if range_is_empty {
22692 all_deletions = false;
22693 } else {
22694 all_insertions = false;
22695 }
22696 } else {
22697 return false;
22698 }
22699
22700 if !all_insertions && !all_deletions {
22701 return false;
22702 }
22703 }
22704 all_insertions || all_deletions
22705}
22706
22707struct MissingEditPredictionKeybindingTooltip;
22708
22709impl Render for MissingEditPredictionKeybindingTooltip {
22710 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22711 ui::tooltip_container(window, cx, |container, _, cx| {
22712 container
22713 .flex_shrink_0()
22714 .max_w_80()
22715 .min_h(rems_from_px(124.))
22716 .justify_between()
22717 .child(
22718 v_flex()
22719 .flex_1()
22720 .text_ui_sm(cx)
22721 .child(Label::new("Conflict with Accept Keybinding"))
22722 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22723 )
22724 .child(
22725 h_flex()
22726 .pb_1()
22727 .gap_1()
22728 .items_end()
22729 .w_full()
22730 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22731 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22732 }))
22733 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22734 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22735 })),
22736 )
22737 })
22738 }
22739}
22740
22741#[derive(Debug, Clone, Copy, PartialEq)]
22742pub struct LineHighlight {
22743 pub background: Background,
22744 pub border: Option<gpui::Hsla>,
22745 pub include_gutter: bool,
22746 pub type_id: Option<TypeId>,
22747}
22748
22749fn render_diff_hunk_controls(
22750 row: u32,
22751 status: &DiffHunkStatus,
22752 hunk_range: Range<Anchor>,
22753 is_created_file: bool,
22754 line_height: Pixels,
22755 editor: &Entity<Editor>,
22756 _window: &mut Window,
22757 cx: &mut App,
22758) -> AnyElement {
22759 h_flex()
22760 .h(line_height)
22761 .mr_1()
22762 .gap_1()
22763 .px_0p5()
22764 .pb_1()
22765 .border_x_1()
22766 .border_b_1()
22767 .border_color(cx.theme().colors().border_variant)
22768 .rounded_b_lg()
22769 .bg(cx.theme().colors().editor_background)
22770 .gap_1()
22771 .block_mouse_except_scroll()
22772 .shadow_md()
22773 .child(if status.has_secondary_hunk() {
22774 Button::new(("stage", row as u64), "Stage")
22775 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22776 .tooltip({
22777 let focus_handle = editor.focus_handle(cx);
22778 move |window, cx| {
22779 Tooltip::for_action_in(
22780 "Stage Hunk",
22781 &::git::ToggleStaged,
22782 &focus_handle,
22783 window,
22784 cx,
22785 )
22786 }
22787 })
22788 .on_click({
22789 let editor = editor.clone();
22790 move |_event, _window, cx| {
22791 editor.update(cx, |editor, cx| {
22792 editor.stage_or_unstage_diff_hunks(
22793 true,
22794 vec![hunk_range.start..hunk_range.start],
22795 cx,
22796 );
22797 });
22798 }
22799 })
22800 } else {
22801 Button::new(("unstage", row as u64), "Unstage")
22802 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22803 .tooltip({
22804 let focus_handle = editor.focus_handle(cx);
22805 move |window, cx| {
22806 Tooltip::for_action_in(
22807 "Unstage Hunk",
22808 &::git::ToggleStaged,
22809 &focus_handle,
22810 window,
22811 cx,
22812 )
22813 }
22814 })
22815 .on_click({
22816 let editor = editor.clone();
22817 move |_event, _window, cx| {
22818 editor.update(cx, |editor, cx| {
22819 editor.stage_or_unstage_diff_hunks(
22820 false,
22821 vec![hunk_range.start..hunk_range.start],
22822 cx,
22823 );
22824 });
22825 }
22826 })
22827 })
22828 .child(
22829 Button::new(("restore", row as u64), "Restore")
22830 .tooltip({
22831 let focus_handle = editor.focus_handle(cx);
22832 move |window, cx| {
22833 Tooltip::for_action_in(
22834 "Restore Hunk",
22835 &::git::Restore,
22836 &focus_handle,
22837 window,
22838 cx,
22839 )
22840 }
22841 })
22842 .on_click({
22843 let editor = editor.clone();
22844 move |_event, window, cx| {
22845 editor.update(cx, |editor, cx| {
22846 let snapshot = editor.snapshot(window, cx);
22847 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22848 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22849 });
22850 }
22851 })
22852 .disabled(is_created_file),
22853 )
22854 .when(
22855 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22856 |el| {
22857 el.child(
22858 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22859 .shape(IconButtonShape::Square)
22860 .icon_size(IconSize::Small)
22861 // .disabled(!has_multiple_hunks)
22862 .tooltip({
22863 let focus_handle = editor.focus_handle(cx);
22864 move |window, cx| {
22865 Tooltip::for_action_in(
22866 "Next Hunk",
22867 &GoToHunk,
22868 &focus_handle,
22869 window,
22870 cx,
22871 )
22872 }
22873 })
22874 .on_click({
22875 let editor = editor.clone();
22876 move |_event, window, cx| {
22877 editor.update(cx, |editor, cx| {
22878 let snapshot = editor.snapshot(window, cx);
22879 let position =
22880 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22881 editor.go_to_hunk_before_or_after_position(
22882 &snapshot,
22883 position,
22884 Direction::Next,
22885 window,
22886 cx,
22887 );
22888 editor.expand_selected_diff_hunks(cx);
22889 });
22890 }
22891 }),
22892 )
22893 .child(
22894 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22895 .shape(IconButtonShape::Square)
22896 .icon_size(IconSize::Small)
22897 // .disabled(!has_multiple_hunks)
22898 .tooltip({
22899 let focus_handle = editor.focus_handle(cx);
22900 move |window, cx| {
22901 Tooltip::for_action_in(
22902 "Previous Hunk",
22903 &GoToPreviousHunk,
22904 &focus_handle,
22905 window,
22906 cx,
22907 )
22908 }
22909 })
22910 .on_click({
22911 let editor = editor.clone();
22912 move |_event, window, cx| {
22913 editor.update(cx, |editor, cx| {
22914 let snapshot = editor.snapshot(window, cx);
22915 let point =
22916 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22917 editor.go_to_hunk_before_or_after_position(
22918 &snapshot,
22919 point,
22920 Direction::Prev,
22921 window,
22922 cx,
22923 );
22924 editor.expand_selected_diff_hunks(cx);
22925 });
22926 }
22927 }),
22928 )
22929 },
22930 )
22931 .into_any_element()
22932}