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 pull_diagnostics_task: Task<()>,
1092 in_project_search: bool,
1093 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1094 breadcrumb_header: Option<String>,
1095 focused_block: Option<FocusedBlock>,
1096 next_scroll_position: NextScrollCursorCenterTopBottom,
1097 addons: HashMap<TypeId, Box<dyn Addon>>,
1098 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1099 load_diff_task: Option<Shared<Task<()>>>,
1100 /// Whether we are temporarily displaying a diff other than git's
1101 temporary_diff_override: bool,
1102 selection_mark_mode: bool,
1103 toggle_fold_multiple_buffers: Task<()>,
1104 _scroll_cursor_center_top_bottom_task: Task<()>,
1105 serialize_selections: Task<()>,
1106 serialize_folds: Task<()>,
1107 mouse_cursor_hidden: bool,
1108 minimap: Option<Entity<Self>>,
1109 hide_mouse_mode: HideMouseMode,
1110 pub change_list: ChangeList,
1111 inline_value_cache: InlineValueCache,
1112 selection_drag_state: SelectionDragState,
1113 drag_and_drop_selection_enabled: bool,
1114}
1115
1116#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1117enum NextScrollCursorCenterTopBottom {
1118 #[default]
1119 Center,
1120 Top,
1121 Bottom,
1122}
1123
1124impl NextScrollCursorCenterTopBottom {
1125 fn next(&self) -> Self {
1126 match self {
1127 Self::Center => Self::Top,
1128 Self::Top => Self::Bottom,
1129 Self::Bottom => Self::Center,
1130 }
1131 }
1132}
1133
1134#[derive(Clone)]
1135pub struct EditorSnapshot {
1136 pub mode: EditorMode,
1137 show_gutter: bool,
1138 show_line_numbers: Option<bool>,
1139 show_git_diff_gutter: Option<bool>,
1140 show_code_actions: Option<bool>,
1141 show_runnables: Option<bool>,
1142 show_breakpoints: Option<bool>,
1143 git_blame_gutter_max_author_length: Option<usize>,
1144 pub display_snapshot: DisplaySnapshot,
1145 pub placeholder_text: Option<Arc<str>>,
1146 is_focused: bool,
1147 scroll_anchor: ScrollAnchor,
1148 ongoing_scroll: OngoingScroll,
1149 current_line_highlight: CurrentLineHighlight,
1150 gutter_hovered: bool,
1151}
1152
1153#[derive(Default, Debug, Clone, Copy)]
1154pub struct GutterDimensions {
1155 pub left_padding: Pixels,
1156 pub right_padding: Pixels,
1157 pub width: Pixels,
1158 pub margin: Pixels,
1159 pub git_blame_entries_width: Option<Pixels>,
1160}
1161
1162impl GutterDimensions {
1163 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1164 Self {
1165 margin: Self::default_gutter_margin(font_id, font_size, cx),
1166 ..Default::default()
1167 }
1168 }
1169
1170 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1171 -cx.text_system().descent(font_id, font_size)
1172 }
1173 /// The full width of the space taken up by the gutter.
1174 pub fn full_width(&self) -> Pixels {
1175 self.margin + self.width
1176 }
1177
1178 /// The width of the space reserved for the fold indicators,
1179 /// use alongside 'justify_end' and `gutter_width` to
1180 /// right align content with the line numbers
1181 pub fn fold_area_width(&self) -> Pixels {
1182 self.margin + self.right_padding
1183 }
1184}
1185
1186#[derive(Debug)]
1187pub struct RemoteSelection {
1188 pub replica_id: ReplicaId,
1189 pub selection: Selection<Anchor>,
1190 pub cursor_shape: CursorShape,
1191 pub collaborator_id: CollaboratorId,
1192 pub line_mode: bool,
1193 pub user_name: Option<SharedString>,
1194 pub color: PlayerColor,
1195}
1196
1197#[derive(Clone, Debug)]
1198struct SelectionHistoryEntry {
1199 selections: Arc<[Selection<Anchor>]>,
1200 select_next_state: Option<SelectNextState>,
1201 select_prev_state: Option<SelectNextState>,
1202 add_selections_state: Option<AddSelectionsState>,
1203}
1204
1205#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1206enum SelectionHistoryMode {
1207 Normal,
1208 Undoing,
1209 Redoing,
1210 Skipping,
1211}
1212
1213#[derive(Clone, PartialEq, Eq, Hash)]
1214struct HoveredCursor {
1215 replica_id: u16,
1216 selection_id: usize,
1217}
1218
1219impl Default for SelectionHistoryMode {
1220 fn default() -> Self {
1221 Self::Normal
1222 }
1223}
1224
1225#[derive(Debug)]
1226pub struct SelectionEffects {
1227 nav_history: bool,
1228 completions: bool,
1229 scroll: Option<Autoscroll>,
1230}
1231
1232impl Default for SelectionEffects {
1233 fn default() -> Self {
1234 Self {
1235 nav_history: true,
1236 completions: true,
1237 scroll: Some(Autoscroll::fit()),
1238 }
1239 }
1240}
1241impl SelectionEffects {
1242 pub fn scroll(scroll: Autoscroll) -> Self {
1243 Self {
1244 scroll: Some(scroll),
1245 ..Default::default()
1246 }
1247 }
1248
1249 pub fn no_scroll() -> Self {
1250 Self {
1251 scroll: None,
1252 ..Default::default()
1253 }
1254 }
1255
1256 pub fn completions(self, completions: bool) -> Self {
1257 Self {
1258 completions,
1259 ..self
1260 }
1261 }
1262
1263 pub fn nav_history(self, nav_history: bool) -> Self {
1264 Self {
1265 nav_history,
1266 ..self
1267 }
1268 }
1269}
1270
1271struct DeferredSelectionEffectsState {
1272 changed: bool,
1273 effects: SelectionEffects,
1274 old_cursor_position: Anchor,
1275 history_entry: SelectionHistoryEntry,
1276}
1277
1278#[derive(Default)]
1279struct SelectionHistory {
1280 #[allow(clippy::type_complexity)]
1281 selections_by_transaction:
1282 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1283 mode: SelectionHistoryMode,
1284 undo_stack: VecDeque<SelectionHistoryEntry>,
1285 redo_stack: VecDeque<SelectionHistoryEntry>,
1286}
1287
1288impl SelectionHistory {
1289 #[track_caller]
1290 fn insert_transaction(
1291 &mut self,
1292 transaction_id: TransactionId,
1293 selections: Arc<[Selection<Anchor>]>,
1294 ) {
1295 if selections.is_empty() {
1296 log::error!(
1297 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1298 std::panic::Location::caller()
1299 );
1300 return;
1301 }
1302 self.selections_by_transaction
1303 .insert(transaction_id, (selections, None));
1304 }
1305
1306 #[allow(clippy::type_complexity)]
1307 fn transaction(
1308 &self,
1309 transaction_id: TransactionId,
1310 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1311 self.selections_by_transaction.get(&transaction_id)
1312 }
1313
1314 #[allow(clippy::type_complexity)]
1315 fn transaction_mut(
1316 &mut self,
1317 transaction_id: TransactionId,
1318 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1319 self.selections_by_transaction.get_mut(&transaction_id)
1320 }
1321
1322 fn push(&mut self, entry: SelectionHistoryEntry) {
1323 if !entry.selections.is_empty() {
1324 match self.mode {
1325 SelectionHistoryMode::Normal => {
1326 self.push_undo(entry);
1327 self.redo_stack.clear();
1328 }
1329 SelectionHistoryMode::Undoing => self.push_redo(entry),
1330 SelectionHistoryMode::Redoing => self.push_undo(entry),
1331 SelectionHistoryMode::Skipping => {}
1332 }
1333 }
1334 }
1335
1336 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1337 if self
1338 .undo_stack
1339 .back()
1340 .map_or(true, |e| e.selections != entry.selections)
1341 {
1342 self.undo_stack.push_back(entry);
1343 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1344 self.undo_stack.pop_front();
1345 }
1346 }
1347 }
1348
1349 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1350 if self
1351 .redo_stack
1352 .back()
1353 .map_or(true, |e| e.selections != entry.selections)
1354 {
1355 self.redo_stack.push_back(entry);
1356 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1357 self.redo_stack.pop_front();
1358 }
1359 }
1360 }
1361}
1362
1363#[derive(Clone, Copy)]
1364pub struct RowHighlightOptions {
1365 pub autoscroll: bool,
1366 pub include_gutter: bool,
1367}
1368
1369impl Default for RowHighlightOptions {
1370 fn default() -> Self {
1371 Self {
1372 autoscroll: Default::default(),
1373 include_gutter: true,
1374 }
1375 }
1376}
1377
1378struct RowHighlight {
1379 index: usize,
1380 range: Range<Anchor>,
1381 color: Hsla,
1382 options: RowHighlightOptions,
1383 type_id: TypeId,
1384}
1385
1386#[derive(Clone, Debug)]
1387struct AddSelectionsState {
1388 groups: Vec<AddSelectionsGroup>,
1389}
1390
1391#[derive(Clone, Debug)]
1392struct AddSelectionsGroup {
1393 above: bool,
1394 stack: Vec<usize>,
1395}
1396
1397#[derive(Clone)]
1398struct SelectNextState {
1399 query: AhoCorasick,
1400 wordwise: bool,
1401 done: bool,
1402}
1403
1404impl std::fmt::Debug for SelectNextState {
1405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1406 f.debug_struct(std::any::type_name::<Self>())
1407 .field("wordwise", &self.wordwise)
1408 .field("done", &self.done)
1409 .finish()
1410 }
1411}
1412
1413#[derive(Debug)]
1414struct AutocloseRegion {
1415 selection_id: usize,
1416 range: Range<Anchor>,
1417 pair: BracketPair,
1418}
1419
1420#[derive(Debug)]
1421struct SnippetState {
1422 ranges: Vec<Vec<Range<Anchor>>>,
1423 active_index: usize,
1424 choices: Vec<Option<Vec<String>>>,
1425}
1426
1427#[doc(hidden)]
1428pub struct RenameState {
1429 pub range: Range<Anchor>,
1430 pub old_name: Arc<str>,
1431 pub editor: Entity<Editor>,
1432 block_id: CustomBlockId,
1433}
1434
1435struct InvalidationStack<T>(Vec<T>);
1436
1437struct RegisteredInlineCompletionProvider {
1438 provider: Arc<dyn InlineCompletionProviderHandle>,
1439 _subscription: Subscription,
1440}
1441
1442#[derive(Debug, PartialEq, Eq)]
1443pub struct ActiveDiagnosticGroup {
1444 pub active_range: Range<Anchor>,
1445 pub active_message: String,
1446 pub group_id: usize,
1447 pub blocks: HashSet<CustomBlockId>,
1448}
1449
1450#[derive(Debug, PartialEq, Eq)]
1451
1452pub(crate) enum ActiveDiagnostic {
1453 None,
1454 All,
1455 Group(ActiveDiagnosticGroup),
1456}
1457
1458#[derive(Serialize, Deserialize, Clone, Debug)]
1459pub struct ClipboardSelection {
1460 /// The number of bytes in this selection.
1461 pub len: usize,
1462 /// Whether this was a full-line selection.
1463 pub is_entire_line: bool,
1464 /// The indentation of the first line when this content was originally copied.
1465 pub first_line_indent: u32,
1466}
1467
1468// selections, scroll behavior, was newest selection reversed
1469type SelectSyntaxNodeHistoryState = (
1470 Box<[Selection<usize>]>,
1471 SelectSyntaxNodeScrollBehavior,
1472 bool,
1473);
1474
1475#[derive(Default)]
1476struct SelectSyntaxNodeHistory {
1477 stack: Vec<SelectSyntaxNodeHistoryState>,
1478 // disable temporarily to allow changing selections without losing the stack
1479 pub disable_clearing: bool,
1480}
1481
1482impl SelectSyntaxNodeHistory {
1483 pub fn try_clear(&mut self) {
1484 if !self.disable_clearing {
1485 self.stack.clear();
1486 }
1487 }
1488
1489 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1490 self.stack.push(selection);
1491 }
1492
1493 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1494 self.stack.pop()
1495 }
1496}
1497
1498enum SelectSyntaxNodeScrollBehavior {
1499 CursorTop,
1500 FitSelection,
1501 CursorBottom,
1502}
1503
1504#[derive(Debug)]
1505pub(crate) struct NavigationData {
1506 cursor_anchor: Anchor,
1507 cursor_position: Point,
1508 scroll_anchor: ScrollAnchor,
1509 scroll_top_row: u32,
1510}
1511
1512#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1513pub enum GotoDefinitionKind {
1514 Symbol,
1515 Declaration,
1516 Type,
1517 Implementation,
1518}
1519
1520#[derive(Debug, Clone)]
1521enum InlayHintRefreshReason {
1522 ModifiersChanged(bool),
1523 Toggle(bool),
1524 SettingsChange(InlayHintSettings),
1525 NewLinesShown,
1526 BufferEdited(HashSet<Arc<Language>>),
1527 RefreshRequested,
1528 ExcerptsRemoved(Vec<ExcerptId>),
1529}
1530
1531impl InlayHintRefreshReason {
1532 fn description(&self) -> &'static str {
1533 match self {
1534 Self::ModifiersChanged(_) => "modifiers changed",
1535 Self::Toggle(_) => "toggle",
1536 Self::SettingsChange(_) => "settings change",
1537 Self::NewLinesShown => "new lines shown",
1538 Self::BufferEdited(_) => "buffer edited",
1539 Self::RefreshRequested => "refresh requested",
1540 Self::ExcerptsRemoved(_) => "excerpts removed",
1541 }
1542 }
1543}
1544
1545pub enum FormatTarget {
1546 Buffers(HashSet<Entity<Buffer>>),
1547 Ranges(Vec<Range<MultiBufferPoint>>),
1548}
1549
1550pub(crate) struct FocusedBlock {
1551 id: BlockId,
1552 focus_handle: WeakFocusHandle,
1553}
1554
1555#[derive(Clone)]
1556enum JumpData {
1557 MultiBufferRow {
1558 row: MultiBufferRow,
1559 line_offset_from_top: u32,
1560 },
1561 MultiBufferPoint {
1562 excerpt_id: ExcerptId,
1563 position: Point,
1564 anchor: text::Anchor,
1565 line_offset_from_top: u32,
1566 },
1567}
1568
1569pub enum MultibufferSelectionMode {
1570 First,
1571 All,
1572}
1573
1574#[derive(Clone, Copy, Debug, Default)]
1575pub struct RewrapOptions {
1576 pub override_language_settings: bool,
1577 pub preserve_existing_whitespace: bool,
1578}
1579
1580impl Editor {
1581 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1582 let buffer = cx.new(|cx| Buffer::local("", cx));
1583 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1584 Self::new(
1585 EditorMode::SingleLine { auto_width: false },
1586 buffer,
1587 None,
1588 window,
1589 cx,
1590 )
1591 }
1592
1593 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1594 let buffer = cx.new(|cx| Buffer::local("", cx));
1595 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1596 Self::new(EditorMode::full(), buffer, None, window, cx)
1597 }
1598
1599 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1600 let buffer = cx.new(|cx| Buffer::local("", cx));
1601 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1602 Self::new(
1603 EditorMode::SingleLine { auto_width: true },
1604 buffer,
1605 None,
1606 window,
1607 cx,
1608 )
1609 }
1610
1611 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1612 let buffer = cx.new(|cx| Buffer::local("", cx));
1613 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1614 Self::new(
1615 EditorMode::AutoHeight { max_lines },
1616 buffer,
1617 None,
1618 window,
1619 cx,
1620 )
1621 }
1622
1623 pub fn for_buffer(
1624 buffer: Entity<Buffer>,
1625 project: Option<Entity<Project>>,
1626 window: &mut Window,
1627 cx: &mut Context<Self>,
1628 ) -> Self {
1629 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1630 Self::new(EditorMode::full(), buffer, project, window, cx)
1631 }
1632
1633 pub fn for_multibuffer(
1634 buffer: Entity<MultiBuffer>,
1635 project: Option<Entity<Project>>,
1636 window: &mut Window,
1637 cx: &mut Context<Self>,
1638 ) -> Self {
1639 Self::new(EditorMode::full(), buffer, project, window, cx)
1640 }
1641
1642 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1643 let mut clone = Self::new(
1644 self.mode.clone(),
1645 self.buffer.clone(),
1646 self.project.clone(),
1647 window,
1648 cx,
1649 );
1650 self.display_map.update(cx, |display_map, cx| {
1651 let snapshot = display_map.snapshot(cx);
1652 clone.display_map.update(cx, |display_map, cx| {
1653 display_map.set_state(&snapshot, cx);
1654 });
1655 });
1656 clone.folds_did_change(cx);
1657 clone.selections.clone_state(&self.selections);
1658 clone.scroll_manager.clone_state(&self.scroll_manager);
1659 clone.searchable = self.searchable;
1660 clone.read_only = self.read_only;
1661 clone
1662 }
1663
1664 pub fn new(
1665 mode: EditorMode,
1666 buffer: Entity<MultiBuffer>,
1667 project: Option<Entity<Project>>,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) -> Self {
1671 Editor::new_internal(mode, buffer, project, None, window, cx)
1672 }
1673
1674 fn new_internal(
1675 mode: EditorMode,
1676 buffer: Entity<MultiBuffer>,
1677 project: Option<Entity<Project>>,
1678 display_map: Option<Entity<DisplayMap>>,
1679 window: &mut Window,
1680 cx: &mut Context<Self>,
1681 ) -> Self {
1682 debug_assert!(
1683 display_map.is_none() || mode.is_minimap(),
1684 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1685 );
1686
1687 let full_mode = mode.is_full();
1688 let diagnostics_max_severity = if full_mode {
1689 EditorSettings::get_global(cx)
1690 .diagnostics_max_severity
1691 .unwrap_or(DiagnosticSeverity::Hint)
1692 } else {
1693 DiagnosticSeverity::Off
1694 };
1695 let style = window.text_style();
1696 let font_size = style.font_size.to_pixels(window.rem_size());
1697 let editor = cx.entity().downgrade();
1698 let fold_placeholder = FoldPlaceholder {
1699 constrain_width: true,
1700 render: Arc::new(move |fold_id, fold_range, cx| {
1701 let editor = editor.clone();
1702 div()
1703 .id(fold_id)
1704 .bg(cx.theme().colors().ghost_element_background)
1705 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1706 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1707 .rounded_xs()
1708 .size_full()
1709 .cursor_pointer()
1710 .child("⋯")
1711 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1712 .on_click(move |_, _window, cx| {
1713 editor
1714 .update(cx, |editor, cx| {
1715 editor.unfold_ranges(
1716 &[fold_range.start..fold_range.end],
1717 true,
1718 false,
1719 cx,
1720 );
1721 cx.stop_propagation();
1722 })
1723 .ok();
1724 })
1725 .into_any()
1726 }),
1727 merge_adjacent: true,
1728 ..FoldPlaceholder::default()
1729 };
1730 let display_map = display_map.unwrap_or_else(|| {
1731 cx.new(|cx| {
1732 DisplayMap::new(
1733 buffer.clone(),
1734 style.font(),
1735 font_size,
1736 None,
1737 FILE_HEADER_HEIGHT,
1738 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1739 fold_placeholder,
1740 diagnostics_max_severity,
1741 cx,
1742 )
1743 })
1744 });
1745
1746 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1747
1748 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1749
1750 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1751 .then(|| language_settings::SoftWrap::None);
1752
1753 let mut project_subscriptions = Vec::new();
1754 if mode.is_full() {
1755 if let Some(project) = project.as_ref() {
1756 project_subscriptions.push(cx.subscribe_in(
1757 project,
1758 window,
1759 |editor, _, event, window, cx| match event {
1760 project::Event::RefreshCodeLens => {
1761 // we always query lens with actions, without storing them, always refreshing them
1762 }
1763 project::Event::RefreshInlayHints => {
1764 editor
1765 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1766 }
1767 project::Event::LanguageServerAdded(..)
1768 | project::Event::LanguageServerRemoved(..) => {
1769 if editor.tasks_update_task.is_none() {
1770 editor.tasks_update_task =
1771 Some(editor.refresh_runnables(window, cx));
1772 }
1773 editor.pull_diagnostics(None, window, cx);
1774 }
1775 project::Event::SnippetEdit(id, snippet_edits) => {
1776 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1777 let focus_handle = editor.focus_handle(cx);
1778 if focus_handle.is_focused(window) {
1779 let snapshot = buffer.read(cx).snapshot();
1780 for (range, snippet) in snippet_edits {
1781 let editor_range =
1782 language::range_from_lsp(*range).to_offset(&snapshot);
1783 editor
1784 .insert_snippet(
1785 &[editor_range],
1786 snippet.clone(),
1787 window,
1788 cx,
1789 )
1790 .ok();
1791 }
1792 }
1793 }
1794 }
1795 _ => {}
1796 },
1797 ));
1798 if let Some(task_inventory) = project
1799 .read(cx)
1800 .task_store()
1801 .read(cx)
1802 .task_inventory()
1803 .cloned()
1804 {
1805 project_subscriptions.push(cx.observe_in(
1806 &task_inventory,
1807 window,
1808 |editor, _, window, cx| {
1809 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1810 },
1811 ));
1812 };
1813
1814 project_subscriptions.push(cx.subscribe_in(
1815 &project.read(cx).breakpoint_store(),
1816 window,
1817 |editor, _, event, window, cx| match event {
1818 BreakpointStoreEvent::ClearDebugLines => {
1819 editor.clear_row_highlights::<ActiveDebugLine>();
1820 editor.refresh_inline_values(cx);
1821 }
1822 BreakpointStoreEvent::SetDebugLine => {
1823 if editor.go_to_active_debug_line(window, cx) {
1824 cx.stop_propagation();
1825 }
1826
1827 editor.refresh_inline_values(cx);
1828 }
1829 _ => {}
1830 },
1831 ));
1832 }
1833 }
1834
1835 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1836
1837 let inlay_hint_settings =
1838 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1839 let focus_handle = cx.focus_handle();
1840 cx.on_focus(&focus_handle, window, Self::handle_focus)
1841 .detach();
1842 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1843 .detach();
1844 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1845 .detach();
1846 cx.on_blur(&focus_handle, window, Self::handle_blur)
1847 .detach();
1848 cx.observe_pending_input(window, Self::observe_pending_input)
1849 .detach();
1850
1851 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1852 Some(false)
1853 } else {
1854 None
1855 };
1856
1857 let breakpoint_store = match (&mode, project.as_ref()) {
1858 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1859 _ => None,
1860 };
1861
1862 let mut code_action_providers = Vec::new();
1863 let mut load_uncommitted_diff = None;
1864 if let Some(project) = project.clone() {
1865 load_uncommitted_diff = Some(
1866 update_uncommitted_diff_for_buffer(
1867 cx.entity(),
1868 &project,
1869 buffer.read(cx).all_buffers(),
1870 buffer.clone(),
1871 cx,
1872 )
1873 .shared(),
1874 );
1875 code_action_providers.push(Rc::new(project) as Rc<_>);
1876 }
1877
1878 let mut editor = Self {
1879 focus_handle,
1880 show_cursor_when_unfocused: false,
1881 last_focused_descendant: None,
1882 buffer: buffer.clone(),
1883 display_map: display_map.clone(),
1884 selections,
1885 scroll_manager: ScrollManager::new(cx),
1886 columnar_selection_tail: None,
1887 columnar_display_point: None,
1888 add_selections_state: None,
1889 select_next_state: None,
1890 select_prev_state: None,
1891 selection_history: SelectionHistory::default(),
1892 defer_selection_effects: false,
1893 deferred_selection_effects_state: None,
1894 autoclose_regions: Vec::new(),
1895 snippet_stack: InvalidationStack::default(),
1896 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1897 ime_transaction: None,
1898 active_diagnostics: ActiveDiagnostic::None,
1899 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1900 inline_diagnostics_update: Task::ready(()),
1901 inline_diagnostics: Vec::new(),
1902 soft_wrap_mode_override,
1903 diagnostics_max_severity,
1904 hard_wrap: None,
1905 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1906 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1907 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1908 project,
1909 blink_manager: blink_manager.clone(),
1910 show_local_selections: true,
1911 show_scrollbars: ScrollbarAxes {
1912 horizontal: full_mode,
1913 vertical: full_mode,
1914 },
1915 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1916 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1917 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1918 show_gutter: mode.is_full(),
1919 show_line_numbers: None,
1920 use_relative_line_numbers: None,
1921 disable_expand_excerpt_buttons: false,
1922 show_git_diff_gutter: None,
1923 show_code_actions: None,
1924 show_runnables: None,
1925 show_breakpoints: None,
1926 show_wrap_guides: None,
1927 show_indent_guides,
1928 placeholder_text: None,
1929 highlight_order: 0,
1930 highlighted_rows: HashMap::default(),
1931 background_highlights: TreeMap::default(),
1932 gutter_highlights: TreeMap::default(),
1933 scrollbar_marker_state: ScrollbarMarkerState::default(),
1934 active_indent_guides_state: ActiveIndentGuidesState::default(),
1935 nav_history: None,
1936 context_menu: RefCell::new(None),
1937 context_menu_options: None,
1938 mouse_context_menu: None,
1939 completion_tasks: Vec::new(),
1940 inline_blame_popover: None,
1941 inline_blame_popover_show_task: None,
1942 signature_help_state: SignatureHelpState::default(),
1943 auto_signature_help: None,
1944 find_all_references_task_sources: Vec::new(),
1945 next_completion_id: 0,
1946 next_inlay_id: 0,
1947 code_action_providers,
1948 available_code_actions: None,
1949 code_actions_task: None,
1950 quick_selection_highlight_task: None,
1951 debounced_selection_highlight_task: None,
1952 document_highlights_task: None,
1953 linked_editing_range_task: None,
1954 pending_rename: None,
1955 searchable: true,
1956 cursor_shape: EditorSettings::get_global(cx)
1957 .cursor_shape
1958 .unwrap_or_default(),
1959 current_line_highlight: None,
1960 autoindent_mode: Some(AutoindentMode::EachLine),
1961 collapse_matches: false,
1962 workspace: None,
1963 input_enabled: true,
1964 use_modal_editing: mode.is_full(),
1965 read_only: mode.is_minimap(),
1966 use_autoclose: true,
1967 use_auto_surround: true,
1968 auto_replace_emoji_shortcode: false,
1969 jsx_tag_auto_close_enabled_in_any_buffer: false,
1970 leader_id: None,
1971 remote_id: None,
1972 hover_state: HoverState::default(),
1973 pending_mouse_down: None,
1974 hovered_link_state: None,
1975 edit_prediction_provider: None,
1976 active_inline_completion: None,
1977 stale_inline_completion_in_menu: None,
1978 edit_prediction_preview: EditPredictionPreview::Inactive {
1979 released_too_fast: false,
1980 },
1981 inline_diagnostics_enabled: mode.is_full(),
1982 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1983 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1984
1985 gutter_hovered: false,
1986 pixel_position_of_newest_cursor: None,
1987 last_bounds: None,
1988 last_position_map: None,
1989 expect_bounds_change: None,
1990 gutter_dimensions: GutterDimensions::default(),
1991 style: None,
1992 show_cursor_names: false,
1993 hovered_cursors: HashMap::default(),
1994 next_editor_action_id: EditorActionId::default(),
1995 editor_actions: Rc::default(),
1996 inline_completions_hidden_for_vim_mode: false,
1997 show_inline_completions_override: None,
1998 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1999 edit_prediction_settings: EditPredictionSettings::Disabled,
2000 edit_prediction_indent_conflict: false,
2001 edit_prediction_requires_modifier_in_indent_conflict: true,
2002 custom_context_menu: None,
2003 show_git_blame_gutter: false,
2004 show_git_blame_inline: false,
2005 show_selection_menu: None,
2006 show_git_blame_inline_delay_task: None,
2007 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2008 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2009 serialize_dirty_buffers: !mode.is_minimap()
2010 && ProjectSettings::get_global(cx)
2011 .session
2012 .restore_unsaved_buffers,
2013 blame: None,
2014 blame_subscription: None,
2015 tasks: BTreeMap::default(),
2016
2017 breakpoint_store,
2018 gutter_breakpoint_indicator: (None, None),
2019 _subscriptions: vec![
2020 cx.observe(&buffer, Self::on_buffer_changed),
2021 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2022 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2023 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2024 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2025 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2026 cx.observe_window_activation(window, |editor, window, cx| {
2027 let active = window.is_window_active();
2028 editor.blink_manager.update(cx, |blink_manager, cx| {
2029 if active {
2030 blink_manager.enable(cx);
2031 } else {
2032 blink_manager.disable(cx);
2033 }
2034 });
2035 if active {
2036 editor.show_mouse_cursor();
2037 }
2038 }),
2039 ],
2040 tasks_update_task: None,
2041 pull_diagnostics_task: Task::ready(()),
2042 linked_edit_ranges: Default::default(),
2043 in_project_search: false,
2044 previous_search_ranges: None,
2045 breadcrumb_header: None,
2046 focused_block: None,
2047 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2048 addons: HashMap::default(),
2049 registered_buffers: HashMap::default(),
2050 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2051 selection_mark_mode: false,
2052 toggle_fold_multiple_buffers: Task::ready(()),
2053 serialize_selections: Task::ready(()),
2054 serialize_folds: Task::ready(()),
2055 text_style_refinement: None,
2056 load_diff_task: load_uncommitted_diff,
2057 temporary_diff_override: false,
2058 mouse_cursor_hidden: false,
2059 minimap: None,
2060 hide_mouse_mode: EditorSettings::get_global(cx)
2061 .hide_mouse
2062 .unwrap_or_default(),
2063 change_list: ChangeList::new(),
2064 mode,
2065 selection_drag_state: SelectionDragState::None,
2066 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2067 };
2068 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2069 editor
2070 ._subscriptions
2071 .push(cx.observe(breakpoints, |_, _, cx| {
2072 cx.notify();
2073 }));
2074 }
2075 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2076 editor._subscriptions.extend(project_subscriptions);
2077
2078 editor._subscriptions.push(cx.subscribe_in(
2079 &cx.entity(),
2080 window,
2081 |editor, _, e: &EditorEvent, window, cx| match e {
2082 EditorEvent::ScrollPositionChanged { local, .. } => {
2083 if *local {
2084 let new_anchor = editor.scroll_manager.anchor();
2085 let snapshot = editor.snapshot(window, cx);
2086 editor.update_restoration_data(cx, move |data| {
2087 data.scroll_position = (
2088 new_anchor.top_row(&snapshot.buffer_snapshot),
2089 new_anchor.offset,
2090 );
2091 });
2092 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2093 editor.inline_blame_popover.take();
2094 }
2095 }
2096 EditorEvent::Edited { .. } => {
2097 if !vim_enabled(cx) {
2098 let (map, selections) = editor.selections.all_adjusted_display(cx);
2099 let pop_state = editor
2100 .change_list
2101 .last()
2102 .map(|previous| {
2103 previous.len() == selections.len()
2104 && previous.iter().enumerate().all(|(ix, p)| {
2105 p.to_display_point(&map).row()
2106 == selections[ix].head().row()
2107 })
2108 })
2109 .unwrap_or(false);
2110 let new_positions = selections
2111 .into_iter()
2112 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2113 .collect();
2114 editor
2115 .change_list
2116 .push_to_change_list(pop_state, new_positions);
2117 }
2118 }
2119 _ => (),
2120 },
2121 ));
2122
2123 if let Some(dap_store) = editor
2124 .project
2125 .as_ref()
2126 .map(|project| project.read(cx).dap_store())
2127 {
2128 let weak_editor = cx.weak_entity();
2129
2130 editor
2131 ._subscriptions
2132 .push(
2133 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2134 let session_entity = cx.entity();
2135 weak_editor
2136 .update(cx, |editor, cx| {
2137 editor._subscriptions.push(
2138 cx.subscribe(&session_entity, Self::on_debug_session_event),
2139 );
2140 })
2141 .ok();
2142 }),
2143 );
2144
2145 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2146 editor
2147 ._subscriptions
2148 .push(cx.subscribe(&session, Self::on_debug_session_event));
2149 }
2150 }
2151
2152 // skip adding the initial selection to selection history
2153 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2154 editor.end_selection(window, cx);
2155 editor.selection_history.mode = SelectionHistoryMode::Normal;
2156
2157 editor.scroll_manager.show_scrollbars(window, cx);
2158 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2159
2160 if full_mode {
2161 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2162 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2163
2164 if editor.git_blame_inline_enabled {
2165 editor.start_git_blame_inline(false, window, cx);
2166 }
2167
2168 editor.go_to_active_debug_line(window, cx);
2169
2170 if let Some(buffer) = buffer.read(cx).as_singleton() {
2171 if let Some(project) = editor.project.as_ref() {
2172 let handle = project.update(cx, |project, cx| {
2173 project.register_buffer_with_language_servers(&buffer, cx)
2174 });
2175 editor
2176 .registered_buffers
2177 .insert(buffer.read(cx).remote_id(), handle);
2178 }
2179 }
2180
2181 editor.minimap =
2182 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2183 editor.pull_diagnostics(None, window, cx);
2184 }
2185
2186 editor.report_editor_event("Editor Opened", None, cx);
2187 editor
2188 }
2189
2190 pub fn deploy_mouse_context_menu(
2191 &mut self,
2192 position: gpui::Point<Pixels>,
2193 context_menu: Entity<ContextMenu>,
2194 window: &mut Window,
2195 cx: &mut Context<Self>,
2196 ) {
2197 self.mouse_context_menu = Some(MouseContextMenu::new(
2198 self,
2199 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2200 context_menu,
2201 window,
2202 cx,
2203 ));
2204 }
2205
2206 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2207 self.mouse_context_menu
2208 .as_ref()
2209 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2210 }
2211
2212 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2213 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2214 }
2215
2216 fn key_context_internal(
2217 &self,
2218 has_active_edit_prediction: bool,
2219 window: &Window,
2220 cx: &App,
2221 ) -> KeyContext {
2222 let mut key_context = KeyContext::new_with_defaults();
2223 key_context.add("Editor");
2224 let mode = match self.mode {
2225 EditorMode::SingleLine { .. } => "single_line",
2226 EditorMode::AutoHeight { .. } => "auto_height",
2227 EditorMode::Minimap { .. } => "minimap",
2228 EditorMode::Full { .. } => "full",
2229 };
2230
2231 if EditorSettings::jupyter_enabled(cx) {
2232 key_context.add("jupyter");
2233 }
2234
2235 key_context.set("mode", mode);
2236 if self.pending_rename.is_some() {
2237 key_context.add("renaming");
2238 }
2239
2240 match self.context_menu.borrow().as_ref() {
2241 Some(CodeContextMenu::Completions(_)) => {
2242 key_context.add("menu");
2243 key_context.add("showing_completions");
2244 }
2245 Some(CodeContextMenu::CodeActions(_)) => {
2246 key_context.add("menu");
2247 key_context.add("showing_code_actions")
2248 }
2249 None => {}
2250 }
2251
2252 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2253 if !self.focus_handle(cx).contains_focused(window, cx)
2254 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2255 {
2256 for addon in self.addons.values() {
2257 addon.extend_key_context(&mut key_context, cx)
2258 }
2259 }
2260
2261 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2262 if let Some(extension) = singleton_buffer
2263 .read(cx)
2264 .file()
2265 .and_then(|file| file.path().extension()?.to_str())
2266 {
2267 key_context.set("extension", extension.to_string());
2268 }
2269 } else {
2270 key_context.add("multibuffer");
2271 }
2272
2273 if has_active_edit_prediction {
2274 if self.edit_prediction_in_conflict() {
2275 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2276 } else {
2277 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2278 key_context.add("copilot_suggestion");
2279 }
2280 }
2281
2282 if self.selection_mark_mode {
2283 key_context.add("selection_mode");
2284 }
2285
2286 key_context
2287 }
2288
2289 fn show_mouse_cursor(&mut self) {
2290 self.mouse_cursor_hidden = false;
2291 }
2292
2293 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2294 self.mouse_cursor_hidden = match origin {
2295 HideMouseCursorOrigin::TypingAction => {
2296 matches!(
2297 self.hide_mouse_mode,
2298 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2299 )
2300 }
2301 HideMouseCursorOrigin::MovementAction => {
2302 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2303 }
2304 };
2305 }
2306
2307 pub fn edit_prediction_in_conflict(&self) -> bool {
2308 if !self.show_edit_predictions_in_menu() {
2309 return false;
2310 }
2311
2312 let showing_completions = self
2313 .context_menu
2314 .borrow()
2315 .as_ref()
2316 .map_or(false, |context| {
2317 matches!(context, CodeContextMenu::Completions(_))
2318 });
2319
2320 showing_completions
2321 || self.edit_prediction_requires_modifier()
2322 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2323 // bindings to insert tab characters.
2324 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2325 }
2326
2327 pub fn accept_edit_prediction_keybind(
2328 &self,
2329 accept_partial: bool,
2330 window: &Window,
2331 cx: &App,
2332 ) -> AcceptEditPredictionBinding {
2333 let key_context = self.key_context_internal(true, window, cx);
2334 let in_conflict = self.edit_prediction_in_conflict();
2335
2336 let bindings = if accept_partial {
2337 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2338 } else {
2339 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2340 };
2341
2342 // TODO: if the binding contains multiple keystrokes, display all of them, not
2343 // just the first one.
2344 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2345 !in_conflict
2346 || binding
2347 .keystrokes()
2348 .first()
2349 .map_or(false, |keystroke| keystroke.modifiers.modified())
2350 }))
2351 }
2352
2353 pub fn new_file(
2354 workspace: &mut Workspace,
2355 _: &workspace::NewFile,
2356 window: &mut Window,
2357 cx: &mut Context<Workspace>,
2358 ) {
2359 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2360 "Failed to create buffer",
2361 window,
2362 cx,
2363 |e, _, _| match e.error_code() {
2364 ErrorCode::RemoteUpgradeRequired => Some(format!(
2365 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2366 e.error_tag("required").unwrap_or("the latest version")
2367 )),
2368 _ => None,
2369 },
2370 );
2371 }
2372
2373 pub fn new_in_workspace(
2374 workspace: &mut Workspace,
2375 window: &mut Window,
2376 cx: &mut Context<Workspace>,
2377 ) -> Task<Result<Entity<Editor>>> {
2378 let project = workspace.project().clone();
2379 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2380
2381 cx.spawn_in(window, async move |workspace, cx| {
2382 let buffer = create.await?;
2383 workspace.update_in(cx, |workspace, window, cx| {
2384 let editor =
2385 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2386 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2387 editor
2388 })
2389 })
2390 }
2391
2392 fn new_file_vertical(
2393 workspace: &mut Workspace,
2394 _: &workspace::NewFileSplitVertical,
2395 window: &mut Window,
2396 cx: &mut Context<Workspace>,
2397 ) {
2398 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2399 }
2400
2401 fn new_file_horizontal(
2402 workspace: &mut Workspace,
2403 _: &workspace::NewFileSplitHorizontal,
2404 window: &mut Window,
2405 cx: &mut Context<Workspace>,
2406 ) {
2407 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2408 }
2409
2410 fn new_file_in_direction(
2411 workspace: &mut Workspace,
2412 direction: SplitDirection,
2413 window: &mut Window,
2414 cx: &mut Context<Workspace>,
2415 ) {
2416 let project = workspace.project().clone();
2417 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2418
2419 cx.spawn_in(window, async move |workspace, cx| {
2420 let buffer = create.await?;
2421 workspace.update_in(cx, move |workspace, window, cx| {
2422 workspace.split_item(
2423 direction,
2424 Box::new(
2425 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2426 ),
2427 window,
2428 cx,
2429 )
2430 })?;
2431 anyhow::Ok(())
2432 })
2433 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2434 match e.error_code() {
2435 ErrorCode::RemoteUpgradeRequired => Some(format!(
2436 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2437 e.error_tag("required").unwrap_or("the latest version")
2438 )),
2439 _ => None,
2440 }
2441 });
2442 }
2443
2444 pub fn leader_id(&self) -> Option<CollaboratorId> {
2445 self.leader_id
2446 }
2447
2448 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2449 &self.buffer
2450 }
2451
2452 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2453 self.workspace.as_ref()?.0.upgrade()
2454 }
2455
2456 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2457 self.buffer().read(cx).title(cx)
2458 }
2459
2460 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2461 let git_blame_gutter_max_author_length = self
2462 .render_git_blame_gutter(cx)
2463 .then(|| {
2464 if let Some(blame) = self.blame.as_ref() {
2465 let max_author_length =
2466 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2467 Some(max_author_length)
2468 } else {
2469 None
2470 }
2471 })
2472 .flatten();
2473
2474 EditorSnapshot {
2475 mode: self.mode.clone(),
2476 show_gutter: self.show_gutter,
2477 show_line_numbers: self.show_line_numbers,
2478 show_git_diff_gutter: self.show_git_diff_gutter,
2479 show_code_actions: self.show_code_actions,
2480 show_runnables: self.show_runnables,
2481 show_breakpoints: self.show_breakpoints,
2482 git_blame_gutter_max_author_length,
2483 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2484 scroll_anchor: self.scroll_manager.anchor(),
2485 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2486 placeholder_text: self.placeholder_text.clone(),
2487 is_focused: self.focus_handle.is_focused(window),
2488 current_line_highlight: self
2489 .current_line_highlight
2490 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2491 gutter_hovered: self.gutter_hovered,
2492 }
2493 }
2494
2495 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2496 self.buffer.read(cx).language_at(point, cx)
2497 }
2498
2499 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2500 self.buffer.read(cx).read(cx).file_at(point).cloned()
2501 }
2502
2503 pub fn active_excerpt(
2504 &self,
2505 cx: &App,
2506 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2507 self.buffer
2508 .read(cx)
2509 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2510 }
2511
2512 pub fn mode(&self) -> &EditorMode {
2513 &self.mode
2514 }
2515
2516 pub fn set_mode(&mut self, mode: EditorMode) {
2517 self.mode = mode;
2518 }
2519
2520 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2521 self.collaboration_hub.as_deref()
2522 }
2523
2524 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2525 self.collaboration_hub = Some(hub);
2526 }
2527
2528 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2529 self.in_project_search = in_project_search;
2530 }
2531
2532 pub fn set_custom_context_menu(
2533 &mut self,
2534 f: impl 'static
2535 + Fn(
2536 &mut Self,
2537 DisplayPoint,
2538 &mut Window,
2539 &mut Context<Self>,
2540 ) -> Option<Entity<ui::ContextMenu>>,
2541 ) {
2542 self.custom_context_menu = Some(Box::new(f))
2543 }
2544
2545 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2546 self.completion_provider = provider;
2547 }
2548
2549 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2550 self.semantics_provider.clone()
2551 }
2552
2553 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2554 self.semantics_provider = provider;
2555 }
2556
2557 pub fn set_edit_prediction_provider<T>(
2558 &mut self,
2559 provider: Option<Entity<T>>,
2560 window: &mut Window,
2561 cx: &mut Context<Self>,
2562 ) where
2563 T: EditPredictionProvider,
2564 {
2565 self.edit_prediction_provider =
2566 provider.map(|provider| RegisteredInlineCompletionProvider {
2567 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2568 if this.focus_handle.is_focused(window) {
2569 this.update_visible_inline_completion(window, cx);
2570 }
2571 }),
2572 provider: Arc::new(provider),
2573 });
2574 self.update_edit_prediction_settings(cx);
2575 self.refresh_inline_completion(false, false, window, cx);
2576 }
2577
2578 pub fn placeholder_text(&self) -> Option<&str> {
2579 self.placeholder_text.as_deref()
2580 }
2581
2582 pub fn set_placeholder_text(
2583 &mut self,
2584 placeholder_text: impl Into<Arc<str>>,
2585 cx: &mut Context<Self>,
2586 ) {
2587 let placeholder_text = Some(placeholder_text.into());
2588 if self.placeholder_text != placeholder_text {
2589 self.placeholder_text = placeholder_text;
2590 cx.notify();
2591 }
2592 }
2593
2594 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2595 self.cursor_shape = cursor_shape;
2596
2597 // Disrupt blink for immediate user feedback that the cursor shape has changed
2598 self.blink_manager.update(cx, BlinkManager::show_cursor);
2599
2600 cx.notify();
2601 }
2602
2603 pub fn set_current_line_highlight(
2604 &mut self,
2605 current_line_highlight: Option<CurrentLineHighlight>,
2606 ) {
2607 self.current_line_highlight = current_line_highlight;
2608 }
2609
2610 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2611 self.collapse_matches = collapse_matches;
2612 }
2613
2614 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2615 let buffers = self.buffer.read(cx).all_buffers();
2616 let Some(project) = self.project.as_ref() else {
2617 return;
2618 };
2619 project.update(cx, |project, cx| {
2620 for buffer in buffers {
2621 self.registered_buffers
2622 .entry(buffer.read(cx).remote_id())
2623 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2624 }
2625 })
2626 }
2627
2628 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2629 if self.collapse_matches {
2630 return range.start..range.start;
2631 }
2632 range.clone()
2633 }
2634
2635 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2636 if self.display_map.read(cx).clip_at_line_ends != clip {
2637 self.display_map
2638 .update(cx, |map, _| map.clip_at_line_ends = clip);
2639 }
2640 }
2641
2642 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2643 self.input_enabled = input_enabled;
2644 }
2645
2646 pub fn set_inline_completions_hidden_for_vim_mode(
2647 &mut self,
2648 hidden: bool,
2649 window: &mut Window,
2650 cx: &mut Context<Self>,
2651 ) {
2652 if hidden != self.inline_completions_hidden_for_vim_mode {
2653 self.inline_completions_hidden_for_vim_mode = hidden;
2654 if hidden {
2655 self.update_visible_inline_completion(window, cx);
2656 } else {
2657 self.refresh_inline_completion(true, false, window, cx);
2658 }
2659 }
2660 }
2661
2662 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2663 self.menu_inline_completions_policy = value;
2664 }
2665
2666 pub fn set_autoindent(&mut self, autoindent: bool) {
2667 if autoindent {
2668 self.autoindent_mode = Some(AutoindentMode::EachLine);
2669 } else {
2670 self.autoindent_mode = None;
2671 }
2672 }
2673
2674 pub fn read_only(&self, cx: &App) -> bool {
2675 self.read_only || self.buffer.read(cx).read_only()
2676 }
2677
2678 pub fn set_read_only(&mut self, read_only: bool) {
2679 self.read_only = read_only;
2680 }
2681
2682 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2683 self.use_autoclose = autoclose;
2684 }
2685
2686 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2687 self.use_auto_surround = auto_surround;
2688 }
2689
2690 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2691 self.auto_replace_emoji_shortcode = auto_replace;
2692 }
2693
2694 pub fn toggle_edit_predictions(
2695 &mut self,
2696 _: &ToggleEditPrediction,
2697 window: &mut Window,
2698 cx: &mut Context<Self>,
2699 ) {
2700 if self.show_inline_completions_override.is_some() {
2701 self.set_show_edit_predictions(None, window, cx);
2702 } else {
2703 let show_edit_predictions = !self.edit_predictions_enabled();
2704 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2705 }
2706 }
2707
2708 pub fn set_show_edit_predictions(
2709 &mut self,
2710 show_edit_predictions: Option<bool>,
2711 window: &mut Window,
2712 cx: &mut Context<Self>,
2713 ) {
2714 self.show_inline_completions_override = show_edit_predictions;
2715 self.update_edit_prediction_settings(cx);
2716
2717 if let Some(false) = show_edit_predictions {
2718 self.discard_inline_completion(false, cx);
2719 } else {
2720 self.refresh_inline_completion(false, true, window, cx);
2721 }
2722 }
2723
2724 fn inline_completions_disabled_in_scope(
2725 &self,
2726 buffer: &Entity<Buffer>,
2727 buffer_position: language::Anchor,
2728 cx: &App,
2729 ) -> bool {
2730 let snapshot = buffer.read(cx).snapshot();
2731 let settings = snapshot.settings_at(buffer_position, cx);
2732
2733 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2734 return false;
2735 };
2736
2737 scope.override_name().map_or(false, |scope_name| {
2738 settings
2739 .edit_predictions_disabled_in
2740 .iter()
2741 .any(|s| s == scope_name)
2742 })
2743 }
2744
2745 pub fn set_use_modal_editing(&mut self, to: bool) {
2746 self.use_modal_editing = to;
2747 }
2748
2749 pub fn use_modal_editing(&self) -> bool {
2750 self.use_modal_editing
2751 }
2752
2753 fn selections_did_change(
2754 &mut self,
2755 local: bool,
2756 old_cursor_position: &Anchor,
2757 effects: SelectionEffects,
2758 window: &mut Window,
2759 cx: &mut Context<Self>,
2760 ) {
2761 window.invalidate_character_coordinates();
2762
2763 // Copy selections to primary selection buffer
2764 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2765 if local {
2766 let selections = self.selections.all::<usize>(cx);
2767 let buffer_handle = self.buffer.read(cx).read(cx);
2768
2769 let mut text = String::new();
2770 for (index, selection) in selections.iter().enumerate() {
2771 let text_for_selection = buffer_handle
2772 .text_for_range(selection.start..selection.end)
2773 .collect::<String>();
2774
2775 text.push_str(&text_for_selection);
2776 if index != selections.len() - 1 {
2777 text.push('\n');
2778 }
2779 }
2780
2781 if !text.is_empty() {
2782 cx.write_to_primary(ClipboardItem::new_string(text));
2783 }
2784 }
2785
2786 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2787 self.buffer.update(cx, |buffer, cx| {
2788 buffer.set_active_selections(
2789 &self.selections.disjoint_anchors(),
2790 self.selections.line_mode,
2791 self.cursor_shape,
2792 cx,
2793 )
2794 });
2795 }
2796 let display_map = self
2797 .display_map
2798 .update(cx, |display_map, cx| display_map.snapshot(cx));
2799 let buffer = &display_map.buffer_snapshot;
2800 if self.selections.count() == 1 {
2801 self.add_selections_state = None;
2802 }
2803 self.select_next_state = None;
2804 self.select_prev_state = None;
2805 self.select_syntax_node_history.try_clear();
2806 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2807 self.snippet_stack
2808 .invalidate(&self.selections.disjoint_anchors(), buffer);
2809 self.take_rename(false, window, cx);
2810
2811 let newest_selection = self.selections.newest_anchor();
2812 let new_cursor_position = newest_selection.head();
2813 let selection_start = newest_selection.start;
2814
2815 if effects.nav_history {
2816 self.push_to_nav_history(
2817 *old_cursor_position,
2818 Some(new_cursor_position.to_point(buffer)),
2819 false,
2820 cx,
2821 );
2822 }
2823
2824 if local {
2825 if let Some(buffer_id) = new_cursor_position.buffer_id {
2826 if !self.registered_buffers.contains_key(&buffer_id) {
2827 if let Some(project) = self.project.as_ref() {
2828 project.update(cx, |project, cx| {
2829 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2830 return;
2831 };
2832 self.registered_buffers.insert(
2833 buffer_id,
2834 project.register_buffer_with_language_servers(&buffer, cx),
2835 );
2836 })
2837 }
2838 }
2839 }
2840
2841 let mut context_menu = self.context_menu.borrow_mut();
2842 let completion_menu = match context_menu.as_ref() {
2843 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2844 Some(CodeContextMenu::CodeActions(_)) => {
2845 *context_menu = None;
2846 None
2847 }
2848 None => None,
2849 };
2850 let completion_position = completion_menu.map(|menu| menu.initial_position);
2851 drop(context_menu);
2852
2853 if effects.completions {
2854 if let Some(completion_position) = completion_position {
2855 let start_offset = selection_start.to_offset(buffer);
2856 let position_matches = start_offset == completion_position.to_offset(buffer);
2857 let continue_showing = if position_matches {
2858 if self.snippet_stack.is_empty() {
2859 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2860 } else {
2861 // Snippet choices can be shown even when the cursor is in whitespace.
2862 // Dismissing the menu when actions like backspace
2863 true
2864 }
2865 } else {
2866 false
2867 };
2868
2869 if continue_showing {
2870 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2871 } else {
2872 self.hide_context_menu(window, cx);
2873 }
2874 }
2875 }
2876
2877 hide_hover(self, cx);
2878
2879 if old_cursor_position.to_display_point(&display_map).row()
2880 != new_cursor_position.to_display_point(&display_map).row()
2881 {
2882 self.available_code_actions.take();
2883 }
2884 self.refresh_code_actions(window, cx);
2885 self.refresh_document_highlights(cx);
2886 self.refresh_selected_text_highlights(false, window, cx);
2887 refresh_matching_bracket_highlights(self, window, cx);
2888 self.update_visible_inline_completion(window, cx);
2889 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2890 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2891 self.inline_blame_popover.take();
2892 if self.git_blame_inline_enabled {
2893 self.start_inline_blame_timer(window, cx);
2894 }
2895 }
2896
2897 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2898 cx.emit(EditorEvent::SelectionsChanged { local });
2899
2900 let selections = &self.selections.disjoint;
2901 if selections.len() == 1 {
2902 cx.emit(SearchEvent::ActiveMatchChanged)
2903 }
2904 if local {
2905 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2906 let inmemory_selections = selections
2907 .iter()
2908 .map(|s| {
2909 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2910 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2911 })
2912 .collect();
2913 self.update_restoration_data(cx, |data| {
2914 data.selections = inmemory_selections;
2915 });
2916
2917 if WorkspaceSettings::get(None, cx).restore_on_startup
2918 != RestoreOnStartupBehavior::None
2919 {
2920 if let Some(workspace_id) =
2921 self.workspace.as_ref().and_then(|workspace| workspace.1)
2922 {
2923 let snapshot = self.buffer().read(cx).snapshot(cx);
2924 let selections = selections.clone();
2925 let background_executor = cx.background_executor().clone();
2926 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2927 self.serialize_selections = cx.background_spawn(async move {
2928 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2929 let db_selections = selections
2930 .iter()
2931 .map(|selection| {
2932 (
2933 selection.start.to_offset(&snapshot),
2934 selection.end.to_offset(&snapshot),
2935 )
2936 })
2937 .collect();
2938
2939 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2940 .await
2941 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2942 .log_err();
2943 });
2944 }
2945 }
2946 }
2947 }
2948
2949 cx.notify();
2950 }
2951
2952 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2953 use text::ToOffset as _;
2954 use text::ToPoint as _;
2955
2956 if self.mode.is_minimap()
2957 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2958 {
2959 return;
2960 }
2961
2962 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2963 return;
2964 };
2965
2966 let snapshot = singleton.read(cx).snapshot();
2967 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2968 let display_snapshot = display_map.snapshot(cx);
2969
2970 display_snapshot
2971 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2972 .map(|fold| {
2973 fold.range.start.text_anchor.to_point(&snapshot)
2974 ..fold.range.end.text_anchor.to_point(&snapshot)
2975 })
2976 .collect()
2977 });
2978 self.update_restoration_data(cx, |data| {
2979 data.folds = inmemory_folds;
2980 });
2981
2982 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2983 return;
2984 };
2985 let background_executor = cx.background_executor().clone();
2986 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2987 let db_folds = self.display_map.update(cx, |display_map, cx| {
2988 display_map
2989 .snapshot(cx)
2990 .folds_in_range(0..snapshot.len())
2991 .map(|fold| {
2992 (
2993 fold.range.start.text_anchor.to_offset(&snapshot),
2994 fold.range.end.text_anchor.to_offset(&snapshot),
2995 )
2996 })
2997 .collect()
2998 });
2999 self.serialize_folds = cx.background_spawn(async move {
3000 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3001 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3002 .await
3003 .with_context(|| {
3004 format!(
3005 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3006 )
3007 })
3008 .log_err();
3009 });
3010 }
3011
3012 pub fn sync_selections(
3013 &mut self,
3014 other: Entity<Editor>,
3015 cx: &mut Context<Self>,
3016 ) -> gpui::Subscription {
3017 let other_selections = other.read(cx).selections.disjoint.to_vec();
3018 self.selections.change_with(cx, |selections| {
3019 selections.select_anchors(other_selections);
3020 });
3021
3022 let other_subscription =
3023 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3024 EditorEvent::SelectionsChanged { local: true } => {
3025 let other_selections = other.read(cx).selections.disjoint.to_vec();
3026 if other_selections.is_empty() {
3027 return;
3028 }
3029 this.selections.change_with(cx, |selections| {
3030 selections.select_anchors(other_selections);
3031 });
3032 }
3033 _ => {}
3034 });
3035
3036 let this_subscription =
3037 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3038 EditorEvent::SelectionsChanged { local: true } => {
3039 let these_selections = this.selections.disjoint.to_vec();
3040 if these_selections.is_empty() {
3041 return;
3042 }
3043 other.update(cx, |other_editor, cx| {
3044 other_editor.selections.change_with(cx, |selections| {
3045 selections.select_anchors(these_selections);
3046 })
3047 });
3048 }
3049 _ => {}
3050 });
3051
3052 Subscription::join(other_subscription, this_subscription)
3053 }
3054
3055 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3056 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3057 /// effects of selection change occur at the end of the transaction.
3058 pub fn change_selections<R>(
3059 &mut self,
3060 effects: impl Into<SelectionEffects>,
3061 window: &mut Window,
3062 cx: &mut Context<Self>,
3063 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3064 ) -> R {
3065 let effects = effects.into();
3066 if let Some(state) = &mut self.deferred_selection_effects_state {
3067 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3068 state.effects.completions = effects.completions;
3069 state.effects.nav_history |= effects.nav_history;
3070 let (changed, result) = self.selections.change_with(cx, change);
3071 state.changed |= changed;
3072 return result;
3073 }
3074 let mut state = DeferredSelectionEffectsState {
3075 changed: false,
3076 effects,
3077 old_cursor_position: self.selections.newest_anchor().head(),
3078 history_entry: SelectionHistoryEntry {
3079 selections: self.selections.disjoint_anchors(),
3080 select_next_state: self.select_next_state.clone(),
3081 select_prev_state: self.select_prev_state.clone(),
3082 add_selections_state: self.add_selections_state.clone(),
3083 },
3084 };
3085 let (changed, result) = self.selections.change_with(cx, change);
3086 state.changed = state.changed || changed;
3087 if self.defer_selection_effects {
3088 self.deferred_selection_effects_state = Some(state);
3089 } else {
3090 self.apply_selection_effects(state, window, cx);
3091 }
3092 result
3093 }
3094
3095 /// Defers the effects of selection change, so that the effects of multiple calls to
3096 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3097 /// to selection history and the state of popovers based on selection position aren't
3098 /// erroneously updated.
3099 pub fn with_selection_effects_deferred<R>(
3100 &mut self,
3101 window: &mut Window,
3102 cx: &mut Context<Self>,
3103 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3104 ) -> R {
3105 let already_deferred = self.defer_selection_effects;
3106 self.defer_selection_effects = true;
3107 let result = update(self, window, cx);
3108 if !already_deferred {
3109 self.defer_selection_effects = false;
3110 if let Some(state) = self.deferred_selection_effects_state.take() {
3111 self.apply_selection_effects(state, window, cx);
3112 }
3113 }
3114 result
3115 }
3116
3117 fn apply_selection_effects(
3118 &mut self,
3119 state: DeferredSelectionEffectsState,
3120 window: &mut Window,
3121 cx: &mut Context<Self>,
3122 ) {
3123 if state.changed {
3124 self.selection_history.push(state.history_entry);
3125
3126 if let Some(autoscroll) = state.effects.scroll {
3127 self.request_autoscroll(autoscroll, cx);
3128 }
3129
3130 let old_cursor_position = &state.old_cursor_position;
3131
3132 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3133
3134 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3135 self.show_signature_help(&ShowSignatureHelp, window, cx);
3136 }
3137 }
3138 }
3139
3140 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3141 where
3142 I: IntoIterator<Item = (Range<S>, T)>,
3143 S: ToOffset,
3144 T: Into<Arc<str>>,
3145 {
3146 if self.read_only(cx) {
3147 return;
3148 }
3149
3150 self.buffer
3151 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3152 }
3153
3154 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3155 where
3156 I: IntoIterator<Item = (Range<S>, T)>,
3157 S: ToOffset,
3158 T: Into<Arc<str>>,
3159 {
3160 if self.read_only(cx) {
3161 return;
3162 }
3163
3164 self.buffer.update(cx, |buffer, cx| {
3165 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3166 });
3167 }
3168
3169 pub fn edit_with_block_indent<I, S, T>(
3170 &mut self,
3171 edits: I,
3172 original_indent_columns: Vec<Option<u32>>,
3173 cx: &mut Context<Self>,
3174 ) where
3175 I: IntoIterator<Item = (Range<S>, T)>,
3176 S: ToOffset,
3177 T: Into<Arc<str>>,
3178 {
3179 if self.read_only(cx) {
3180 return;
3181 }
3182
3183 self.buffer.update(cx, |buffer, cx| {
3184 buffer.edit(
3185 edits,
3186 Some(AutoindentMode::Block {
3187 original_indent_columns,
3188 }),
3189 cx,
3190 )
3191 });
3192 }
3193
3194 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3195 self.hide_context_menu(window, cx);
3196
3197 match phase {
3198 SelectPhase::Begin {
3199 position,
3200 add,
3201 click_count,
3202 } => self.begin_selection(position, add, click_count, window, cx),
3203 SelectPhase::BeginColumnar {
3204 position,
3205 goal_column,
3206 reset,
3207 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3208 SelectPhase::Extend {
3209 position,
3210 click_count,
3211 } => self.extend_selection(position, click_count, window, cx),
3212 SelectPhase::Update {
3213 position,
3214 goal_column,
3215 scroll_delta,
3216 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3217 SelectPhase::End => self.end_selection(window, cx),
3218 }
3219 }
3220
3221 fn extend_selection(
3222 &mut self,
3223 position: DisplayPoint,
3224 click_count: usize,
3225 window: &mut Window,
3226 cx: &mut Context<Self>,
3227 ) {
3228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3229 let tail = self.selections.newest::<usize>(cx).tail();
3230 self.begin_selection(position, false, click_count, window, cx);
3231
3232 let position = position.to_offset(&display_map, Bias::Left);
3233 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3234
3235 let mut pending_selection = self
3236 .selections
3237 .pending_anchor()
3238 .expect("extend_selection not called with pending selection");
3239 if position >= tail {
3240 pending_selection.start = tail_anchor;
3241 } else {
3242 pending_selection.end = tail_anchor;
3243 pending_selection.reversed = true;
3244 }
3245
3246 let mut pending_mode = self.selections.pending_mode().unwrap();
3247 match &mut pending_mode {
3248 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3249 _ => {}
3250 }
3251
3252 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3253 SelectionEffects::scroll(Autoscroll::fit())
3254 } else {
3255 SelectionEffects::no_scroll()
3256 };
3257
3258 self.change_selections(effects, window, cx, |s| {
3259 s.set_pending(pending_selection, pending_mode)
3260 });
3261 }
3262
3263 fn begin_selection(
3264 &mut self,
3265 position: DisplayPoint,
3266 add: bool,
3267 click_count: usize,
3268 window: &mut Window,
3269 cx: &mut Context<Self>,
3270 ) {
3271 if !self.focus_handle.is_focused(window) {
3272 self.last_focused_descendant = None;
3273 window.focus(&self.focus_handle);
3274 }
3275
3276 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3277 let buffer = &display_map.buffer_snapshot;
3278 let position = display_map.clip_point(position, Bias::Left);
3279
3280 let start;
3281 let end;
3282 let mode;
3283 let mut auto_scroll;
3284 match click_count {
3285 1 => {
3286 start = buffer.anchor_before(position.to_point(&display_map));
3287 end = start;
3288 mode = SelectMode::Character;
3289 auto_scroll = true;
3290 }
3291 2 => {
3292 let range = movement::surrounding_word(&display_map, position);
3293 start = buffer.anchor_before(range.start.to_point(&display_map));
3294 end = buffer.anchor_before(range.end.to_point(&display_map));
3295 mode = SelectMode::Word(start..end);
3296 auto_scroll = true;
3297 }
3298 3 => {
3299 let position = display_map
3300 .clip_point(position, Bias::Left)
3301 .to_point(&display_map);
3302 let line_start = display_map.prev_line_boundary(position).0;
3303 let next_line_start = buffer.clip_point(
3304 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3305 Bias::Left,
3306 );
3307 start = buffer.anchor_before(line_start);
3308 end = buffer.anchor_before(next_line_start);
3309 mode = SelectMode::Line(start..end);
3310 auto_scroll = true;
3311 }
3312 _ => {
3313 start = buffer.anchor_before(0);
3314 end = buffer.anchor_before(buffer.len());
3315 mode = SelectMode::All;
3316 auto_scroll = false;
3317 }
3318 }
3319 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3320
3321 let point_to_delete: Option<usize> = {
3322 let selected_points: Vec<Selection<Point>> =
3323 self.selections.disjoint_in_range(start..end, cx);
3324
3325 if !add || click_count > 1 {
3326 None
3327 } else if !selected_points.is_empty() {
3328 Some(selected_points[0].id)
3329 } else {
3330 let clicked_point_already_selected =
3331 self.selections.disjoint.iter().find(|selection| {
3332 selection.start.to_point(buffer) == start.to_point(buffer)
3333 || selection.end.to_point(buffer) == end.to_point(buffer)
3334 });
3335
3336 clicked_point_already_selected.map(|selection| selection.id)
3337 }
3338 };
3339
3340 let selections_count = self.selections.count();
3341
3342 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3343 if let Some(point_to_delete) = point_to_delete {
3344 s.delete(point_to_delete);
3345
3346 if selections_count == 1 {
3347 s.set_pending_anchor_range(start..end, mode);
3348 }
3349 } else {
3350 if !add {
3351 s.clear_disjoint();
3352 }
3353
3354 s.set_pending_anchor_range(start..end, mode);
3355 }
3356 });
3357 }
3358
3359 fn begin_columnar_selection(
3360 &mut self,
3361 position: DisplayPoint,
3362 goal_column: u32,
3363 reset: bool,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 ) {
3367 if !self.focus_handle.is_focused(window) {
3368 self.last_focused_descendant = None;
3369 window.focus(&self.focus_handle);
3370 }
3371
3372 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3373
3374 if reset {
3375 let pointer_position = display_map
3376 .buffer_snapshot
3377 .anchor_before(position.to_point(&display_map));
3378
3379 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3380 s.clear_disjoint();
3381 s.set_pending_anchor_range(
3382 pointer_position..pointer_position,
3383 SelectMode::Character,
3384 );
3385 });
3386 if position.column() != goal_column {
3387 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3388 } else {
3389 self.columnar_display_point = None;
3390 }
3391 }
3392
3393 let tail = self.selections.newest::<Point>(cx).tail();
3394 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3395
3396 if !reset {
3397 self.columnar_display_point = None;
3398 self.select_columns(
3399 tail.to_display_point(&display_map),
3400 position,
3401 goal_column,
3402 &display_map,
3403 window,
3404 cx,
3405 );
3406 }
3407 }
3408
3409 fn update_selection(
3410 &mut self,
3411 position: DisplayPoint,
3412 goal_column: u32,
3413 scroll_delta: gpui::Point<f32>,
3414 window: &mut Window,
3415 cx: &mut Context<Self>,
3416 ) {
3417 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3418
3419 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3420 let tail = self
3421 .columnar_display_point
3422 .unwrap_or_else(|| tail.to_display_point(&display_map));
3423 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3424 } else if let Some(mut pending) = self.selections.pending_anchor() {
3425 let buffer = self.buffer.read(cx).snapshot(cx);
3426 let head;
3427 let tail;
3428 let mode = self.selections.pending_mode().unwrap();
3429 match &mode {
3430 SelectMode::Character => {
3431 head = position.to_point(&display_map);
3432 tail = pending.tail().to_point(&buffer);
3433 }
3434 SelectMode::Word(original_range) => {
3435 let original_display_range = original_range.start.to_display_point(&display_map)
3436 ..original_range.end.to_display_point(&display_map);
3437 let original_buffer_range = original_display_range.start.to_point(&display_map)
3438 ..original_display_range.end.to_point(&display_map);
3439 if movement::is_inside_word(&display_map, position)
3440 || original_display_range.contains(&position)
3441 {
3442 let word_range = movement::surrounding_word(&display_map, position);
3443 if word_range.start < original_display_range.start {
3444 head = word_range.start.to_point(&display_map);
3445 } else {
3446 head = word_range.end.to_point(&display_map);
3447 }
3448 } else {
3449 head = position.to_point(&display_map);
3450 }
3451
3452 if head <= original_buffer_range.start {
3453 tail = original_buffer_range.end;
3454 } else {
3455 tail = original_buffer_range.start;
3456 }
3457 }
3458 SelectMode::Line(original_range) => {
3459 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3460
3461 let position = display_map
3462 .clip_point(position, Bias::Left)
3463 .to_point(&display_map);
3464 let line_start = display_map.prev_line_boundary(position).0;
3465 let next_line_start = buffer.clip_point(
3466 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3467 Bias::Left,
3468 );
3469
3470 if line_start < original_range.start {
3471 head = line_start
3472 } else {
3473 head = next_line_start
3474 }
3475
3476 if head <= original_range.start {
3477 tail = original_range.end;
3478 } else {
3479 tail = original_range.start;
3480 }
3481 }
3482 SelectMode::All => {
3483 return;
3484 }
3485 };
3486
3487 if head < tail {
3488 pending.start = buffer.anchor_before(head);
3489 pending.end = buffer.anchor_before(tail);
3490 pending.reversed = true;
3491 } else {
3492 pending.start = buffer.anchor_before(tail);
3493 pending.end = buffer.anchor_before(head);
3494 pending.reversed = false;
3495 }
3496
3497 self.change_selections(None, window, cx, |s| {
3498 s.set_pending(pending, mode);
3499 });
3500 } else {
3501 log::error!("update_selection dispatched with no pending selection");
3502 return;
3503 }
3504
3505 self.apply_scroll_delta(scroll_delta, window, cx);
3506 cx.notify();
3507 }
3508
3509 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3510 self.columnar_selection_tail.take();
3511 if self.selections.pending_anchor().is_some() {
3512 let selections = self.selections.all::<usize>(cx);
3513 self.change_selections(None, window, cx, |s| {
3514 s.select(selections);
3515 s.clear_pending();
3516 });
3517 }
3518 }
3519
3520 fn select_columns(
3521 &mut self,
3522 tail: DisplayPoint,
3523 head: DisplayPoint,
3524 goal_column: u32,
3525 display_map: &DisplaySnapshot,
3526 window: &mut Window,
3527 cx: &mut Context<Self>,
3528 ) {
3529 let start_row = cmp::min(tail.row(), head.row());
3530 let end_row = cmp::max(tail.row(), head.row());
3531 let start_column = cmp::min(tail.column(), goal_column);
3532 let end_column = cmp::max(tail.column(), goal_column);
3533 let reversed = start_column < tail.column();
3534
3535 let selection_ranges = (start_row.0..=end_row.0)
3536 .map(DisplayRow)
3537 .filter_map(|row| {
3538 if !display_map.is_block_line(row) {
3539 let start = display_map
3540 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3541 .to_point(display_map);
3542 let end = display_map
3543 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3544 .to_point(display_map);
3545 if reversed {
3546 Some(end..start)
3547 } else {
3548 Some(start..end)
3549 }
3550 } else {
3551 None
3552 }
3553 })
3554 .collect::<Vec<_>>();
3555
3556 let mut non_empty_ranges = selection_ranges
3557 .iter()
3558 .filter(|selection_range| selection_range.start != selection_range.end)
3559 .peekable();
3560
3561 let ranges = if non_empty_ranges.peek().is_some() {
3562 non_empty_ranges.cloned().collect()
3563 } else {
3564 selection_ranges
3565 };
3566
3567 self.change_selections(None, window, cx, |s| {
3568 s.select_ranges(ranges);
3569 });
3570 cx.notify();
3571 }
3572
3573 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3574 self.selections
3575 .all_adjusted(cx)
3576 .iter()
3577 .any(|selection| !selection.is_empty())
3578 }
3579
3580 pub fn has_pending_nonempty_selection(&self) -> bool {
3581 let pending_nonempty_selection = match self.selections.pending_anchor() {
3582 Some(Selection { start, end, .. }) => start != end,
3583 None => false,
3584 };
3585
3586 pending_nonempty_selection
3587 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3588 }
3589
3590 pub fn has_pending_selection(&self) -> bool {
3591 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3592 }
3593
3594 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3595 self.selection_mark_mode = false;
3596 self.selection_drag_state = SelectionDragState::None;
3597
3598 if self.clear_expanded_diff_hunks(cx) {
3599 cx.notify();
3600 return;
3601 }
3602 if self.dismiss_menus_and_popups(true, window, cx) {
3603 return;
3604 }
3605
3606 if self.mode.is_full()
3607 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3608 {
3609 return;
3610 }
3611
3612 cx.propagate();
3613 }
3614
3615 pub fn dismiss_menus_and_popups(
3616 &mut self,
3617 is_user_requested: bool,
3618 window: &mut Window,
3619 cx: &mut Context<Self>,
3620 ) -> bool {
3621 if self.take_rename(false, window, cx).is_some() {
3622 return true;
3623 }
3624
3625 if hide_hover(self, cx) {
3626 return true;
3627 }
3628
3629 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3630 return true;
3631 }
3632
3633 if self.hide_context_menu(window, cx).is_some() {
3634 return true;
3635 }
3636
3637 if self.mouse_context_menu.take().is_some() {
3638 return true;
3639 }
3640
3641 if is_user_requested && self.discard_inline_completion(true, cx) {
3642 return true;
3643 }
3644
3645 if self.snippet_stack.pop().is_some() {
3646 return true;
3647 }
3648
3649 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3650 self.dismiss_diagnostics(cx);
3651 return true;
3652 }
3653
3654 false
3655 }
3656
3657 fn linked_editing_ranges_for(
3658 &self,
3659 selection: Range<text::Anchor>,
3660 cx: &App,
3661 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3662 if self.linked_edit_ranges.is_empty() {
3663 return None;
3664 }
3665 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3666 selection.end.buffer_id.and_then(|end_buffer_id| {
3667 if selection.start.buffer_id != Some(end_buffer_id) {
3668 return None;
3669 }
3670 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3671 let snapshot = buffer.read(cx).snapshot();
3672 self.linked_edit_ranges
3673 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3674 .map(|ranges| (ranges, snapshot, buffer))
3675 })?;
3676 use text::ToOffset as TO;
3677 // find offset from the start of current range to current cursor position
3678 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3679
3680 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3681 let start_difference = start_offset - start_byte_offset;
3682 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3683 let end_difference = end_offset - start_byte_offset;
3684 // Current range has associated linked ranges.
3685 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3686 for range in linked_ranges.iter() {
3687 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3688 let end_offset = start_offset + end_difference;
3689 let start_offset = start_offset + start_difference;
3690 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3691 continue;
3692 }
3693 if self.selections.disjoint_anchor_ranges().any(|s| {
3694 if s.start.buffer_id != selection.start.buffer_id
3695 || s.end.buffer_id != selection.end.buffer_id
3696 {
3697 return false;
3698 }
3699 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3700 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3701 }) {
3702 continue;
3703 }
3704 let start = buffer_snapshot.anchor_after(start_offset);
3705 let end = buffer_snapshot.anchor_after(end_offset);
3706 linked_edits
3707 .entry(buffer.clone())
3708 .or_default()
3709 .push(start..end);
3710 }
3711 Some(linked_edits)
3712 }
3713
3714 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3715 let text: Arc<str> = text.into();
3716
3717 if self.read_only(cx) {
3718 return;
3719 }
3720
3721 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3722
3723 let selections = self.selections.all_adjusted(cx);
3724 let mut bracket_inserted = false;
3725 let mut edits = Vec::new();
3726 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3727 let mut new_selections = Vec::with_capacity(selections.len());
3728 let mut new_autoclose_regions = Vec::new();
3729 let snapshot = self.buffer.read(cx).read(cx);
3730 let mut clear_linked_edit_ranges = false;
3731
3732 for (selection, autoclose_region) in
3733 self.selections_with_autoclose_regions(selections, &snapshot)
3734 {
3735 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3736 // Determine if the inserted text matches the opening or closing
3737 // bracket of any of this language's bracket pairs.
3738 let mut bracket_pair = None;
3739 let mut is_bracket_pair_start = false;
3740 let mut is_bracket_pair_end = false;
3741 if !text.is_empty() {
3742 let mut bracket_pair_matching_end = None;
3743 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3744 // and they are removing the character that triggered IME popup.
3745 for (pair, enabled) in scope.brackets() {
3746 if !pair.close && !pair.surround {
3747 continue;
3748 }
3749
3750 if enabled && pair.start.ends_with(text.as_ref()) {
3751 let prefix_len = pair.start.len() - text.len();
3752 let preceding_text_matches_prefix = prefix_len == 0
3753 || (selection.start.column >= (prefix_len as u32)
3754 && snapshot.contains_str_at(
3755 Point::new(
3756 selection.start.row,
3757 selection.start.column - (prefix_len as u32),
3758 ),
3759 &pair.start[..prefix_len],
3760 ));
3761 if preceding_text_matches_prefix {
3762 bracket_pair = Some(pair.clone());
3763 is_bracket_pair_start = true;
3764 break;
3765 }
3766 }
3767 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3768 {
3769 // take first bracket pair matching end, but don't break in case a later bracket
3770 // pair matches start
3771 bracket_pair_matching_end = Some(pair.clone());
3772 }
3773 }
3774 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3775 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3776 is_bracket_pair_end = true;
3777 }
3778 }
3779
3780 if let Some(bracket_pair) = bracket_pair {
3781 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3782 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3783 let auto_surround =
3784 self.use_auto_surround && snapshot_settings.use_auto_surround;
3785 if selection.is_empty() {
3786 if is_bracket_pair_start {
3787 // If the inserted text is a suffix of an opening bracket and the
3788 // selection is preceded by the rest of the opening bracket, then
3789 // insert the closing bracket.
3790 let following_text_allows_autoclose = snapshot
3791 .chars_at(selection.start)
3792 .next()
3793 .map_or(true, |c| scope.should_autoclose_before(c));
3794
3795 let preceding_text_allows_autoclose = selection.start.column == 0
3796 || snapshot.reversed_chars_at(selection.start).next().map_or(
3797 true,
3798 |c| {
3799 bracket_pair.start != bracket_pair.end
3800 || !snapshot
3801 .char_classifier_at(selection.start)
3802 .is_word(c)
3803 },
3804 );
3805
3806 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3807 && bracket_pair.start.len() == 1
3808 {
3809 let target = bracket_pair.start.chars().next().unwrap();
3810 let current_line_count = snapshot
3811 .reversed_chars_at(selection.start)
3812 .take_while(|&c| c != '\n')
3813 .filter(|&c| c == target)
3814 .count();
3815 current_line_count % 2 == 1
3816 } else {
3817 false
3818 };
3819
3820 if autoclose
3821 && bracket_pair.close
3822 && following_text_allows_autoclose
3823 && preceding_text_allows_autoclose
3824 && !is_closing_quote
3825 {
3826 let anchor = snapshot.anchor_before(selection.end);
3827 new_selections.push((selection.map(|_| anchor), text.len()));
3828 new_autoclose_regions.push((
3829 anchor,
3830 text.len(),
3831 selection.id,
3832 bracket_pair.clone(),
3833 ));
3834 edits.push((
3835 selection.range(),
3836 format!("{}{}", text, bracket_pair.end).into(),
3837 ));
3838 bracket_inserted = true;
3839 continue;
3840 }
3841 }
3842
3843 if let Some(region) = autoclose_region {
3844 // If the selection is followed by an auto-inserted closing bracket,
3845 // then don't insert that closing bracket again; just move the selection
3846 // past the closing bracket.
3847 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3848 && text.as_ref() == region.pair.end.as_str();
3849 if should_skip {
3850 let anchor = snapshot.anchor_after(selection.end);
3851 new_selections
3852 .push((selection.map(|_| anchor), region.pair.end.len()));
3853 continue;
3854 }
3855 }
3856
3857 let always_treat_brackets_as_autoclosed = snapshot
3858 .language_settings_at(selection.start, cx)
3859 .always_treat_brackets_as_autoclosed;
3860 if always_treat_brackets_as_autoclosed
3861 && is_bracket_pair_end
3862 && snapshot.contains_str_at(selection.end, text.as_ref())
3863 {
3864 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3865 // and the inserted text is a closing bracket and the selection is followed
3866 // by the closing bracket then move the selection past the closing bracket.
3867 let anchor = snapshot.anchor_after(selection.end);
3868 new_selections.push((selection.map(|_| anchor), text.len()));
3869 continue;
3870 }
3871 }
3872 // If an opening bracket is 1 character long and is typed while
3873 // text is selected, then surround that text with the bracket pair.
3874 else if auto_surround
3875 && bracket_pair.surround
3876 && is_bracket_pair_start
3877 && bracket_pair.start.chars().count() == 1
3878 {
3879 edits.push((selection.start..selection.start, text.clone()));
3880 edits.push((
3881 selection.end..selection.end,
3882 bracket_pair.end.as_str().into(),
3883 ));
3884 bracket_inserted = true;
3885 new_selections.push((
3886 Selection {
3887 id: selection.id,
3888 start: snapshot.anchor_after(selection.start),
3889 end: snapshot.anchor_before(selection.end),
3890 reversed: selection.reversed,
3891 goal: selection.goal,
3892 },
3893 0,
3894 ));
3895 continue;
3896 }
3897 }
3898 }
3899
3900 if self.auto_replace_emoji_shortcode
3901 && selection.is_empty()
3902 && text.as_ref().ends_with(':')
3903 {
3904 if let Some(possible_emoji_short_code) =
3905 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3906 {
3907 if !possible_emoji_short_code.is_empty() {
3908 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3909 let emoji_shortcode_start = Point::new(
3910 selection.start.row,
3911 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3912 );
3913
3914 // Remove shortcode from buffer
3915 edits.push((
3916 emoji_shortcode_start..selection.start,
3917 "".to_string().into(),
3918 ));
3919 new_selections.push((
3920 Selection {
3921 id: selection.id,
3922 start: snapshot.anchor_after(emoji_shortcode_start),
3923 end: snapshot.anchor_before(selection.start),
3924 reversed: selection.reversed,
3925 goal: selection.goal,
3926 },
3927 0,
3928 ));
3929
3930 // Insert emoji
3931 let selection_start_anchor = snapshot.anchor_after(selection.start);
3932 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3933 edits.push((selection.start..selection.end, emoji.to_string().into()));
3934
3935 continue;
3936 }
3937 }
3938 }
3939 }
3940
3941 // If not handling any auto-close operation, then just replace the selected
3942 // text with the given input and move the selection to the end of the
3943 // newly inserted text.
3944 let anchor = snapshot.anchor_after(selection.end);
3945 if !self.linked_edit_ranges.is_empty() {
3946 let start_anchor = snapshot.anchor_before(selection.start);
3947
3948 let is_word_char = text.chars().next().map_or(true, |char| {
3949 let classifier = snapshot
3950 .char_classifier_at(start_anchor.to_offset(&snapshot))
3951 .ignore_punctuation(true);
3952 classifier.is_word(char)
3953 });
3954
3955 if is_word_char {
3956 if let Some(ranges) = self
3957 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3958 {
3959 for (buffer, edits) in ranges {
3960 linked_edits
3961 .entry(buffer.clone())
3962 .or_default()
3963 .extend(edits.into_iter().map(|range| (range, text.clone())));
3964 }
3965 }
3966 } else {
3967 clear_linked_edit_ranges = true;
3968 }
3969 }
3970
3971 new_selections.push((selection.map(|_| anchor), 0));
3972 edits.push((selection.start..selection.end, text.clone()));
3973 }
3974
3975 drop(snapshot);
3976
3977 self.transact(window, cx, |this, window, cx| {
3978 if clear_linked_edit_ranges {
3979 this.linked_edit_ranges.clear();
3980 }
3981 let initial_buffer_versions =
3982 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3983
3984 this.buffer.update(cx, |buffer, cx| {
3985 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3986 });
3987 for (buffer, edits) in linked_edits {
3988 buffer.update(cx, |buffer, cx| {
3989 let snapshot = buffer.snapshot();
3990 let edits = edits
3991 .into_iter()
3992 .map(|(range, text)| {
3993 use text::ToPoint as TP;
3994 let end_point = TP::to_point(&range.end, &snapshot);
3995 let start_point = TP::to_point(&range.start, &snapshot);
3996 (start_point..end_point, text)
3997 })
3998 .sorted_by_key(|(range, _)| range.start);
3999 buffer.edit(edits, None, cx);
4000 })
4001 }
4002 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4003 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4004 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4005 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4006 .zip(new_selection_deltas)
4007 .map(|(selection, delta)| Selection {
4008 id: selection.id,
4009 start: selection.start + delta,
4010 end: selection.end + delta,
4011 reversed: selection.reversed,
4012 goal: SelectionGoal::None,
4013 })
4014 .collect::<Vec<_>>();
4015
4016 let mut i = 0;
4017 for (position, delta, selection_id, pair) in new_autoclose_regions {
4018 let position = position.to_offset(&map.buffer_snapshot) + delta;
4019 let start = map.buffer_snapshot.anchor_before(position);
4020 let end = map.buffer_snapshot.anchor_after(position);
4021 while let Some(existing_state) = this.autoclose_regions.get(i) {
4022 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4023 Ordering::Less => i += 1,
4024 Ordering::Greater => break,
4025 Ordering::Equal => {
4026 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4027 Ordering::Less => i += 1,
4028 Ordering::Equal => break,
4029 Ordering::Greater => break,
4030 }
4031 }
4032 }
4033 }
4034 this.autoclose_regions.insert(
4035 i,
4036 AutocloseRegion {
4037 selection_id,
4038 range: start..end,
4039 pair,
4040 },
4041 );
4042 }
4043
4044 let had_active_inline_completion = this.has_active_inline_completion();
4045 this.change_selections(
4046 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4047 window,
4048 cx,
4049 |s| s.select(new_selections),
4050 );
4051
4052 if !bracket_inserted {
4053 if let Some(on_type_format_task) =
4054 this.trigger_on_type_formatting(text.to_string(), window, cx)
4055 {
4056 on_type_format_task.detach_and_log_err(cx);
4057 }
4058 }
4059
4060 let editor_settings = EditorSettings::get_global(cx);
4061 if bracket_inserted
4062 && (editor_settings.auto_signature_help
4063 || editor_settings.show_signature_help_after_edits)
4064 {
4065 this.show_signature_help(&ShowSignatureHelp, window, cx);
4066 }
4067
4068 let trigger_in_words =
4069 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4070 if this.hard_wrap.is_some() {
4071 let latest: Range<Point> = this.selections.newest(cx).range();
4072 if latest.is_empty()
4073 && this
4074 .buffer()
4075 .read(cx)
4076 .snapshot(cx)
4077 .line_len(MultiBufferRow(latest.start.row))
4078 == latest.start.column
4079 {
4080 this.rewrap_impl(
4081 RewrapOptions {
4082 override_language_settings: true,
4083 preserve_existing_whitespace: true,
4084 },
4085 cx,
4086 )
4087 }
4088 }
4089 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4090 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4091 this.refresh_inline_completion(true, false, window, cx);
4092 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4093 });
4094 }
4095
4096 fn find_possible_emoji_shortcode_at_position(
4097 snapshot: &MultiBufferSnapshot,
4098 position: Point,
4099 ) -> Option<String> {
4100 let mut chars = Vec::new();
4101 let mut found_colon = false;
4102 for char in snapshot.reversed_chars_at(position).take(100) {
4103 // Found a possible emoji shortcode in the middle of the buffer
4104 if found_colon {
4105 if char.is_whitespace() {
4106 chars.reverse();
4107 return Some(chars.iter().collect());
4108 }
4109 // If the previous character is not a whitespace, we are in the middle of a word
4110 // and we only want to complete the shortcode if the word is made up of other emojis
4111 let mut containing_word = String::new();
4112 for ch in snapshot
4113 .reversed_chars_at(position)
4114 .skip(chars.len() + 1)
4115 .take(100)
4116 {
4117 if ch.is_whitespace() {
4118 break;
4119 }
4120 containing_word.push(ch);
4121 }
4122 let containing_word = containing_word.chars().rev().collect::<String>();
4123 if util::word_consists_of_emojis(containing_word.as_str()) {
4124 chars.reverse();
4125 return Some(chars.iter().collect());
4126 }
4127 }
4128
4129 if char.is_whitespace() || !char.is_ascii() {
4130 return None;
4131 }
4132 if char == ':' {
4133 found_colon = true;
4134 } else {
4135 chars.push(char);
4136 }
4137 }
4138 // Found a possible emoji shortcode at the beginning of the buffer
4139 chars.reverse();
4140 Some(chars.iter().collect())
4141 }
4142
4143 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4144 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4145 self.transact(window, cx, |this, window, cx| {
4146 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4147 let selections = this.selections.all::<usize>(cx);
4148 let multi_buffer = this.buffer.read(cx);
4149 let buffer = multi_buffer.snapshot(cx);
4150 selections
4151 .iter()
4152 .map(|selection| {
4153 let start_point = selection.start.to_point(&buffer);
4154 let mut existing_indent =
4155 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4156 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4157 let start = selection.start;
4158 let end = selection.end;
4159 let selection_is_empty = start == end;
4160 let language_scope = buffer.language_scope_at(start);
4161 let (
4162 comment_delimiter,
4163 doc_delimiter,
4164 insert_extra_newline,
4165 indent_on_newline,
4166 indent_on_extra_newline,
4167 ) = if let Some(language) = &language_scope {
4168 let mut insert_extra_newline =
4169 insert_extra_newline_brackets(&buffer, start..end, language)
4170 || insert_extra_newline_tree_sitter(&buffer, start..end);
4171
4172 // Comment extension on newline is allowed only for cursor selections
4173 let comment_delimiter = maybe!({
4174 if !selection_is_empty {
4175 return None;
4176 }
4177
4178 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4179 return None;
4180 }
4181
4182 let delimiters = language.line_comment_prefixes();
4183 let max_len_of_delimiter =
4184 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4185 let (snapshot, range) =
4186 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4187
4188 let num_of_whitespaces = snapshot
4189 .chars_for_range(range.clone())
4190 .take_while(|c| c.is_whitespace())
4191 .count();
4192 let comment_candidate = snapshot
4193 .chars_for_range(range)
4194 .skip(num_of_whitespaces)
4195 .take(max_len_of_delimiter)
4196 .collect::<String>();
4197 let (delimiter, trimmed_len) = delimiters
4198 .iter()
4199 .filter_map(|delimiter| {
4200 let prefix = delimiter.trim_end();
4201 if comment_candidate.starts_with(prefix) {
4202 Some((delimiter, prefix.len()))
4203 } else {
4204 None
4205 }
4206 })
4207 .max_by_key(|(_, len)| *len)?;
4208
4209 let cursor_is_placed_after_comment_marker =
4210 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4211 if cursor_is_placed_after_comment_marker {
4212 Some(delimiter.clone())
4213 } else {
4214 None
4215 }
4216 });
4217
4218 let mut indent_on_newline = IndentSize::spaces(0);
4219 let mut indent_on_extra_newline = IndentSize::spaces(0);
4220
4221 let doc_delimiter = maybe!({
4222 if !selection_is_empty {
4223 return None;
4224 }
4225
4226 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4227 return None;
4228 }
4229
4230 let DocumentationConfig {
4231 start: start_tag,
4232 end: end_tag,
4233 prefix: delimiter,
4234 tab_size: len,
4235 } = language.documentation()?;
4236
4237 let is_within_block_comment = buffer
4238 .language_scope_at(start_point)
4239 .is_some_and(|scope| scope.override_name() == Some("comment"));
4240 if !is_within_block_comment {
4241 return None;
4242 }
4243
4244 let (snapshot, range) =
4245 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4246
4247 let num_of_whitespaces = snapshot
4248 .chars_for_range(range.clone())
4249 .take_while(|c| c.is_whitespace())
4250 .count();
4251
4252 // 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.
4253 let column = start_point.column;
4254 let cursor_is_after_start_tag = {
4255 let start_tag_len = start_tag.len();
4256 let start_tag_line = snapshot
4257 .chars_for_range(range.clone())
4258 .skip(num_of_whitespaces)
4259 .take(start_tag_len)
4260 .collect::<String>();
4261 if start_tag_line.starts_with(start_tag.as_ref()) {
4262 num_of_whitespaces + start_tag_len <= column as usize
4263 } else {
4264 false
4265 }
4266 };
4267
4268 let cursor_is_after_delimiter = {
4269 let delimiter_trim = delimiter.trim_end();
4270 let delimiter_line = snapshot
4271 .chars_for_range(range.clone())
4272 .skip(num_of_whitespaces)
4273 .take(delimiter_trim.len())
4274 .collect::<String>();
4275 if delimiter_line.starts_with(delimiter_trim) {
4276 num_of_whitespaces + delimiter_trim.len() <= column as usize
4277 } else {
4278 false
4279 }
4280 };
4281
4282 let cursor_is_before_end_tag_if_exists = {
4283 let mut char_position = 0u32;
4284 let mut end_tag_offset = None;
4285
4286 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4287 if let Some(byte_pos) = chunk.find(&**end_tag) {
4288 let chars_before_match =
4289 chunk[..byte_pos].chars().count() as u32;
4290 end_tag_offset =
4291 Some(char_position + chars_before_match);
4292 break 'outer;
4293 }
4294 char_position += chunk.chars().count() as u32;
4295 }
4296
4297 if let Some(end_tag_offset) = end_tag_offset {
4298 let cursor_is_before_end_tag = column <= end_tag_offset;
4299 if cursor_is_after_start_tag {
4300 if cursor_is_before_end_tag {
4301 insert_extra_newline = true;
4302 }
4303 let cursor_is_at_start_of_end_tag =
4304 column == end_tag_offset;
4305 if cursor_is_at_start_of_end_tag {
4306 indent_on_extra_newline.len = (*len).into();
4307 }
4308 }
4309 cursor_is_before_end_tag
4310 } else {
4311 true
4312 }
4313 };
4314
4315 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4316 && cursor_is_before_end_tag_if_exists
4317 {
4318 if cursor_is_after_start_tag {
4319 indent_on_newline.len = (*len).into();
4320 }
4321 Some(delimiter.clone())
4322 } else {
4323 None
4324 }
4325 });
4326
4327 (
4328 comment_delimiter,
4329 doc_delimiter,
4330 insert_extra_newline,
4331 indent_on_newline,
4332 indent_on_extra_newline,
4333 )
4334 } else {
4335 (
4336 None,
4337 None,
4338 false,
4339 IndentSize::default(),
4340 IndentSize::default(),
4341 )
4342 };
4343
4344 let prevent_auto_indent = doc_delimiter.is_some();
4345 let delimiter = comment_delimiter.or(doc_delimiter);
4346
4347 let capacity_for_delimiter =
4348 delimiter.as_deref().map(str::len).unwrap_or_default();
4349 let mut new_text = String::with_capacity(
4350 1 + capacity_for_delimiter
4351 + existing_indent.len as usize
4352 + indent_on_newline.len as usize
4353 + indent_on_extra_newline.len as usize,
4354 );
4355 new_text.push('\n');
4356 new_text.extend(existing_indent.chars());
4357 new_text.extend(indent_on_newline.chars());
4358
4359 if let Some(delimiter) = &delimiter {
4360 new_text.push_str(delimiter);
4361 }
4362
4363 if insert_extra_newline {
4364 new_text.push('\n');
4365 new_text.extend(existing_indent.chars());
4366 new_text.extend(indent_on_extra_newline.chars());
4367 }
4368
4369 let anchor = buffer.anchor_after(end);
4370 let new_selection = selection.map(|_| anchor);
4371 (
4372 ((start..end, new_text), prevent_auto_indent),
4373 (insert_extra_newline, new_selection),
4374 )
4375 })
4376 .unzip()
4377 };
4378
4379 let mut auto_indent_edits = Vec::new();
4380 let mut edits = Vec::new();
4381 for (edit, prevent_auto_indent) in edits_with_flags {
4382 if prevent_auto_indent {
4383 edits.push(edit);
4384 } else {
4385 auto_indent_edits.push(edit);
4386 }
4387 }
4388 if !edits.is_empty() {
4389 this.edit(edits, cx);
4390 }
4391 if !auto_indent_edits.is_empty() {
4392 this.edit_with_autoindent(auto_indent_edits, cx);
4393 }
4394
4395 let buffer = this.buffer.read(cx).snapshot(cx);
4396 let new_selections = selection_info
4397 .into_iter()
4398 .map(|(extra_newline_inserted, new_selection)| {
4399 let mut cursor = new_selection.end.to_point(&buffer);
4400 if extra_newline_inserted {
4401 cursor.row -= 1;
4402 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4403 }
4404 new_selection.map(|_| cursor)
4405 })
4406 .collect();
4407
4408 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4409 s.select(new_selections)
4410 });
4411 this.refresh_inline_completion(true, false, window, cx);
4412 });
4413 }
4414
4415 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4416 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4417
4418 let buffer = self.buffer.read(cx);
4419 let snapshot = buffer.snapshot(cx);
4420
4421 let mut edits = Vec::new();
4422 let mut rows = Vec::new();
4423
4424 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4425 let cursor = selection.head();
4426 let row = cursor.row;
4427
4428 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4429
4430 let newline = "\n".to_string();
4431 edits.push((start_of_line..start_of_line, newline));
4432
4433 rows.push(row + rows_inserted as u32);
4434 }
4435
4436 self.transact(window, cx, |editor, window, cx| {
4437 editor.edit(edits, cx);
4438
4439 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4440 let mut index = 0;
4441 s.move_cursors_with(|map, _, _| {
4442 let row = rows[index];
4443 index += 1;
4444
4445 let point = Point::new(row, 0);
4446 let boundary = map.next_line_boundary(point).1;
4447 let clipped = map.clip_point(boundary, Bias::Left);
4448
4449 (clipped, SelectionGoal::None)
4450 });
4451 });
4452
4453 let mut indent_edits = Vec::new();
4454 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4455 for row in rows {
4456 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4457 for (row, indent) in indents {
4458 if indent.len == 0 {
4459 continue;
4460 }
4461
4462 let text = match indent.kind {
4463 IndentKind::Space => " ".repeat(indent.len as usize),
4464 IndentKind::Tab => "\t".repeat(indent.len as usize),
4465 };
4466 let point = Point::new(row.0, 0);
4467 indent_edits.push((point..point, text));
4468 }
4469 }
4470 editor.edit(indent_edits, cx);
4471 });
4472 }
4473
4474 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4475 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4476
4477 let buffer = self.buffer.read(cx);
4478 let snapshot = buffer.snapshot(cx);
4479
4480 let mut edits = Vec::new();
4481 let mut rows = Vec::new();
4482 let mut rows_inserted = 0;
4483
4484 for selection in self.selections.all_adjusted(cx) {
4485 let cursor = selection.head();
4486 let row = cursor.row;
4487
4488 let point = Point::new(row + 1, 0);
4489 let start_of_line = snapshot.clip_point(point, Bias::Left);
4490
4491 let newline = "\n".to_string();
4492 edits.push((start_of_line..start_of_line, newline));
4493
4494 rows_inserted += 1;
4495 rows.push(row + rows_inserted);
4496 }
4497
4498 self.transact(window, cx, |editor, window, cx| {
4499 editor.edit(edits, cx);
4500
4501 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4502 let mut index = 0;
4503 s.move_cursors_with(|map, _, _| {
4504 let row = rows[index];
4505 index += 1;
4506
4507 let point = Point::new(row, 0);
4508 let boundary = map.next_line_boundary(point).1;
4509 let clipped = map.clip_point(boundary, Bias::Left);
4510
4511 (clipped, SelectionGoal::None)
4512 });
4513 });
4514
4515 let mut indent_edits = Vec::new();
4516 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4517 for row in rows {
4518 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4519 for (row, indent) in indents {
4520 if indent.len == 0 {
4521 continue;
4522 }
4523
4524 let text = match indent.kind {
4525 IndentKind::Space => " ".repeat(indent.len as usize),
4526 IndentKind::Tab => "\t".repeat(indent.len as usize),
4527 };
4528 let point = Point::new(row.0, 0);
4529 indent_edits.push((point..point, text));
4530 }
4531 }
4532 editor.edit(indent_edits, cx);
4533 });
4534 }
4535
4536 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4537 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4538 original_indent_columns: Vec::new(),
4539 });
4540 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4541 }
4542
4543 fn insert_with_autoindent_mode(
4544 &mut self,
4545 text: &str,
4546 autoindent_mode: Option<AutoindentMode>,
4547 window: &mut Window,
4548 cx: &mut Context<Self>,
4549 ) {
4550 if self.read_only(cx) {
4551 return;
4552 }
4553
4554 let text: Arc<str> = text.into();
4555 self.transact(window, cx, |this, window, cx| {
4556 let old_selections = this.selections.all_adjusted(cx);
4557 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4558 let anchors = {
4559 let snapshot = buffer.read(cx);
4560 old_selections
4561 .iter()
4562 .map(|s| {
4563 let anchor = snapshot.anchor_after(s.head());
4564 s.map(|_| anchor)
4565 })
4566 .collect::<Vec<_>>()
4567 };
4568 buffer.edit(
4569 old_selections
4570 .iter()
4571 .map(|s| (s.start..s.end, text.clone())),
4572 autoindent_mode,
4573 cx,
4574 );
4575 anchors
4576 });
4577
4578 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4579 s.select_anchors(selection_anchors);
4580 });
4581
4582 cx.notify();
4583 });
4584 }
4585
4586 fn trigger_completion_on_input(
4587 &mut self,
4588 text: &str,
4589 trigger_in_words: bool,
4590 window: &mut Window,
4591 cx: &mut Context<Self>,
4592 ) {
4593 let completions_source = self
4594 .context_menu
4595 .borrow()
4596 .as_ref()
4597 .and_then(|menu| match menu {
4598 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4599 CodeContextMenu::CodeActions(_) => None,
4600 });
4601
4602 match completions_source {
4603 Some(CompletionsMenuSource::Words) => {
4604 self.show_word_completions(&ShowWordCompletions, window, cx)
4605 }
4606 Some(CompletionsMenuSource::Normal)
4607 | Some(CompletionsMenuSource::SnippetChoices)
4608 | None
4609 if self.is_completion_trigger(
4610 text,
4611 trigger_in_words,
4612 completions_source.is_some(),
4613 cx,
4614 ) =>
4615 {
4616 self.show_completions(
4617 &ShowCompletions {
4618 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4619 },
4620 window,
4621 cx,
4622 )
4623 }
4624 _ => {
4625 self.hide_context_menu(window, cx);
4626 }
4627 }
4628 }
4629
4630 fn is_completion_trigger(
4631 &self,
4632 text: &str,
4633 trigger_in_words: bool,
4634 menu_is_open: bool,
4635 cx: &mut Context<Self>,
4636 ) -> bool {
4637 let position = self.selections.newest_anchor().head();
4638 let multibuffer = self.buffer.read(cx);
4639 let Some(buffer) = position
4640 .buffer_id
4641 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4642 else {
4643 return false;
4644 };
4645
4646 if let Some(completion_provider) = &self.completion_provider {
4647 completion_provider.is_completion_trigger(
4648 &buffer,
4649 position.text_anchor,
4650 text,
4651 trigger_in_words,
4652 menu_is_open,
4653 cx,
4654 )
4655 } else {
4656 false
4657 }
4658 }
4659
4660 /// If any empty selections is touching the start of its innermost containing autoclose
4661 /// region, expand it to select the brackets.
4662 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4663 let selections = self.selections.all::<usize>(cx);
4664 let buffer = self.buffer.read(cx).read(cx);
4665 let new_selections = self
4666 .selections_with_autoclose_regions(selections, &buffer)
4667 .map(|(mut selection, region)| {
4668 if !selection.is_empty() {
4669 return selection;
4670 }
4671
4672 if let Some(region) = region {
4673 let mut range = region.range.to_offset(&buffer);
4674 if selection.start == range.start && range.start >= region.pair.start.len() {
4675 range.start -= region.pair.start.len();
4676 if buffer.contains_str_at(range.start, ®ion.pair.start)
4677 && buffer.contains_str_at(range.end, ®ion.pair.end)
4678 {
4679 range.end += region.pair.end.len();
4680 selection.start = range.start;
4681 selection.end = range.end;
4682
4683 return selection;
4684 }
4685 }
4686 }
4687
4688 let always_treat_brackets_as_autoclosed = buffer
4689 .language_settings_at(selection.start, cx)
4690 .always_treat_brackets_as_autoclosed;
4691
4692 if !always_treat_brackets_as_autoclosed {
4693 return selection;
4694 }
4695
4696 if let Some(scope) = buffer.language_scope_at(selection.start) {
4697 for (pair, enabled) in scope.brackets() {
4698 if !enabled || !pair.close {
4699 continue;
4700 }
4701
4702 if buffer.contains_str_at(selection.start, &pair.end) {
4703 let pair_start_len = pair.start.len();
4704 if buffer.contains_str_at(
4705 selection.start.saturating_sub(pair_start_len),
4706 &pair.start,
4707 ) {
4708 selection.start -= pair_start_len;
4709 selection.end += pair.end.len();
4710
4711 return selection;
4712 }
4713 }
4714 }
4715 }
4716
4717 selection
4718 })
4719 .collect();
4720
4721 drop(buffer);
4722 self.change_selections(None, window, cx, |selections| {
4723 selections.select(new_selections)
4724 });
4725 }
4726
4727 /// Iterate the given selections, and for each one, find the smallest surrounding
4728 /// autoclose region. This uses the ordering of the selections and the autoclose
4729 /// regions to avoid repeated comparisons.
4730 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4731 &'a self,
4732 selections: impl IntoIterator<Item = Selection<D>>,
4733 buffer: &'a MultiBufferSnapshot,
4734 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4735 let mut i = 0;
4736 let mut regions = self.autoclose_regions.as_slice();
4737 selections.into_iter().map(move |selection| {
4738 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4739
4740 let mut enclosing = None;
4741 while let Some(pair_state) = regions.get(i) {
4742 if pair_state.range.end.to_offset(buffer) < range.start {
4743 regions = ®ions[i + 1..];
4744 i = 0;
4745 } else if pair_state.range.start.to_offset(buffer) > range.end {
4746 break;
4747 } else {
4748 if pair_state.selection_id == selection.id {
4749 enclosing = Some(pair_state);
4750 }
4751 i += 1;
4752 }
4753 }
4754
4755 (selection, enclosing)
4756 })
4757 }
4758
4759 /// Remove any autoclose regions that no longer contain their selection.
4760 fn invalidate_autoclose_regions(
4761 &mut self,
4762 mut selections: &[Selection<Anchor>],
4763 buffer: &MultiBufferSnapshot,
4764 ) {
4765 self.autoclose_regions.retain(|state| {
4766 let mut i = 0;
4767 while let Some(selection) = selections.get(i) {
4768 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4769 selections = &selections[1..];
4770 continue;
4771 }
4772 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4773 break;
4774 }
4775 if selection.id == state.selection_id {
4776 return true;
4777 } else {
4778 i += 1;
4779 }
4780 }
4781 false
4782 });
4783 }
4784
4785 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4786 let offset = position.to_offset(buffer);
4787 let (word_range, kind) = buffer.surrounding_word(offset, true);
4788 if offset > word_range.start && kind == Some(CharKind::Word) {
4789 Some(
4790 buffer
4791 .text_for_range(word_range.start..offset)
4792 .collect::<String>(),
4793 )
4794 } else {
4795 None
4796 }
4797 }
4798
4799 pub fn toggle_inline_values(
4800 &mut self,
4801 _: &ToggleInlineValues,
4802 _: &mut Window,
4803 cx: &mut Context<Self>,
4804 ) {
4805 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4806
4807 self.refresh_inline_values(cx);
4808 }
4809
4810 pub fn toggle_inlay_hints(
4811 &mut self,
4812 _: &ToggleInlayHints,
4813 _: &mut Window,
4814 cx: &mut Context<Self>,
4815 ) {
4816 self.refresh_inlay_hints(
4817 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4818 cx,
4819 );
4820 }
4821
4822 pub fn inlay_hints_enabled(&self) -> bool {
4823 self.inlay_hint_cache.enabled
4824 }
4825
4826 pub fn inline_values_enabled(&self) -> bool {
4827 self.inline_value_cache.enabled
4828 }
4829
4830 #[cfg(any(test, feature = "test-support"))]
4831 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4832 self.display_map
4833 .read(cx)
4834 .current_inlays()
4835 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4836 .cloned()
4837 .collect()
4838 }
4839
4840 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4841 if self.semantics_provider.is_none() || !self.mode.is_full() {
4842 return;
4843 }
4844
4845 let reason_description = reason.description();
4846 let ignore_debounce = matches!(
4847 reason,
4848 InlayHintRefreshReason::SettingsChange(_)
4849 | InlayHintRefreshReason::Toggle(_)
4850 | InlayHintRefreshReason::ExcerptsRemoved(_)
4851 | InlayHintRefreshReason::ModifiersChanged(_)
4852 );
4853 let (invalidate_cache, required_languages) = match reason {
4854 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4855 match self.inlay_hint_cache.modifiers_override(enabled) {
4856 Some(enabled) => {
4857 if enabled {
4858 (InvalidationStrategy::RefreshRequested, None)
4859 } else {
4860 self.splice_inlays(
4861 &self
4862 .visible_inlay_hints(cx)
4863 .iter()
4864 .map(|inlay| inlay.id)
4865 .collect::<Vec<InlayId>>(),
4866 Vec::new(),
4867 cx,
4868 );
4869 return;
4870 }
4871 }
4872 None => return,
4873 }
4874 }
4875 InlayHintRefreshReason::Toggle(enabled) => {
4876 if self.inlay_hint_cache.toggle(enabled) {
4877 if enabled {
4878 (InvalidationStrategy::RefreshRequested, None)
4879 } else {
4880 self.splice_inlays(
4881 &self
4882 .visible_inlay_hints(cx)
4883 .iter()
4884 .map(|inlay| inlay.id)
4885 .collect::<Vec<InlayId>>(),
4886 Vec::new(),
4887 cx,
4888 );
4889 return;
4890 }
4891 } else {
4892 return;
4893 }
4894 }
4895 InlayHintRefreshReason::SettingsChange(new_settings) => {
4896 match self.inlay_hint_cache.update_settings(
4897 &self.buffer,
4898 new_settings,
4899 self.visible_inlay_hints(cx),
4900 cx,
4901 ) {
4902 ControlFlow::Break(Some(InlaySplice {
4903 to_remove,
4904 to_insert,
4905 })) => {
4906 self.splice_inlays(&to_remove, to_insert, cx);
4907 return;
4908 }
4909 ControlFlow::Break(None) => return,
4910 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4911 }
4912 }
4913 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4914 if let Some(InlaySplice {
4915 to_remove,
4916 to_insert,
4917 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4918 {
4919 self.splice_inlays(&to_remove, to_insert, cx);
4920 }
4921 self.display_map.update(cx, |display_map, _| {
4922 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4923 });
4924 return;
4925 }
4926 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4927 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4928 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4929 }
4930 InlayHintRefreshReason::RefreshRequested => {
4931 (InvalidationStrategy::RefreshRequested, None)
4932 }
4933 };
4934
4935 if let Some(InlaySplice {
4936 to_remove,
4937 to_insert,
4938 }) = self.inlay_hint_cache.spawn_hint_refresh(
4939 reason_description,
4940 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4941 invalidate_cache,
4942 ignore_debounce,
4943 cx,
4944 ) {
4945 self.splice_inlays(&to_remove, to_insert, cx);
4946 }
4947 }
4948
4949 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4950 self.display_map
4951 .read(cx)
4952 .current_inlays()
4953 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4954 .cloned()
4955 .collect()
4956 }
4957
4958 pub fn excerpts_for_inlay_hints_query(
4959 &self,
4960 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4961 cx: &mut Context<Editor>,
4962 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4963 let Some(project) = self.project.as_ref() else {
4964 return HashMap::default();
4965 };
4966 let project = project.read(cx);
4967 let multi_buffer = self.buffer().read(cx);
4968 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4969 let multi_buffer_visible_start = self
4970 .scroll_manager
4971 .anchor()
4972 .anchor
4973 .to_point(&multi_buffer_snapshot);
4974 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4975 multi_buffer_visible_start
4976 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4977 Bias::Left,
4978 );
4979 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4980 multi_buffer_snapshot
4981 .range_to_buffer_ranges(multi_buffer_visible_range)
4982 .into_iter()
4983 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4984 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4985 let buffer_file = project::File::from_dyn(buffer.file())?;
4986 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4987 let worktree_entry = buffer_worktree
4988 .read(cx)
4989 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4990 if worktree_entry.is_ignored {
4991 return None;
4992 }
4993
4994 let language = buffer.language()?;
4995 if let Some(restrict_to_languages) = restrict_to_languages {
4996 if !restrict_to_languages.contains(language) {
4997 return None;
4998 }
4999 }
5000 Some((
5001 excerpt_id,
5002 (
5003 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5004 buffer.version().clone(),
5005 excerpt_visible_range,
5006 ),
5007 ))
5008 })
5009 .collect()
5010 }
5011
5012 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5013 TextLayoutDetails {
5014 text_system: window.text_system().clone(),
5015 editor_style: self.style.clone().unwrap(),
5016 rem_size: window.rem_size(),
5017 scroll_anchor: self.scroll_manager.anchor(),
5018 visible_rows: self.visible_line_count(),
5019 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5020 }
5021 }
5022
5023 pub fn splice_inlays(
5024 &self,
5025 to_remove: &[InlayId],
5026 to_insert: Vec<Inlay>,
5027 cx: &mut Context<Self>,
5028 ) {
5029 self.display_map.update(cx, |display_map, cx| {
5030 display_map.splice_inlays(to_remove, to_insert, cx)
5031 });
5032 cx.notify();
5033 }
5034
5035 fn trigger_on_type_formatting(
5036 &self,
5037 input: String,
5038 window: &mut Window,
5039 cx: &mut Context<Self>,
5040 ) -> Option<Task<Result<()>>> {
5041 if input.len() != 1 {
5042 return None;
5043 }
5044
5045 let project = self.project.as_ref()?;
5046 let position = self.selections.newest_anchor().head();
5047 let (buffer, buffer_position) = self
5048 .buffer
5049 .read(cx)
5050 .text_anchor_for_position(position, cx)?;
5051
5052 let settings = language_settings::language_settings(
5053 buffer
5054 .read(cx)
5055 .language_at(buffer_position)
5056 .map(|l| l.name()),
5057 buffer.read(cx).file(),
5058 cx,
5059 );
5060 if !settings.use_on_type_format {
5061 return None;
5062 }
5063
5064 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5065 // hence we do LSP request & edit on host side only — add formats to host's history.
5066 let push_to_lsp_host_history = true;
5067 // If this is not the host, append its history with new edits.
5068 let push_to_client_history = project.read(cx).is_via_collab();
5069
5070 let on_type_formatting = project.update(cx, |project, cx| {
5071 project.on_type_format(
5072 buffer.clone(),
5073 buffer_position,
5074 input,
5075 push_to_lsp_host_history,
5076 cx,
5077 )
5078 });
5079 Some(cx.spawn_in(window, async move |editor, cx| {
5080 if let Some(transaction) = on_type_formatting.await? {
5081 if push_to_client_history {
5082 buffer
5083 .update(cx, |buffer, _| {
5084 buffer.push_transaction(transaction, Instant::now());
5085 buffer.finalize_last_transaction();
5086 })
5087 .ok();
5088 }
5089 editor.update(cx, |editor, cx| {
5090 editor.refresh_document_highlights(cx);
5091 })?;
5092 }
5093 Ok(())
5094 }))
5095 }
5096
5097 pub fn show_word_completions(
5098 &mut self,
5099 _: &ShowWordCompletions,
5100 window: &mut Window,
5101 cx: &mut Context<Self>,
5102 ) {
5103 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5104 }
5105
5106 pub fn show_completions(
5107 &mut self,
5108 options: &ShowCompletions,
5109 window: &mut Window,
5110 cx: &mut Context<Self>,
5111 ) {
5112 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5113 }
5114
5115 fn open_or_update_completions_menu(
5116 &mut self,
5117 requested_source: Option<CompletionsMenuSource>,
5118 trigger: Option<&str>,
5119 window: &mut Window,
5120 cx: &mut Context<Self>,
5121 ) {
5122 if self.pending_rename.is_some() {
5123 return;
5124 }
5125
5126 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5127
5128 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5129 // inserted and selected. To handle that case, the start of the selection is used so that
5130 // the menu starts with all choices.
5131 let position = self
5132 .selections
5133 .newest_anchor()
5134 .start
5135 .bias_right(&multibuffer_snapshot);
5136 if position.diff_base_anchor.is_some() {
5137 return;
5138 }
5139 let (buffer, buffer_position) =
5140 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5141 output
5142 } else {
5143 return;
5144 };
5145 let buffer_snapshot = buffer.read(cx).snapshot();
5146
5147 let query: Option<Arc<String>> =
5148 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5149
5150 drop(multibuffer_snapshot);
5151
5152 let provider = match requested_source {
5153 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5154 Some(CompletionsMenuSource::Words) => None,
5155 Some(CompletionsMenuSource::SnippetChoices) => {
5156 log::error!("bug: SnippetChoices requested_source is not handled");
5157 None
5158 }
5159 };
5160
5161 let sort_completions = provider
5162 .as_ref()
5163 .map_or(false, |provider| provider.sort_completions());
5164
5165 let filter_completions = provider
5166 .as_ref()
5167 .map_or(true, |provider| provider.filter_completions());
5168
5169 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5170 if filter_completions {
5171 menu.filter(query.clone(), provider.clone(), window, cx);
5172 }
5173 // When `is_incomplete` is false, no need to re-query completions when the current query
5174 // is a suffix of the initial query.
5175 if !menu.is_incomplete {
5176 // If the new query is a suffix of the old query (typing more characters) and
5177 // the previous result was complete, the existing completions can be filtered.
5178 //
5179 // Note that this is always true for snippet completions.
5180 let query_matches = match (&menu.initial_query, &query) {
5181 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5182 (None, _) => true,
5183 _ => false,
5184 };
5185 if query_matches {
5186 let position_matches = if menu.initial_position == position {
5187 true
5188 } else {
5189 let snapshot = self.buffer.read(cx).read(cx);
5190 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5191 };
5192 if position_matches {
5193 return;
5194 }
5195 }
5196 }
5197 };
5198
5199 let trigger_kind = match trigger {
5200 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5201 CompletionTriggerKind::TRIGGER_CHARACTER
5202 }
5203 _ => CompletionTriggerKind::INVOKED,
5204 };
5205 let completion_context = CompletionContext {
5206 trigger_character: trigger.and_then(|trigger| {
5207 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5208 Some(String::from(trigger))
5209 } else {
5210 None
5211 }
5212 }),
5213 trigger_kind,
5214 };
5215
5216 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5217 buffer_snapshot.surrounding_word(buffer_position)
5218 {
5219 let word_to_exclude = buffer_snapshot
5220 .text_for_range(word_range.clone())
5221 .collect::<String>();
5222 (
5223 buffer_snapshot.anchor_before(word_range.start)
5224 ..buffer_snapshot.anchor_after(buffer_position),
5225 Some(word_to_exclude),
5226 )
5227 } else {
5228 (buffer_position..buffer_position, None)
5229 };
5230
5231 let language = buffer_snapshot
5232 .language_at(buffer_position)
5233 .map(|language| language.name());
5234
5235 let completion_settings =
5236 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5237
5238 let show_completion_documentation = buffer_snapshot
5239 .settings_at(buffer_position, cx)
5240 .show_completion_documentation;
5241
5242 // The document can be large, so stay in reasonable bounds when searching for words,
5243 // otherwise completion pop-up might be slow to appear.
5244 const WORD_LOOKUP_ROWS: u32 = 5_000;
5245 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5246 let min_word_search = buffer_snapshot.clip_point(
5247 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5248 Bias::Left,
5249 );
5250 let max_word_search = buffer_snapshot.clip_point(
5251 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5252 Bias::Right,
5253 );
5254 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5255 ..buffer_snapshot.point_to_offset(max_word_search);
5256
5257 let skip_digits = query
5258 .as_ref()
5259 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5260
5261 let (mut words, provider_responses) = match &provider {
5262 Some(provider) => {
5263 let provider_responses = provider.completions(
5264 position.excerpt_id,
5265 &buffer,
5266 buffer_position,
5267 completion_context,
5268 window,
5269 cx,
5270 );
5271
5272 let words = match completion_settings.words {
5273 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5274 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5275 .background_spawn(async move {
5276 buffer_snapshot.words_in_range(WordsQuery {
5277 fuzzy_contents: None,
5278 range: word_search_range,
5279 skip_digits,
5280 })
5281 }),
5282 };
5283
5284 (words, provider_responses)
5285 }
5286 None => (
5287 cx.background_spawn(async move {
5288 buffer_snapshot.words_in_range(WordsQuery {
5289 fuzzy_contents: None,
5290 range: word_search_range,
5291 skip_digits,
5292 })
5293 }),
5294 Task::ready(Ok(Vec::new())),
5295 ),
5296 };
5297
5298 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5299
5300 let id = post_inc(&mut self.next_completion_id);
5301 let task = cx.spawn_in(window, async move |editor, cx| {
5302 let Ok(()) = editor.update(cx, |this, _| {
5303 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5304 }) else {
5305 return;
5306 };
5307
5308 // TODO: Ideally completions from different sources would be selectively re-queried, so
5309 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5310 let mut completions = Vec::new();
5311 let mut is_incomplete = false;
5312 if let Some(provider_responses) = provider_responses.await.log_err() {
5313 if !provider_responses.is_empty() {
5314 for response in provider_responses {
5315 completions.extend(response.completions);
5316 is_incomplete = is_incomplete || response.is_incomplete;
5317 }
5318 if completion_settings.words == WordsCompletionMode::Fallback {
5319 words = Task::ready(BTreeMap::default());
5320 }
5321 }
5322 }
5323
5324 let mut words = words.await;
5325 if let Some(word_to_exclude) = &word_to_exclude {
5326 words.remove(word_to_exclude);
5327 }
5328 for lsp_completion in &completions {
5329 words.remove(&lsp_completion.new_text);
5330 }
5331 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5332 replace_range: word_replace_range.clone(),
5333 new_text: word.clone(),
5334 label: CodeLabel::plain(word, None),
5335 icon_path: None,
5336 documentation: None,
5337 source: CompletionSource::BufferWord {
5338 word_range,
5339 resolved: false,
5340 },
5341 insert_text_mode: Some(InsertTextMode::AS_IS),
5342 confirm: None,
5343 }));
5344
5345 let menu = if completions.is_empty() {
5346 None
5347 } else {
5348 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5349 let languages = editor
5350 .workspace
5351 .as_ref()
5352 .and_then(|(workspace, _)| workspace.upgrade())
5353 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5354 let menu = CompletionsMenu::new(
5355 id,
5356 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5357 sort_completions,
5358 show_completion_documentation,
5359 position,
5360 query.clone(),
5361 is_incomplete,
5362 buffer.clone(),
5363 completions.into(),
5364 snippet_sort_order,
5365 languages,
5366 language,
5367 cx,
5368 );
5369
5370 let query = if filter_completions { query } else { None };
5371 let matches_task = if let Some(query) = query {
5372 menu.do_async_filtering(query, cx)
5373 } else {
5374 Task::ready(menu.unfiltered_matches())
5375 };
5376 (menu, matches_task)
5377 }) else {
5378 return;
5379 };
5380
5381 let matches = matches_task.await;
5382
5383 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5384 // Newer menu already set, so exit.
5385 match editor.context_menu.borrow().as_ref() {
5386 Some(CodeContextMenu::Completions(prev_menu)) => {
5387 if prev_menu.id > id {
5388 return;
5389 }
5390 }
5391 _ => {}
5392 };
5393
5394 // Only valid to take prev_menu because it the new menu is immediately set
5395 // below, or the menu is hidden.
5396 match editor.context_menu.borrow_mut().take() {
5397 Some(CodeContextMenu::Completions(prev_menu)) => {
5398 let position_matches =
5399 if prev_menu.initial_position == menu.initial_position {
5400 true
5401 } else {
5402 let snapshot = editor.buffer.read(cx).read(cx);
5403 prev_menu.initial_position.to_offset(&snapshot)
5404 == menu.initial_position.to_offset(&snapshot)
5405 };
5406 if position_matches {
5407 // Preserve markdown cache before `set_filter_results` because it will
5408 // try to populate the documentation cache.
5409 menu.preserve_markdown_cache(prev_menu);
5410 }
5411 }
5412 _ => {}
5413 };
5414
5415 menu.set_filter_results(matches, provider, window, cx);
5416 }) else {
5417 return;
5418 };
5419
5420 menu.visible().then_some(menu)
5421 };
5422
5423 editor
5424 .update_in(cx, |editor, window, cx| {
5425 if editor.focus_handle.is_focused(window) {
5426 if let Some(menu) = menu {
5427 *editor.context_menu.borrow_mut() =
5428 Some(CodeContextMenu::Completions(menu));
5429
5430 crate::hover_popover::hide_hover(editor, cx);
5431 if editor.show_edit_predictions_in_menu() {
5432 editor.update_visible_inline_completion(window, cx);
5433 } else {
5434 editor.discard_inline_completion(false, cx);
5435 }
5436
5437 cx.notify();
5438 return;
5439 }
5440 }
5441
5442 if editor.completion_tasks.len() <= 1 {
5443 // If there are no more completion tasks and the last menu was empty, we should hide it.
5444 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5445 // If it was already hidden and we don't show inline completions in the menu, we should
5446 // also show the inline-completion when available.
5447 if was_hidden && editor.show_edit_predictions_in_menu() {
5448 editor.update_visible_inline_completion(window, cx);
5449 }
5450 }
5451 })
5452 .ok();
5453 });
5454
5455 self.completion_tasks.push((id, task));
5456 }
5457
5458 #[cfg(feature = "test-support")]
5459 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5460 let menu = self.context_menu.borrow();
5461 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5462 let completions = menu.completions.borrow();
5463 Some(completions.to_vec())
5464 } else {
5465 None
5466 }
5467 }
5468
5469 pub fn with_completions_menu_matching_id<R>(
5470 &self,
5471 id: CompletionId,
5472 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5473 ) -> R {
5474 let mut context_menu = self.context_menu.borrow_mut();
5475 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5476 return f(None);
5477 };
5478 if completions_menu.id != id {
5479 return f(None);
5480 }
5481 f(Some(completions_menu))
5482 }
5483
5484 pub fn confirm_completion(
5485 &mut self,
5486 action: &ConfirmCompletion,
5487 window: &mut Window,
5488 cx: &mut Context<Self>,
5489 ) -> Option<Task<Result<()>>> {
5490 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5491 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5492 }
5493
5494 pub fn confirm_completion_insert(
5495 &mut self,
5496 _: &ConfirmCompletionInsert,
5497 window: &mut Window,
5498 cx: &mut Context<Self>,
5499 ) -> Option<Task<Result<()>>> {
5500 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5501 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5502 }
5503
5504 pub fn confirm_completion_replace(
5505 &mut self,
5506 _: &ConfirmCompletionReplace,
5507 window: &mut Window,
5508 cx: &mut Context<Self>,
5509 ) -> Option<Task<Result<()>>> {
5510 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5511 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5512 }
5513
5514 pub fn compose_completion(
5515 &mut self,
5516 action: &ComposeCompletion,
5517 window: &mut Window,
5518 cx: &mut Context<Self>,
5519 ) -> Option<Task<Result<()>>> {
5520 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5521 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5522 }
5523
5524 fn do_completion(
5525 &mut self,
5526 item_ix: Option<usize>,
5527 intent: CompletionIntent,
5528 window: &mut Window,
5529 cx: &mut Context<Editor>,
5530 ) -> Option<Task<Result<()>>> {
5531 use language::ToOffset as _;
5532
5533 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5534 else {
5535 return None;
5536 };
5537
5538 let candidate_id = {
5539 let entries = completions_menu.entries.borrow();
5540 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5541 if self.show_edit_predictions_in_menu() {
5542 self.discard_inline_completion(true, cx);
5543 }
5544 mat.candidate_id
5545 };
5546
5547 let completion = completions_menu
5548 .completions
5549 .borrow()
5550 .get(candidate_id)?
5551 .clone();
5552 cx.stop_propagation();
5553
5554 let buffer_handle = completions_menu.buffer.clone();
5555
5556 let CompletionEdit {
5557 new_text,
5558 snippet,
5559 replace_range,
5560 } = process_completion_for_edit(
5561 &completion,
5562 intent,
5563 &buffer_handle,
5564 &completions_menu.initial_position.text_anchor,
5565 cx,
5566 );
5567
5568 let buffer = buffer_handle.read(cx);
5569 let snapshot = self.buffer.read(cx).snapshot(cx);
5570 let newest_anchor = self.selections.newest_anchor();
5571 let replace_range_multibuffer = {
5572 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5573 let multibuffer_anchor = snapshot
5574 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5575 .unwrap()
5576 ..snapshot
5577 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5578 .unwrap();
5579 multibuffer_anchor.start.to_offset(&snapshot)
5580 ..multibuffer_anchor.end.to_offset(&snapshot)
5581 };
5582 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5583 return None;
5584 }
5585
5586 let old_text = buffer
5587 .text_for_range(replace_range.clone())
5588 .collect::<String>();
5589 let lookbehind = newest_anchor
5590 .start
5591 .text_anchor
5592 .to_offset(buffer)
5593 .saturating_sub(replace_range.start);
5594 let lookahead = replace_range
5595 .end
5596 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5597 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5598 let suffix = &old_text[lookbehind.min(old_text.len())..];
5599
5600 let selections = self.selections.all::<usize>(cx);
5601 let mut ranges = Vec::new();
5602 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5603
5604 for selection in &selections {
5605 let range = if selection.id == newest_anchor.id {
5606 replace_range_multibuffer.clone()
5607 } else {
5608 let mut range = selection.range();
5609
5610 // if prefix is present, don't duplicate it
5611 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5612 range.start = range.start.saturating_sub(lookbehind);
5613
5614 // if suffix is also present, mimic the newest cursor and replace it
5615 if selection.id != newest_anchor.id
5616 && snapshot.contains_str_at(range.end, suffix)
5617 {
5618 range.end += lookahead;
5619 }
5620 }
5621 range
5622 };
5623
5624 ranges.push(range.clone());
5625
5626 if !self.linked_edit_ranges.is_empty() {
5627 let start_anchor = snapshot.anchor_before(range.start);
5628 let end_anchor = snapshot.anchor_after(range.end);
5629 if let Some(ranges) = self
5630 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5631 {
5632 for (buffer, edits) in ranges {
5633 linked_edits
5634 .entry(buffer.clone())
5635 .or_default()
5636 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5637 }
5638 }
5639 }
5640 }
5641
5642 let common_prefix_len = old_text
5643 .chars()
5644 .zip(new_text.chars())
5645 .take_while(|(a, b)| a == b)
5646 .map(|(a, _)| a.len_utf8())
5647 .sum::<usize>();
5648
5649 cx.emit(EditorEvent::InputHandled {
5650 utf16_range_to_replace: None,
5651 text: new_text[common_prefix_len..].into(),
5652 });
5653
5654 self.transact(window, cx, |this, window, cx| {
5655 if let Some(mut snippet) = snippet {
5656 snippet.text = new_text.to_string();
5657 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5658 } else {
5659 this.buffer.update(cx, |buffer, cx| {
5660 let auto_indent = match completion.insert_text_mode {
5661 Some(InsertTextMode::AS_IS) => None,
5662 _ => this.autoindent_mode.clone(),
5663 };
5664 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5665 buffer.edit(edits, auto_indent, cx);
5666 });
5667 }
5668 for (buffer, edits) in linked_edits {
5669 buffer.update(cx, |buffer, cx| {
5670 let snapshot = buffer.snapshot();
5671 let edits = edits
5672 .into_iter()
5673 .map(|(range, text)| {
5674 use text::ToPoint as TP;
5675 let end_point = TP::to_point(&range.end, &snapshot);
5676 let start_point = TP::to_point(&range.start, &snapshot);
5677 (start_point..end_point, text)
5678 })
5679 .sorted_by_key(|(range, _)| range.start);
5680 buffer.edit(edits, None, cx);
5681 })
5682 }
5683
5684 this.refresh_inline_completion(true, false, window, cx);
5685 });
5686
5687 let show_new_completions_on_confirm = completion
5688 .confirm
5689 .as_ref()
5690 .map_or(false, |confirm| confirm(intent, window, cx));
5691 if show_new_completions_on_confirm {
5692 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5693 }
5694
5695 let provider = self.completion_provider.as_ref()?;
5696 drop(completion);
5697 let apply_edits = provider.apply_additional_edits_for_completion(
5698 buffer_handle,
5699 completions_menu.completions.clone(),
5700 candidate_id,
5701 true,
5702 cx,
5703 );
5704
5705 let editor_settings = EditorSettings::get_global(cx);
5706 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5707 // After the code completion is finished, users often want to know what signatures are needed.
5708 // so we should automatically call signature_help
5709 self.show_signature_help(&ShowSignatureHelp, window, cx);
5710 }
5711
5712 Some(cx.foreground_executor().spawn(async move {
5713 apply_edits.await?;
5714 Ok(())
5715 }))
5716 }
5717
5718 pub fn toggle_code_actions(
5719 &mut self,
5720 action: &ToggleCodeActions,
5721 window: &mut Window,
5722 cx: &mut Context<Self>,
5723 ) {
5724 let quick_launch = action.quick_launch;
5725 let mut context_menu = self.context_menu.borrow_mut();
5726 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5727 if code_actions.deployed_from == action.deployed_from {
5728 // Toggle if we're selecting the same one
5729 *context_menu = None;
5730 cx.notify();
5731 return;
5732 } else {
5733 // Otherwise, clear it and start a new one
5734 *context_menu = None;
5735 cx.notify();
5736 }
5737 }
5738 drop(context_menu);
5739 let snapshot = self.snapshot(window, cx);
5740 let deployed_from = action.deployed_from.clone();
5741 let action = action.clone();
5742 self.completion_tasks.clear();
5743 self.discard_inline_completion(false, cx);
5744
5745 let multibuffer_point = match &action.deployed_from {
5746 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5747 DisplayPoint::new(*row, 0).to_point(&snapshot)
5748 }
5749 _ => self.selections.newest::<Point>(cx).head(),
5750 };
5751 let Some((buffer, buffer_row)) = snapshot
5752 .buffer_snapshot
5753 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5754 .and_then(|(buffer_snapshot, range)| {
5755 self.buffer()
5756 .read(cx)
5757 .buffer(buffer_snapshot.remote_id())
5758 .map(|buffer| (buffer, range.start.row))
5759 })
5760 else {
5761 return;
5762 };
5763 let buffer_id = buffer.read(cx).remote_id();
5764 let tasks = self
5765 .tasks
5766 .get(&(buffer_id, buffer_row))
5767 .map(|t| Arc::new(t.to_owned()));
5768
5769 if !self.focus_handle.is_focused(window) {
5770 return;
5771 }
5772 let project = self.project.clone();
5773
5774 let code_actions_task = match deployed_from {
5775 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5776 _ => self.code_actions(buffer_row, window, cx),
5777 };
5778
5779 let runnable_task = match deployed_from {
5780 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5781 _ => {
5782 let mut task_context_task = Task::ready(None);
5783 if let Some(tasks) = &tasks {
5784 if let Some(project) = project {
5785 task_context_task =
5786 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5787 }
5788 }
5789
5790 cx.spawn_in(window, {
5791 let buffer = buffer.clone();
5792 async move |editor, cx| {
5793 let task_context = task_context_task.await;
5794
5795 let resolved_tasks =
5796 tasks
5797 .zip(task_context.clone())
5798 .map(|(tasks, task_context)| ResolvedTasks {
5799 templates: tasks.resolve(&task_context).collect(),
5800 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5801 multibuffer_point.row,
5802 tasks.column,
5803 )),
5804 });
5805 let debug_scenarios = editor
5806 .update(cx, |editor, cx| {
5807 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5808 })?
5809 .await;
5810 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5811 }
5812 })
5813 }
5814 };
5815
5816 cx.spawn_in(window, async move |editor, cx| {
5817 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5818 let code_actions = code_actions_task.await;
5819 let spawn_straight_away = quick_launch
5820 && resolved_tasks
5821 .as_ref()
5822 .map_or(false, |tasks| tasks.templates.len() == 1)
5823 && code_actions
5824 .as_ref()
5825 .map_or(true, |actions| actions.is_empty())
5826 && debug_scenarios.is_empty();
5827
5828 editor.update_in(cx, |editor, window, cx| {
5829 crate::hover_popover::hide_hover(editor, cx);
5830 *editor.context_menu.borrow_mut() =
5831 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5832 buffer,
5833 actions: CodeActionContents::new(
5834 resolved_tasks,
5835 code_actions,
5836 debug_scenarios,
5837 task_context.unwrap_or_default(),
5838 ),
5839 selected_item: Default::default(),
5840 scroll_handle: UniformListScrollHandle::default(),
5841 deployed_from,
5842 }));
5843 if spawn_straight_away {
5844 if let Some(task) = editor.confirm_code_action(
5845 &ConfirmCodeAction { item_ix: Some(0) },
5846 window,
5847 cx,
5848 ) {
5849 cx.notify();
5850 return task;
5851 }
5852 }
5853
5854 Task::ready(Ok(()))
5855 })
5856 })
5857 .detach_and_log_err(cx);
5858 }
5859
5860 fn debug_scenarios(
5861 &mut self,
5862 resolved_tasks: &Option<ResolvedTasks>,
5863 buffer: &Entity<Buffer>,
5864 cx: &mut App,
5865 ) -> Task<Vec<task::DebugScenario>> {
5866 if cx.has_flag::<DebuggerFeatureFlag>() {
5867 maybe!({
5868 let project = self.project.as_ref()?;
5869 let dap_store = project.read(cx).dap_store();
5870 let mut scenarios = vec![];
5871 let resolved_tasks = resolved_tasks.as_ref()?;
5872 let buffer = buffer.read(cx);
5873 let language = buffer.language()?;
5874 let file = buffer.file();
5875 let debug_adapter = language_settings(language.name().into(), file, cx)
5876 .debuggers
5877 .first()
5878 .map(SharedString::from)
5879 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5880
5881 dap_store.update(cx, |dap_store, cx| {
5882 for (_, task) in &resolved_tasks.templates {
5883 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5884 task.original_task().clone(),
5885 debug_adapter.clone().into(),
5886 task.display_label().to_owned().into(),
5887 cx,
5888 );
5889 scenarios.push(maybe_scenario);
5890 }
5891 });
5892 Some(cx.background_spawn(async move {
5893 let scenarios = futures::future::join_all(scenarios)
5894 .await
5895 .into_iter()
5896 .flatten()
5897 .collect::<Vec<_>>();
5898 scenarios
5899 }))
5900 })
5901 .unwrap_or_else(|| Task::ready(vec![]))
5902 } else {
5903 Task::ready(vec![])
5904 }
5905 }
5906
5907 fn code_actions(
5908 &mut self,
5909 buffer_row: u32,
5910 window: &mut Window,
5911 cx: &mut Context<Self>,
5912 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5913 let mut task = self.code_actions_task.take();
5914 cx.spawn_in(window, async move |editor, cx| {
5915 while let Some(prev_task) = task {
5916 prev_task.await.log_err();
5917 task = editor
5918 .update(cx, |this, _| this.code_actions_task.take())
5919 .ok()?;
5920 }
5921
5922 editor
5923 .update(cx, |editor, cx| {
5924 editor
5925 .available_code_actions
5926 .clone()
5927 .and_then(|(location, code_actions)| {
5928 let snapshot = location.buffer.read(cx).snapshot();
5929 let point_range = location.range.to_point(&snapshot);
5930 let point_range = point_range.start.row..=point_range.end.row;
5931 if point_range.contains(&buffer_row) {
5932 Some(code_actions)
5933 } else {
5934 None
5935 }
5936 })
5937 })
5938 .ok()
5939 .flatten()
5940 })
5941 }
5942
5943 pub fn confirm_code_action(
5944 &mut self,
5945 action: &ConfirmCodeAction,
5946 window: &mut Window,
5947 cx: &mut Context<Self>,
5948 ) -> Option<Task<Result<()>>> {
5949 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5950
5951 let actions_menu =
5952 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5953 menu
5954 } else {
5955 return None;
5956 };
5957
5958 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5959 let action = actions_menu.actions.get(action_ix)?;
5960 let title = action.label();
5961 let buffer = actions_menu.buffer;
5962 let workspace = self.workspace()?;
5963
5964 match action {
5965 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5966 workspace.update(cx, |workspace, cx| {
5967 workspace.schedule_resolved_task(
5968 task_source_kind,
5969 resolved_task,
5970 false,
5971 window,
5972 cx,
5973 );
5974
5975 Some(Task::ready(Ok(())))
5976 })
5977 }
5978 CodeActionsItem::CodeAction {
5979 excerpt_id,
5980 action,
5981 provider,
5982 } => {
5983 let apply_code_action =
5984 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5985 let workspace = workspace.downgrade();
5986 Some(cx.spawn_in(window, async move |editor, cx| {
5987 let project_transaction = apply_code_action.await?;
5988 Self::open_project_transaction(
5989 &editor,
5990 workspace,
5991 project_transaction,
5992 title,
5993 cx,
5994 )
5995 .await
5996 }))
5997 }
5998 CodeActionsItem::DebugScenario(scenario) => {
5999 let context = actions_menu.actions.context.clone();
6000
6001 workspace.update(cx, |workspace, cx| {
6002 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6003 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6004 });
6005 Some(Task::ready(Ok(())))
6006 }
6007 }
6008 }
6009
6010 pub async fn open_project_transaction(
6011 this: &WeakEntity<Editor>,
6012 workspace: WeakEntity<Workspace>,
6013 transaction: ProjectTransaction,
6014 title: String,
6015 cx: &mut AsyncWindowContext,
6016 ) -> Result<()> {
6017 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6018 cx.update(|_, cx| {
6019 entries.sort_unstable_by_key(|(buffer, _)| {
6020 buffer.read(cx).file().map(|f| f.path().clone())
6021 });
6022 })?;
6023
6024 // If the project transaction's edits are all contained within this editor, then
6025 // avoid opening a new editor to display them.
6026
6027 if let Some((buffer, transaction)) = entries.first() {
6028 if entries.len() == 1 {
6029 let excerpt = this.update(cx, |editor, cx| {
6030 editor
6031 .buffer()
6032 .read(cx)
6033 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6034 })?;
6035 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6036 if excerpted_buffer == *buffer {
6037 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6038 let excerpt_range = excerpt_range.to_offset(buffer);
6039 buffer
6040 .edited_ranges_for_transaction::<usize>(transaction)
6041 .all(|range| {
6042 excerpt_range.start <= range.start
6043 && excerpt_range.end >= range.end
6044 })
6045 })?;
6046
6047 if all_edits_within_excerpt {
6048 return Ok(());
6049 }
6050 }
6051 }
6052 }
6053 } else {
6054 return Ok(());
6055 }
6056
6057 let mut ranges_to_highlight = Vec::new();
6058 let excerpt_buffer = cx.new(|cx| {
6059 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6060 for (buffer_handle, transaction) in &entries {
6061 let edited_ranges = buffer_handle
6062 .read(cx)
6063 .edited_ranges_for_transaction::<Point>(transaction)
6064 .collect::<Vec<_>>();
6065 let (ranges, _) = multibuffer.set_excerpts_for_path(
6066 PathKey::for_buffer(buffer_handle, cx),
6067 buffer_handle.clone(),
6068 edited_ranges,
6069 DEFAULT_MULTIBUFFER_CONTEXT,
6070 cx,
6071 );
6072
6073 ranges_to_highlight.extend(ranges);
6074 }
6075 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6076 multibuffer
6077 })?;
6078
6079 workspace.update_in(cx, |workspace, window, cx| {
6080 let project = workspace.project().clone();
6081 let editor =
6082 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6083 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6084 editor.update(cx, |editor, cx| {
6085 editor.highlight_background::<Self>(
6086 &ranges_to_highlight,
6087 |theme| theme.editor_highlighted_line_background,
6088 cx,
6089 );
6090 });
6091 })?;
6092
6093 Ok(())
6094 }
6095
6096 pub fn clear_code_action_providers(&mut self) {
6097 self.code_action_providers.clear();
6098 self.available_code_actions.take();
6099 }
6100
6101 pub fn add_code_action_provider(
6102 &mut self,
6103 provider: Rc<dyn CodeActionProvider>,
6104 window: &mut Window,
6105 cx: &mut Context<Self>,
6106 ) {
6107 if self
6108 .code_action_providers
6109 .iter()
6110 .any(|existing_provider| existing_provider.id() == provider.id())
6111 {
6112 return;
6113 }
6114
6115 self.code_action_providers.push(provider);
6116 self.refresh_code_actions(window, cx);
6117 }
6118
6119 pub fn remove_code_action_provider(
6120 &mut self,
6121 id: Arc<str>,
6122 window: &mut Window,
6123 cx: &mut Context<Self>,
6124 ) {
6125 self.code_action_providers
6126 .retain(|provider| provider.id() != id);
6127 self.refresh_code_actions(window, cx);
6128 }
6129
6130 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6131 !self.code_action_providers.is_empty()
6132 && EditorSettings::get_global(cx).toolbar.code_actions
6133 }
6134
6135 pub fn has_available_code_actions(&self) -> bool {
6136 self.available_code_actions
6137 .as_ref()
6138 .is_some_and(|(_, actions)| !actions.is_empty())
6139 }
6140
6141 fn render_inline_code_actions(
6142 &self,
6143 icon_size: ui::IconSize,
6144 display_row: DisplayRow,
6145 is_active: bool,
6146 cx: &mut Context<Self>,
6147 ) -> AnyElement {
6148 let show_tooltip = !self.context_menu_visible();
6149 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6150 .icon_size(icon_size)
6151 .shape(ui::IconButtonShape::Square)
6152 .style(ButtonStyle::Transparent)
6153 .icon_color(ui::Color::Hidden)
6154 .toggle_state(is_active)
6155 .when(show_tooltip, |this| {
6156 this.tooltip({
6157 let focus_handle = self.focus_handle.clone();
6158 move |window, cx| {
6159 Tooltip::for_action_in(
6160 "Toggle Code Actions",
6161 &ToggleCodeActions {
6162 deployed_from: None,
6163 quick_launch: false,
6164 },
6165 &focus_handle,
6166 window,
6167 cx,
6168 )
6169 }
6170 })
6171 })
6172 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6173 window.focus(&editor.focus_handle(cx));
6174 editor.toggle_code_actions(
6175 &crate::actions::ToggleCodeActions {
6176 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6177 display_row,
6178 )),
6179 quick_launch: false,
6180 },
6181 window,
6182 cx,
6183 );
6184 }))
6185 .into_any_element()
6186 }
6187
6188 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6189 &self.context_menu
6190 }
6191
6192 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6193 let newest_selection = self.selections.newest_anchor().clone();
6194 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6195 let buffer = self.buffer.read(cx);
6196 if newest_selection.head().diff_base_anchor.is_some() {
6197 return None;
6198 }
6199 let (start_buffer, start) =
6200 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6201 let (end_buffer, end) =
6202 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6203 if start_buffer != end_buffer {
6204 return None;
6205 }
6206
6207 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6208 cx.background_executor()
6209 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6210 .await;
6211
6212 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6213 let providers = this.code_action_providers.clone();
6214 let tasks = this
6215 .code_action_providers
6216 .iter()
6217 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6218 .collect::<Vec<_>>();
6219 (providers, tasks)
6220 })?;
6221
6222 let mut actions = Vec::new();
6223 for (provider, provider_actions) in
6224 providers.into_iter().zip(future::join_all(tasks).await)
6225 {
6226 if let Some(provider_actions) = provider_actions.log_err() {
6227 actions.extend(provider_actions.into_iter().map(|action| {
6228 AvailableCodeAction {
6229 excerpt_id: newest_selection.start.excerpt_id,
6230 action,
6231 provider: provider.clone(),
6232 }
6233 }));
6234 }
6235 }
6236
6237 this.update(cx, |this, cx| {
6238 this.available_code_actions = if actions.is_empty() {
6239 None
6240 } else {
6241 Some((
6242 Location {
6243 buffer: start_buffer,
6244 range: start..end,
6245 },
6246 actions.into(),
6247 ))
6248 };
6249 cx.notify();
6250 })
6251 }));
6252 None
6253 }
6254
6255 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6256 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6257 self.show_git_blame_inline = false;
6258
6259 self.show_git_blame_inline_delay_task =
6260 Some(cx.spawn_in(window, async move |this, cx| {
6261 cx.background_executor().timer(delay).await;
6262
6263 this.update(cx, |this, cx| {
6264 this.show_git_blame_inline = true;
6265 cx.notify();
6266 })
6267 .log_err();
6268 }));
6269 }
6270 }
6271
6272 fn show_blame_popover(
6273 &mut self,
6274 blame_entry: &BlameEntry,
6275 position: gpui::Point<Pixels>,
6276 cx: &mut Context<Self>,
6277 ) {
6278 if let Some(state) = &mut self.inline_blame_popover {
6279 state.hide_task.take();
6280 } else {
6281 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6282 let blame_entry = blame_entry.clone();
6283 let show_task = cx.spawn(async move |editor, cx| {
6284 cx.background_executor()
6285 .timer(std::time::Duration::from_millis(delay))
6286 .await;
6287 editor
6288 .update(cx, |editor, cx| {
6289 editor.inline_blame_popover_show_task.take();
6290 let Some(blame) = editor.blame.as_ref() else {
6291 return;
6292 };
6293 let blame = blame.read(cx);
6294 let details = blame.details_for_entry(&blame_entry);
6295 let markdown = cx.new(|cx| {
6296 Markdown::new(
6297 details
6298 .as_ref()
6299 .map(|message| message.message.clone())
6300 .unwrap_or_default(),
6301 None,
6302 None,
6303 cx,
6304 )
6305 });
6306 editor.inline_blame_popover = Some(InlineBlamePopover {
6307 position,
6308 hide_task: None,
6309 popover_bounds: None,
6310 popover_state: InlineBlamePopoverState {
6311 scroll_handle: ScrollHandle::new(),
6312 commit_message: details,
6313 markdown,
6314 },
6315 });
6316 cx.notify();
6317 })
6318 .ok();
6319 });
6320 self.inline_blame_popover_show_task = Some(show_task);
6321 }
6322 }
6323
6324 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6325 self.inline_blame_popover_show_task.take();
6326 if let Some(state) = &mut self.inline_blame_popover {
6327 let hide_task = cx.spawn(async move |editor, cx| {
6328 cx.background_executor()
6329 .timer(std::time::Duration::from_millis(100))
6330 .await;
6331 editor
6332 .update(cx, |editor, cx| {
6333 editor.inline_blame_popover.take();
6334 cx.notify();
6335 })
6336 .ok();
6337 });
6338 state.hide_task = Some(hide_task);
6339 }
6340 }
6341
6342 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6343 if self.pending_rename.is_some() {
6344 return None;
6345 }
6346
6347 let provider = self.semantics_provider.clone()?;
6348 let buffer = self.buffer.read(cx);
6349 let newest_selection = self.selections.newest_anchor().clone();
6350 let cursor_position = newest_selection.head();
6351 let (cursor_buffer, cursor_buffer_position) =
6352 buffer.text_anchor_for_position(cursor_position, cx)?;
6353 let (tail_buffer, tail_buffer_position) =
6354 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6355 if cursor_buffer != tail_buffer {
6356 return None;
6357 }
6358
6359 let snapshot = cursor_buffer.read(cx).snapshot();
6360 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6361 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6362 if start_word_range != end_word_range {
6363 self.document_highlights_task.take();
6364 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6365 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6366 return None;
6367 }
6368
6369 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6370 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6371 cx.background_executor()
6372 .timer(Duration::from_millis(debounce))
6373 .await;
6374
6375 let highlights = if let Some(highlights) = cx
6376 .update(|cx| {
6377 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6378 })
6379 .ok()
6380 .flatten()
6381 {
6382 highlights.await.log_err()
6383 } else {
6384 None
6385 };
6386
6387 if let Some(highlights) = highlights {
6388 this.update(cx, |this, cx| {
6389 if this.pending_rename.is_some() {
6390 return;
6391 }
6392
6393 let buffer_id = cursor_position.buffer_id;
6394 let buffer = this.buffer.read(cx);
6395 if !buffer
6396 .text_anchor_for_position(cursor_position, cx)
6397 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6398 {
6399 return;
6400 }
6401
6402 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6403 let mut write_ranges = Vec::new();
6404 let mut read_ranges = Vec::new();
6405 for highlight in highlights {
6406 for (excerpt_id, excerpt_range) in
6407 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6408 {
6409 let start = highlight
6410 .range
6411 .start
6412 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6413 let end = highlight
6414 .range
6415 .end
6416 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6417 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6418 continue;
6419 }
6420
6421 let range = Anchor {
6422 buffer_id,
6423 excerpt_id,
6424 text_anchor: start,
6425 diff_base_anchor: None,
6426 }..Anchor {
6427 buffer_id,
6428 excerpt_id,
6429 text_anchor: end,
6430 diff_base_anchor: None,
6431 };
6432 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6433 write_ranges.push(range);
6434 } else {
6435 read_ranges.push(range);
6436 }
6437 }
6438 }
6439
6440 this.highlight_background::<DocumentHighlightRead>(
6441 &read_ranges,
6442 |theme| theme.editor_document_highlight_read_background,
6443 cx,
6444 );
6445 this.highlight_background::<DocumentHighlightWrite>(
6446 &write_ranges,
6447 |theme| theme.editor_document_highlight_write_background,
6448 cx,
6449 );
6450 cx.notify();
6451 })
6452 .log_err();
6453 }
6454 }));
6455 None
6456 }
6457
6458 fn prepare_highlight_query_from_selection(
6459 &mut self,
6460 cx: &mut Context<Editor>,
6461 ) -> Option<(String, Range<Anchor>)> {
6462 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6463 return None;
6464 }
6465 if !EditorSettings::get_global(cx).selection_highlight {
6466 return None;
6467 }
6468 if self.selections.count() != 1 || self.selections.line_mode {
6469 return None;
6470 }
6471 let selection = self.selections.newest::<Point>(cx);
6472 if selection.is_empty() || selection.start.row != selection.end.row {
6473 return None;
6474 }
6475 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6476 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6477 let query = multi_buffer_snapshot
6478 .text_for_range(selection_anchor_range.clone())
6479 .collect::<String>();
6480 if query.trim().is_empty() {
6481 return None;
6482 }
6483 Some((query, selection_anchor_range))
6484 }
6485
6486 fn update_selection_occurrence_highlights(
6487 &mut self,
6488 query_text: String,
6489 query_range: Range<Anchor>,
6490 multi_buffer_range_to_query: Range<Point>,
6491 use_debounce: bool,
6492 window: &mut Window,
6493 cx: &mut Context<Editor>,
6494 ) -> Task<()> {
6495 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6496 cx.spawn_in(window, async move |editor, cx| {
6497 if use_debounce {
6498 cx.background_executor()
6499 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6500 .await;
6501 }
6502 let match_task = cx.background_spawn(async move {
6503 let buffer_ranges = multi_buffer_snapshot
6504 .range_to_buffer_ranges(multi_buffer_range_to_query)
6505 .into_iter()
6506 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6507 let mut match_ranges = Vec::new();
6508 let Ok(regex) = project::search::SearchQuery::text(
6509 query_text.clone(),
6510 false,
6511 false,
6512 false,
6513 Default::default(),
6514 Default::default(),
6515 false,
6516 None,
6517 ) else {
6518 return Vec::default();
6519 };
6520 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6521 match_ranges.extend(
6522 regex
6523 .search(&buffer_snapshot, Some(search_range.clone()))
6524 .await
6525 .into_iter()
6526 .filter_map(|match_range| {
6527 let match_start = buffer_snapshot
6528 .anchor_after(search_range.start + match_range.start);
6529 let match_end = buffer_snapshot
6530 .anchor_before(search_range.start + match_range.end);
6531 let match_anchor_range = Anchor::range_in_buffer(
6532 excerpt_id,
6533 buffer_snapshot.remote_id(),
6534 match_start..match_end,
6535 );
6536 (match_anchor_range != query_range).then_some(match_anchor_range)
6537 }),
6538 );
6539 }
6540 match_ranges
6541 });
6542 let match_ranges = match_task.await;
6543 editor
6544 .update_in(cx, |editor, _, cx| {
6545 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6546 if !match_ranges.is_empty() {
6547 editor.highlight_background::<SelectedTextHighlight>(
6548 &match_ranges,
6549 |theme| theme.editor_document_highlight_bracket_background,
6550 cx,
6551 )
6552 }
6553 })
6554 .log_err();
6555 })
6556 }
6557
6558 fn refresh_selected_text_highlights(
6559 &mut self,
6560 on_buffer_edit: bool,
6561 window: &mut Window,
6562 cx: &mut Context<Editor>,
6563 ) {
6564 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6565 else {
6566 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6567 self.quick_selection_highlight_task.take();
6568 self.debounced_selection_highlight_task.take();
6569 return;
6570 };
6571 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6572 if on_buffer_edit
6573 || self
6574 .quick_selection_highlight_task
6575 .as_ref()
6576 .map_or(true, |(prev_anchor_range, _)| {
6577 prev_anchor_range != &query_range
6578 })
6579 {
6580 let multi_buffer_visible_start = self
6581 .scroll_manager
6582 .anchor()
6583 .anchor
6584 .to_point(&multi_buffer_snapshot);
6585 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6586 multi_buffer_visible_start
6587 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6588 Bias::Left,
6589 );
6590 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6591 self.quick_selection_highlight_task = Some((
6592 query_range.clone(),
6593 self.update_selection_occurrence_highlights(
6594 query_text.clone(),
6595 query_range.clone(),
6596 multi_buffer_visible_range,
6597 false,
6598 window,
6599 cx,
6600 ),
6601 ));
6602 }
6603 if on_buffer_edit
6604 || self
6605 .debounced_selection_highlight_task
6606 .as_ref()
6607 .map_or(true, |(prev_anchor_range, _)| {
6608 prev_anchor_range != &query_range
6609 })
6610 {
6611 let multi_buffer_start = multi_buffer_snapshot
6612 .anchor_before(0)
6613 .to_point(&multi_buffer_snapshot);
6614 let multi_buffer_end = multi_buffer_snapshot
6615 .anchor_after(multi_buffer_snapshot.len())
6616 .to_point(&multi_buffer_snapshot);
6617 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6618 self.debounced_selection_highlight_task = Some((
6619 query_range.clone(),
6620 self.update_selection_occurrence_highlights(
6621 query_text,
6622 query_range,
6623 multi_buffer_full_range,
6624 true,
6625 window,
6626 cx,
6627 ),
6628 ));
6629 }
6630 }
6631
6632 pub fn refresh_inline_completion(
6633 &mut self,
6634 debounce: bool,
6635 user_requested: bool,
6636 window: &mut Window,
6637 cx: &mut Context<Self>,
6638 ) -> Option<()> {
6639 let provider = self.edit_prediction_provider()?;
6640 let cursor = self.selections.newest_anchor().head();
6641 let (buffer, cursor_buffer_position) =
6642 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6643
6644 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6645 self.discard_inline_completion(false, cx);
6646 return None;
6647 }
6648
6649 if !user_requested
6650 && (!self.should_show_edit_predictions()
6651 || !self.is_focused(window)
6652 || buffer.read(cx).is_empty())
6653 {
6654 self.discard_inline_completion(false, cx);
6655 return None;
6656 }
6657
6658 self.update_visible_inline_completion(window, cx);
6659 provider.refresh(
6660 self.project.clone(),
6661 buffer,
6662 cursor_buffer_position,
6663 debounce,
6664 cx,
6665 );
6666 Some(())
6667 }
6668
6669 fn show_edit_predictions_in_menu(&self) -> bool {
6670 match self.edit_prediction_settings {
6671 EditPredictionSettings::Disabled => false,
6672 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6673 }
6674 }
6675
6676 pub fn edit_predictions_enabled(&self) -> bool {
6677 match self.edit_prediction_settings {
6678 EditPredictionSettings::Disabled => false,
6679 EditPredictionSettings::Enabled { .. } => true,
6680 }
6681 }
6682
6683 fn edit_prediction_requires_modifier(&self) -> bool {
6684 match self.edit_prediction_settings {
6685 EditPredictionSettings::Disabled => false,
6686 EditPredictionSettings::Enabled {
6687 preview_requires_modifier,
6688 ..
6689 } => preview_requires_modifier,
6690 }
6691 }
6692
6693 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6694 if self.edit_prediction_provider.is_none() {
6695 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6696 } else {
6697 let selection = self.selections.newest_anchor();
6698 let cursor = selection.head();
6699
6700 if let Some((buffer, cursor_buffer_position)) =
6701 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6702 {
6703 self.edit_prediction_settings =
6704 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6705 }
6706 }
6707 }
6708
6709 fn edit_prediction_settings_at_position(
6710 &self,
6711 buffer: &Entity<Buffer>,
6712 buffer_position: language::Anchor,
6713 cx: &App,
6714 ) -> EditPredictionSettings {
6715 if !self.mode.is_full()
6716 || !self.show_inline_completions_override.unwrap_or(true)
6717 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6718 {
6719 return EditPredictionSettings::Disabled;
6720 }
6721
6722 let buffer = buffer.read(cx);
6723
6724 let file = buffer.file();
6725
6726 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6727 return EditPredictionSettings::Disabled;
6728 };
6729
6730 let by_provider = matches!(
6731 self.menu_inline_completions_policy,
6732 MenuInlineCompletionsPolicy::ByProvider
6733 );
6734
6735 let show_in_menu = by_provider
6736 && self
6737 .edit_prediction_provider
6738 .as_ref()
6739 .map_or(false, |provider| {
6740 provider.provider.show_completions_in_menu()
6741 });
6742
6743 let preview_requires_modifier =
6744 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6745
6746 EditPredictionSettings::Enabled {
6747 show_in_menu,
6748 preview_requires_modifier,
6749 }
6750 }
6751
6752 fn should_show_edit_predictions(&self) -> bool {
6753 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6754 }
6755
6756 pub fn edit_prediction_preview_is_active(&self) -> bool {
6757 matches!(
6758 self.edit_prediction_preview,
6759 EditPredictionPreview::Active { .. }
6760 )
6761 }
6762
6763 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6764 let cursor = self.selections.newest_anchor().head();
6765 if let Some((buffer, cursor_position)) =
6766 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6767 {
6768 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6769 } else {
6770 false
6771 }
6772 }
6773
6774 pub fn supports_minimap(&self, cx: &App) -> bool {
6775 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6776 }
6777
6778 fn edit_predictions_enabled_in_buffer(
6779 &self,
6780 buffer: &Entity<Buffer>,
6781 buffer_position: language::Anchor,
6782 cx: &App,
6783 ) -> bool {
6784 maybe!({
6785 if self.read_only(cx) {
6786 return Some(false);
6787 }
6788 let provider = self.edit_prediction_provider()?;
6789 if !provider.is_enabled(&buffer, buffer_position, cx) {
6790 return Some(false);
6791 }
6792 let buffer = buffer.read(cx);
6793 let Some(file) = buffer.file() else {
6794 return Some(true);
6795 };
6796 let settings = all_language_settings(Some(file), cx);
6797 Some(settings.edit_predictions_enabled_for_file(file, cx))
6798 })
6799 .unwrap_or(false)
6800 }
6801
6802 fn cycle_inline_completion(
6803 &mut self,
6804 direction: Direction,
6805 window: &mut Window,
6806 cx: &mut Context<Self>,
6807 ) -> Option<()> {
6808 let provider = self.edit_prediction_provider()?;
6809 let cursor = self.selections.newest_anchor().head();
6810 let (buffer, cursor_buffer_position) =
6811 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6812 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6813 return None;
6814 }
6815
6816 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6817 self.update_visible_inline_completion(window, cx);
6818
6819 Some(())
6820 }
6821
6822 pub fn show_inline_completion(
6823 &mut self,
6824 _: &ShowEditPrediction,
6825 window: &mut Window,
6826 cx: &mut Context<Self>,
6827 ) {
6828 if !self.has_active_inline_completion() {
6829 self.refresh_inline_completion(false, true, window, cx);
6830 return;
6831 }
6832
6833 self.update_visible_inline_completion(window, cx);
6834 }
6835
6836 pub fn display_cursor_names(
6837 &mut self,
6838 _: &DisplayCursorNames,
6839 window: &mut Window,
6840 cx: &mut Context<Self>,
6841 ) {
6842 self.show_cursor_names(window, cx);
6843 }
6844
6845 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6846 self.show_cursor_names = true;
6847 cx.notify();
6848 cx.spawn_in(window, async move |this, cx| {
6849 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6850 this.update(cx, |this, cx| {
6851 this.show_cursor_names = false;
6852 cx.notify()
6853 })
6854 .ok()
6855 })
6856 .detach();
6857 }
6858
6859 pub fn next_edit_prediction(
6860 &mut self,
6861 _: &NextEditPrediction,
6862 window: &mut Window,
6863 cx: &mut Context<Self>,
6864 ) {
6865 if self.has_active_inline_completion() {
6866 self.cycle_inline_completion(Direction::Next, window, cx);
6867 } else {
6868 let is_copilot_disabled = self
6869 .refresh_inline_completion(false, true, window, cx)
6870 .is_none();
6871 if is_copilot_disabled {
6872 cx.propagate();
6873 }
6874 }
6875 }
6876
6877 pub fn previous_edit_prediction(
6878 &mut self,
6879 _: &PreviousEditPrediction,
6880 window: &mut Window,
6881 cx: &mut Context<Self>,
6882 ) {
6883 if self.has_active_inline_completion() {
6884 self.cycle_inline_completion(Direction::Prev, window, cx);
6885 } else {
6886 let is_copilot_disabled = self
6887 .refresh_inline_completion(false, true, window, cx)
6888 .is_none();
6889 if is_copilot_disabled {
6890 cx.propagate();
6891 }
6892 }
6893 }
6894
6895 pub fn accept_edit_prediction(
6896 &mut self,
6897 _: &AcceptEditPrediction,
6898 window: &mut Window,
6899 cx: &mut Context<Self>,
6900 ) {
6901 if self.show_edit_predictions_in_menu() {
6902 self.hide_context_menu(window, cx);
6903 }
6904
6905 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6906 return;
6907 };
6908
6909 self.report_inline_completion_event(
6910 active_inline_completion.completion_id.clone(),
6911 true,
6912 cx,
6913 );
6914
6915 match &active_inline_completion.completion {
6916 InlineCompletion::Move { target, .. } => {
6917 let target = *target;
6918
6919 if let Some(position_map) = &self.last_position_map {
6920 if position_map
6921 .visible_row_range
6922 .contains(&target.to_display_point(&position_map.snapshot).row())
6923 || !self.edit_prediction_requires_modifier()
6924 {
6925 self.unfold_ranges(&[target..target], true, false, cx);
6926 // Note that this is also done in vim's handler of the Tab action.
6927 self.change_selections(
6928 Some(Autoscroll::newest()),
6929 window,
6930 cx,
6931 |selections| {
6932 selections.select_anchor_ranges([target..target]);
6933 },
6934 );
6935 self.clear_row_highlights::<EditPredictionPreview>();
6936
6937 self.edit_prediction_preview
6938 .set_previous_scroll_position(None);
6939 } else {
6940 self.edit_prediction_preview
6941 .set_previous_scroll_position(Some(
6942 position_map.snapshot.scroll_anchor,
6943 ));
6944
6945 self.highlight_rows::<EditPredictionPreview>(
6946 target..target,
6947 cx.theme().colors().editor_highlighted_line_background,
6948 RowHighlightOptions {
6949 autoscroll: true,
6950 ..Default::default()
6951 },
6952 cx,
6953 );
6954 self.request_autoscroll(Autoscroll::fit(), cx);
6955 }
6956 }
6957 }
6958 InlineCompletion::Edit { edits, .. } => {
6959 if let Some(provider) = self.edit_prediction_provider() {
6960 provider.accept(cx);
6961 }
6962
6963 // Store the transaction ID and selections before applying the edit
6964 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6965
6966 let snapshot = self.buffer.read(cx).snapshot(cx);
6967 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6968
6969 self.buffer.update(cx, |buffer, cx| {
6970 buffer.edit(edits.iter().cloned(), None, cx)
6971 });
6972
6973 self.change_selections(None, window, cx, |s| {
6974 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6975 });
6976
6977 let selections = self.selections.disjoint_anchors();
6978 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6979 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6980 if has_new_transaction {
6981 self.selection_history
6982 .insert_transaction(transaction_id_now, selections);
6983 }
6984 }
6985
6986 self.update_visible_inline_completion(window, cx);
6987 if self.active_inline_completion.is_none() {
6988 self.refresh_inline_completion(true, true, window, cx);
6989 }
6990
6991 cx.notify();
6992 }
6993 }
6994
6995 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6996 }
6997
6998 pub fn accept_partial_inline_completion(
6999 &mut self,
7000 _: &AcceptPartialEditPrediction,
7001 window: &mut Window,
7002 cx: &mut Context<Self>,
7003 ) {
7004 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7005 return;
7006 };
7007 if self.selections.count() != 1 {
7008 return;
7009 }
7010
7011 self.report_inline_completion_event(
7012 active_inline_completion.completion_id.clone(),
7013 true,
7014 cx,
7015 );
7016
7017 match &active_inline_completion.completion {
7018 InlineCompletion::Move { target, .. } => {
7019 let target = *target;
7020 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7021 selections.select_anchor_ranges([target..target]);
7022 });
7023 }
7024 InlineCompletion::Edit { edits, .. } => {
7025 // Find an insertion that starts at the cursor position.
7026 let snapshot = self.buffer.read(cx).snapshot(cx);
7027 let cursor_offset = self.selections.newest::<usize>(cx).head();
7028 let insertion = edits.iter().find_map(|(range, text)| {
7029 let range = range.to_offset(&snapshot);
7030 if range.is_empty() && range.start == cursor_offset {
7031 Some(text)
7032 } else {
7033 None
7034 }
7035 });
7036
7037 if let Some(text) = insertion {
7038 let mut partial_completion = text
7039 .chars()
7040 .by_ref()
7041 .take_while(|c| c.is_alphabetic())
7042 .collect::<String>();
7043 if partial_completion.is_empty() {
7044 partial_completion = text
7045 .chars()
7046 .by_ref()
7047 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7048 .collect::<String>();
7049 }
7050
7051 cx.emit(EditorEvent::InputHandled {
7052 utf16_range_to_replace: None,
7053 text: partial_completion.clone().into(),
7054 });
7055
7056 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7057
7058 self.refresh_inline_completion(true, true, window, cx);
7059 cx.notify();
7060 } else {
7061 self.accept_edit_prediction(&Default::default(), window, cx);
7062 }
7063 }
7064 }
7065 }
7066
7067 fn discard_inline_completion(
7068 &mut self,
7069 should_report_inline_completion_event: bool,
7070 cx: &mut Context<Self>,
7071 ) -> bool {
7072 if should_report_inline_completion_event {
7073 let completion_id = self
7074 .active_inline_completion
7075 .as_ref()
7076 .and_then(|active_completion| active_completion.completion_id.clone());
7077
7078 self.report_inline_completion_event(completion_id, false, cx);
7079 }
7080
7081 if let Some(provider) = self.edit_prediction_provider() {
7082 provider.discard(cx);
7083 }
7084
7085 self.take_active_inline_completion(cx)
7086 }
7087
7088 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7089 let Some(provider) = self.edit_prediction_provider() else {
7090 return;
7091 };
7092
7093 let Some((_, buffer, _)) = self
7094 .buffer
7095 .read(cx)
7096 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7097 else {
7098 return;
7099 };
7100
7101 let extension = buffer
7102 .read(cx)
7103 .file()
7104 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7105
7106 let event_type = match accepted {
7107 true => "Edit Prediction Accepted",
7108 false => "Edit Prediction Discarded",
7109 };
7110 telemetry::event!(
7111 event_type,
7112 provider = provider.name(),
7113 prediction_id = id,
7114 suggestion_accepted = accepted,
7115 file_extension = extension,
7116 );
7117 }
7118
7119 pub fn has_active_inline_completion(&self) -> bool {
7120 self.active_inline_completion.is_some()
7121 }
7122
7123 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7124 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7125 return false;
7126 };
7127
7128 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7129 self.clear_highlights::<InlineCompletionHighlight>(cx);
7130 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7131 true
7132 }
7133
7134 /// Returns true when we're displaying the edit prediction popover below the cursor
7135 /// like we are not previewing and the LSP autocomplete menu is visible
7136 /// or we are in `when_holding_modifier` mode.
7137 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7138 if self.edit_prediction_preview_is_active()
7139 || !self.show_edit_predictions_in_menu()
7140 || !self.edit_predictions_enabled()
7141 {
7142 return false;
7143 }
7144
7145 if self.has_visible_completions_menu() {
7146 return true;
7147 }
7148
7149 has_completion && self.edit_prediction_requires_modifier()
7150 }
7151
7152 fn handle_modifiers_changed(
7153 &mut self,
7154 modifiers: Modifiers,
7155 position_map: &PositionMap,
7156 window: &mut Window,
7157 cx: &mut Context<Self>,
7158 ) {
7159 if self.show_edit_predictions_in_menu() {
7160 self.update_edit_prediction_preview(&modifiers, window, cx);
7161 }
7162
7163 self.update_selection_mode(&modifiers, position_map, window, cx);
7164
7165 let mouse_position = window.mouse_position();
7166 if !position_map.text_hitbox.is_hovered(window) {
7167 return;
7168 }
7169
7170 self.update_hovered_link(
7171 position_map.point_for_position(mouse_position),
7172 &position_map.snapshot,
7173 modifiers,
7174 window,
7175 cx,
7176 )
7177 }
7178
7179 fn multi_cursor_modifier(
7180 cursor_event: bool,
7181 modifiers: &Modifiers,
7182 cx: &mut Context<Self>,
7183 ) -> bool {
7184 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7185 if cursor_event {
7186 match multi_cursor_setting {
7187 MultiCursorModifier::Alt => modifiers.alt,
7188 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7189 }
7190 } else {
7191 match multi_cursor_setting {
7192 MultiCursorModifier::Alt => modifiers.secondary(),
7193 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7194 }
7195 }
7196 }
7197
7198 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7199 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7200 }
7201
7202 fn update_selection_mode(
7203 &mut self,
7204 modifiers: &Modifiers,
7205 position_map: &PositionMap,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7210 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7211 || self.selections.pending.is_none()
7212 {
7213 return;
7214 }
7215
7216 let mouse_position = window.mouse_position();
7217 let point_for_position = position_map.point_for_position(mouse_position);
7218 let position = point_for_position.previous_valid;
7219
7220 self.select(
7221 SelectPhase::BeginColumnar {
7222 position,
7223 reset: false,
7224 goal_column: point_for_position.exact_unclipped.column(),
7225 },
7226 window,
7227 cx,
7228 );
7229 }
7230
7231 fn update_edit_prediction_preview(
7232 &mut self,
7233 modifiers: &Modifiers,
7234 window: &mut Window,
7235 cx: &mut Context<Self>,
7236 ) {
7237 let mut modifiers_held = false;
7238 if let Some(accept_keystroke) = self
7239 .accept_edit_prediction_keybind(false, window, cx)
7240 .keystroke()
7241 {
7242 modifiers_held = modifiers_held
7243 || (&accept_keystroke.modifiers == modifiers
7244 && accept_keystroke.modifiers.modified());
7245 };
7246 if let Some(accept_partial_keystroke) = self
7247 .accept_edit_prediction_keybind(true, window, cx)
7248 .keystroke()
7249 {
7250 modifiers_held = modifiers_held
7251 || (&accept_partial_keystroke.modifiers == modifiers
7252 && accept_partial_keystroke.modifiers.modified());
7253 }
7254
7255 if modifiers_held {
7256 if matches!(
7257 self.edit_prediction_preview,
7258 EditPredictionPreview::Inactive { .. }
7259 ) {
7260 self.edit_prediction_preview = EditPredictionPreview::Active {
7261 previous_scroll_position: None,
7262 since: Instant::now(),
7263 };
7264
7265 self.update_visible_inline_completion(window, cx);
7266 cx.notify();
7267 }
7268 } else if let EditPredictionPreview::Active {
7269 previous_scroll_position,
7270 since,
7271 } = self.edit_prediction_preview
7272 {
7273 if let (Some(previous_scroll_position), Some(position_map)) =
7274 (previous_scroll_position, self.last_position_map.as_ref())
7275 {
7276 self.set_scroll_position(
7277 previous_scroll_position
7278 .scroll_position(&position_map.snapshot.display_snapshot),
7279 window,
7280 cx,
7281 );
7282 }
7283
7284 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7285 released_too_fast: since.elapsed() < Duration::from_millis(200),
7286 };
7287 self.clear_row_highlights::<EditPredictionPreview>();
7288 self.update_visible_inline_completion(window, cx);
7289 cx.notify();
7290 }
7291 }
7292
7293 fn update_visible_inline_completion(
7294 &mut self,
7295 _window: &mut Window,
7296 cx: &mut Context<Self>,
7297 ) -> Option<()> {
7298 let selection = self.selections.newest_anchor();
7299 let cursor = selection.head();
7300 let multibuffer = self.buffer.read(cx).snapshot(cx);
7301 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7302 let excerpt_id = cursor.excerpt_id;
7303
7304 let show_in_menu = self.show_edit_predictions_in_menu();
7305 let completions_menu_has_precedence = !show_in_menu
7306 && (self.context_menu.borrow().is_some()
7307 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7308
7309 if completions_menu_has_precedence
7310 || !offset_selection.is_empty()
7311 || self
7312 .active_inline_completion
7313 .as_ref()
7314 .map_or(false, |completion| {
7315 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7316 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7317 !invalidation_range.contains(&offset_selection.head())
7318 })
7319 {
7320 self.discard_inline_completion(false, cx);
7321 return None;
7322 }
7323
7324 self.take_active_inline_completion(cx);
7325 let Some(provider) = self.edit_prediction_provider() else {
7326 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7327 return None;
7328 };
7329
7330 let (buffer, cursor_buffer_position) =
7331 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7332
7333 self.edit_prediction_settings =
7334 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7335
7336 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7337
7338 if self.edit_prediction_indent_conflict {
7339 let cursor_point = cursor.to_point(&multibuffer);
7340
7341 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7342
7343 if let Some((_, indent)) = indents.iter().next() {
7344 if indent.len == cursor_point.column {
7345 self.edit_prediction_indent_conflict = false;
7346 }
7347 }
7348 }
7349
7350 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7351 let edits = inline_completion
7352 .edits
7353 .into_iter()
7354 .flat_map(|(range, new_text)| {
7355 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7356 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7357 Some((start..end, new_text))
7358 })
7359 .collect::<Vec<_>>();
7360 if edits.is_empty() {
7361 return None;
7362 }
7363
7364 let first_edit_start = edits.first().unwrap().0.start;
7365 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7366 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7367
7368 let last_edit_end = edits.last().unwrap().0.end;
7369 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7370 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7371
7372 let cursor_row = cursor.to_point(&multibuffer).row;
7373
7374 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7375
7376 let mut inlay_ids = Vec::new();
7377 let invalidation_row_range;
7378 let move_invalidation_row_range = if cursor_row < edit_start_row {
7379 Some(cursor_row..edit_end_row)
7380 } else if cursor_row > edit_end_row {
7381 Some(edit_start_row..cursor_row)
7382 } else {
7383 None
7384 };
7385 let is_move =
7386 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7387 let completion = if is_move {
7388 invalidation_row_range =
7389 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7390 let target = first_edit_start;
7391 InlineCompletion::Move { target, snapshot }
7392 } else {
7393 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7394 && !self.inline_completions_hidden_for_vim_mode;
7395
7396 if show_completions_in_buffer {
7397 if edits
7398 .iter()
7399 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7400 {
7401 let mut inlays = Vec::new();
7402 for (range, new_text) in &edits {
7403 let inlay = Inlay::inline_completion(
7404 post_inc(&mut self.next_inlay_id),
7405 range.start,
7406 new_text.as_str(),
7407 );
7408 inlay_ids.push(inlay.id);
7409 inlays.push(inlay);
7410 }
7411
7412 self.splice_inlays(&[], inlays, cx);
7413 } else {
7414 let background_color = cx.theme().status().deleted_background;
7415 self.highlight_text::<InlineCompletionHighlight>(
7416 edits.iter().map(|(range, _)| range.clone()).collect(),
7417 HighlightStyle {
7418 background_color: Some(background_color),
7419 ..Default::default()
7420 },
7421 cx,
7422 );
7423 }
7424 }
7425
7426 invalidation_row_range = edit_start_row..edit_end_row;
7427
7428 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7429 if provider.show_tab_accept_marker() {
7430 EditDisplayMode::TabAccept
7431 } else {
7432 EditDisplayMode::Inline
7433 }
7434 } else {
7435 EditDisplayMode::DiffPopover
7436 };
7437
7438 InlineCompletion::Edit {
7439 edits,
7440 edit_preview: inline_completion.edit_preview,
7441 display_mode,
7442 snapshot,
7443 }
7444 };
7445
7446 let invalidation_range = multibuffer
7447 .anchor_before(Point::new(invalidation_row_range.start, 0))
7448 ..multibuffer.anchor_after(Point::new(
7449 invalidation_row_range.end,
7450 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7451 ));
7452
7453 self.stale_inline_completion_in_menu = None;
7454 self.active_inline_completion = Some(InlineCompletionState {
7455 inlay_ids,
7456 completion,
7457 completion_id: inline_completion.id,
7458 invalidation_range,
7459 });
7460
7461 cx.notify();
7462
7463 Some(())
7464 }
7465
7466 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7467 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7468 }
7469
7470 fn clear_tasks(&mut self) {
7471 self.tasks.clear()
7472 }
7473
7474 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7475 if self.tasks.insert(key, value).is_some() {
7476 // This case should hopefully be rare, but just in case...
7477 log::error!(
7478 "multiple different run targets found on a single line, only the last target will be rendered"
7479 )
7480 }
7481 }
7482
7483 /// Get all display points of breakpoints that will be rendered within editor
7484 ///
7485 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7486 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7487 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7488 fn active_breakpoints(
7489 &self,
7490 range: Range<DisplayRow>,
7491 window: &mut Window,
7492 cx: &mut Context<Self>,
7493 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7494 let mut breakpoint_display_points = HashMap::default();
7495
7496 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7497 return breakpoint_display_points;
7498 };
7499
7500 let snapshot = self.snapshot(window, cx);
7501
7502 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7503 let Some(project) = self.project.as_ref() else {
7504 return breakpoint_display_points;
7505 };
7506
7507 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7508 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7509
7510 for (buffer_snapshot, range, excerpt_id) in
7511 multi_buffer_snapshot.range_to_buffer_ranges(range)
7512 {
7513 let Some(buffer) = project
7514 .read(cx)
7515 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7516 else {
7517 continue;
7518 };
7519 let breakpoints = breakpoint_store.read(cx).breakpoints(
7520 &buffer,
7521 Some(
7522 buffer_snapshot.anchor_before(range.start)
7523 ..buffer_snapshot.anchor_after(range.end),
7524 ),
7525 buffer_snapshot,
7526 cx,
7527 );
7528 for (breakpoint, state) in breakpoints {
7529 let multi_buffer_anchor =
7530 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7531 let position = multi_buffer_anchor
7532 .to_point(&multi_buffer_snapshot)
7533 .to_display_point(&snapshot);
7534
7535 breakpoint_display_points.insert(
7536 position.row(),
7537 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7538 );
7539 }
7540 }
7541
7542 breakpoint_display_points
7543 }
7544
7545 fn breakpoint_context_menu(
7546 &self,
7547 anchor: Anchor,
7548 window: &mut Window,
7549 cx: &mut Context<Self>,
7550 ) -> Entity<ui::ContextMenu> {
7551 let weak_editor = cx.weak_entity();
7552 let focus_handle = self.focus_handle(cx);
7553
7554 let row = self
7555 .buffer
7556 .read(cx)
7557 .snapshot(cx)
7558 .summary_for_anchor::<Point>(&anchor)
7559 .row;
7560
7561 let breakpoint = self
7562 .breakpoint_at_row(row, window, cx)
7563 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7564
7565 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7566 "Edit Log Breakpoint"
7567 } else {
7568 "Set Log Breakpoint"
7569 };
7570
7571 let condition_breakpoint_msg = if breakpoint
7572 .as_ref()
7573 .is_some_and(|bp| bp.1.condition.is_some())
7574 {
7575 "Edit Condition Breakpoint"
7576 } else {
7577 "Set Condition Breakpoint"
7578 };
7579
7580 let hit_condition_breakpoint_msg = if breakpoint
7581 .as_ref()
7582 .is_some_and(|bp| bp.1.hit_condition.is_some())
7583 {
7584 "Edit Hit Condition Breakpoint"
7585 } else {
7586 "Set Hit Condition Breakpoint"
7587 };
7588
7589 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7590 "Unset Breakpoint"
7591 } else {
7592 "Set Breakpoint"
7593 };
7594
7595 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7596
7597 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7598 BreakpointState::Enabled => Some("Disable"),
7599 BreakpointState::Disabled => Some("Enable"),
7600 });
7601
7602 let (anchor, breakpoint) =
7603 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7604
7605 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7606 menu.on_blur_subscription(Subscription::new(|| {}))
7607 .context(focus_handle)
7608 .when(run_to_cursor, |this| {
7609 let weak_editor = weak_editor.clone();
7610 this.entry("Run to cursor", None, move |window, cx| {
7611 weak_editor
7612 .update(cx, |editor, cx| {
7613 editor.change_selections(None, window, cx, |s| {
7614 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7615 });
7616 })
7617 .ok();
7618
7619 window.dispatch_action(Box::new(RunToCursor), cx);
7620 })
7621 .separator()
7622 })
7623 .when_some(toggle_state_msg, |this, msg| {
7624 this.entry(msg, None, {
7625 let weak_editor = weak_editor.clone();
7626 let breakpoint = breakpoint.clone();
7627 move |_window, cx| {
7628 weak_editor
7629 .update(cx, |this, cx| {
7630 this.edit_breakpoint_at_anchor(
7631 anchor,
7632 breakpoint.as_ref().clone(),
7633 BreakpointEditAction::InvertState,
7634 cx,
7635 );
7636 })
7637 .log_err();
7638 }
7639 })
7640 })
7641 .entry(set_breakpoint_msg, None, {
7642 let weak_editor = weak_editor.clone();
7643 let breakpoint = breakpoint.clone();
7644 move |_window, cx| {
7645 weak_editor
7646 .update(cx, |this, cx| {
7647 this.edit_breakpoint_at_anchor(
7648 anchor,
7649 breakpoint.as_ref().clone(),
7650 BreakpointEditAction::Toggle,
7651 cx,
7652 );
7653 })
7654 .log_err();
7655 }
7656 })
7657 .entry(log_breakpoint_msg, None, {
7658 let breakpoint = breakpoint.clone();
7659 let weak_editor = weak_editor.clone();
7660 move |window, cx| {
7661 weak_editor
7662 .update(cx, |this, cx| {
7663 this.add_edit_breakpoint_block(
7664 anchor,
7665 breakpoint.as_ref(),
7666 BreakpointPromptEditAction::Log,
7667 window,
7668 cx,
7669 );
7670 })
7671 .log_err();
7672 }
7673 })
7674 .entry(condition_breakpoint_msg, None, {
7675 let breakpoint = breakpoint.clone();
7676 let weak_editor = weak_editor.clone();
7677 move |window, cx| {
7678 weak_editor
7679 .update(cx, |this, cx| {
7680 this.add_edit_breakpoint_block(
7681 anchor,
7682 breakpoint.as_ref(),
7683 BreakpointPromptEditAction::Condition,
7684 window,
7685 cx,
7686 );
7687 })
7688 .log_err();
7689 }
7690 })
7691 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7692 weak_editor
7693 .update(cx, |this, cx| {
7694 this.add_edit_breakpoint_block(
7695 anchor,
7696 breakpoint.as_ref(),
7697 BreakpointPromptEditAction::HitCondition,
7698 window,
7699 cx,
7700 );
7701 })
7702 .log_err();
7703 })
7704 })
7705 }
7706
7707 fn render_breakpoint(
7708 &self,
7709 position: Anchor,
7710 row: DisplayRow,
7711 breakpoint: &Breakpoint,
7712 state: Option<BreakpointSessionState>,
7713 cx: &mut Context<Self>,
7714 ) -> IconButton {
7715 let is_rejected = state.is_some_and(|s| !s.verified);
7716 // Is it a breakpoint that shows up when hovering over gutter?
7717 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7718 (false, false),
7719 |PhantomBreakpointIndicator {
7720 is_active,
7721 display_row,
7722 collides_with_existing_breakpoint,
7723 }| {
7724 (
7725 is_active && display_row == row,
7726 collides_with_existing_breakpoint,
7727 )
7728 },
7729 );
7730
7731 let (color, icon) = {
7732 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7733 (false, false) => ui::IconName::DebugBreakpoint,
7734 (true, false) => ui::IconName::DebugLogBreakpoint,
7735 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7736 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7737 };
7738
7739 let color = if is_phantom {
7740 Color::Hint
7741 } else if is_rejected {
7742 Color::Disabled
7743 } else {
7744 Color::Debugger
7745 };
7746
7747 (color, icon)
7748 };
7749
7750 let breakpoint = Arc::from(breakpoint.clone());
7751
7752 let alt_as_text = gpui::Keystroke {
7753 modifiers: Modifiers::secondary_key(),
7754 ..Default::default()
7755 };
7756 let primary_action_text = if breakpoint.is_disabled() {
7757 "Enable breakpoint"
7758 } else if is_phantom && !collides_with_existing {
7759 "Set breakpoint"
7760 } else {
7761 "Unset breakpoint"
7762 };
7763 let focus_handle = self.focus_handle.clone();
7764
7765 let meta = if is_rejected {
7766 SharedString::from("No executable code is associated with this line.")
7767 } else if collides_with_existing && !breakpoint.is_disabled() {
7768 SharedString::from(format!(
7769 "{alt_as_text}-click to disable,\nright-click for more options."
7770 ))
7771 } else {
7772 SharedString::from("Right-click for more options.")
7773 };
7774 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7775 .icon_size(IconSize::XSmall)
7776 .size(ui::ButtonSize::None)
7777 .when(is_rejected, |this| {
7778 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7779 })
7780 .icon_color(color)
7781 .style(ButtonStyle::Transparent)
7782 .on_click(cx.listener({
7783 let breakpoint = breakpoint.clone();
7784
7785 move |editor, event: &ClickEvent, window, cx| {
7786 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7787 BreakpointEditAction::InvertState
7788 } else {
7789 BreakpointEditAction::Toggle
7790 };
7791
7792 window.focus(&editor.focus_handle(cx));
7793 editor.edit_breakpoint_at_anchor(
7794 position,
7795 breakpoint.as_ref().clone(),
7796 edit_action,
7797 cx,
7798 );
7799 }
7800 }))
7801 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7802 editor.set_breakpoint_context_menu(
7803 row,
7804 Some(position),
7805 event.down.position,
7806 window,
7807 cx,
7808 );
7809 }))
7810 .tooltip(move |window, cx| {
7811 Tooltip::with_meta_in(
7812 primary_action_text,
7813 Some(&ToggleBreakpoint),
7814 meta.clone(),
7815 &focus_handle,
7816 window,
7817 cx,
7818 )
7819 })
7820 }
7821
7822 fn build_tasks_context(
7823 project: &Entity<Project>,
7824 buffer: &Entity<Buffer>,
7825 buffer_row: u32,
7826 tasks: &Arc<RunnableTasks>,
7827 cx: &mut Context<Self>,
7828 ) -> Task<Option<task::TaskContext>> {
7829 let position = Point::new(buffer_row, tasks.column);
7830 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7831 let location = Location {
7832 buffer: buffer.clone(),
7833 range: range_start..range_start,
7834 };
7835 // Fill in the environmental variables from the tree-sitter captures
7836 let mut captured_task_variables = TaskVariables::default();
7837 for (capture_name, value) in tasks.extra_variables.clone() {
7838 captured_task_variables.insert(
7839 task::VariableName::Custom(capture_name.into()),
7840 value.clone(),
7841 );
7842 }
7843 project.update(cx, |project, cx| {
7844 project.task_store().update(cx, |task_store, cx| {
7845 task_store.task_context_for_location(captured_task_variables, location, cx)
7846 })
7847 })
7848 }
7849
7850 pub fn spawn_nearest_task(
7851 &mut self,
7852 action: &SpawnNearestTask,
7853 window: &mut Window,
7854 cx: &mut Context<Self>,
7855 ) {
7856 let Some((workspace, _)) = self.workspace.clone() else {
7857 return;
7858 };
7859 let Some(project) = self.project.clone() else {
7860 return;
7861 };
7862
7863 // Try to find a closest, enclosing node using tree-sitter that has a
7864 // task
7865 let Some((buffer, buffer_row, tasks)) = self
7866 .find_enclosing_node_task(cx)
7867 // Or find the task that's closest in row-distance.
7868 .or_else(|| self.find_closest_task(cx))
7869 else {
7870 return;
7871 };
7872
7873 let reveal_strategy = action.reveal;
7874 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7875 cx.spawn_in(window, async move |_, cx| {
7876 let context = task_context.await?;
7877 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7878
7879 let resolved = &mut resolved_task.resolved;
7880 resolved.reveal = reveal_strategy;
7881
7882 workspace
7883 .update_in(cx, |workspace, window, cx| {
7884 workspace.schedule_resolved_task(
7885 task_source_kind,
7886 resolved_task,
7887 false,
7888 window,
7889 cx,
7890 );
7891 })
7892 .ok()
7893 })
7894 .detach();
7895 }
7896
7897 fn find_closest_task(
7898 &mut self,
7899 cx: &mut Context<Self>,
7900 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7901 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7902
7903 let ((buffer_id, row), tasks) = self
7904 .tasks
7905 .iter()
7906 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7907
7908 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7909 let tasks = Arc::new(tasks.to_owned());
7910 Some((buffer, *row, tasks))
7911 }
7912
7913 fn find_enclosing_node_task(
7914 &mut self,
7915 cx: &mut Context<Self>,
7916 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7917 let snapshot = self.buffer.read(cx).snapshot(cx);
7918 let offset = self.selections.newest::<usize>(cx).head();
7919 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7920 let buffer_id = excerpt.buffer().remote_id();
7921
7922 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7923 let mut cursor = layer.node().walk();
7924
7925 while cursor.goto_first_child_for_byte(offset).is_some() {
7926 if cursor.node().end_byte() == offset {
7927 cursor.goto_next_sibling();
7928 }
7929 }
7930
7931 // Ascend to the smallest ancestor that contains the range and has a task.
7932 loop {
7933 let node = cursor.node();
7934 let node_range = node.byte_range();
7935 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7936
7937 // Check if this node contains our offset
7938 if node_range.start <= offset && node_range.end >= offset {
7939 // If it contains offset, check for task
7940 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7941 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7942 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7943 }
7944 }
7945
7946 if !cursor.goto_parent() {
7947 break;
7948 }
7949 }
7950 None
7951 }
7952
7953 fn render_run_indicator(
7954 &self,
7955 _style: &EditorStyle,
7956 is_active: bool,
7957 row: DisplayRow,
7958 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7959 cx: &mut Context<Self>,
7960 ) -> IconButton {
7961 let color = Color::Muted;
7962 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7963
7964 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7965 .shape(ui::IconButtonShape::Square)
7966 .icon_size(IconSize::XSmall)
7967 .icon_color(color)
7968 .toggle_state(is_active)
7969 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7970 let quick_launch = e.down.button == MouseButton::Left;
7971 window.focus(&editor.focus_handle(cx));
7972 editor.toggle_code_actions(
7973 &ToggleCodeActions {
7974 deployed_from: Some(CodeActionSource::RunMenu(row)),
7975 quick_launch,
7976 },
7977 window,
7978 cx,
7979 );
7980 }))
7981 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7982 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7983 }))
7984 }
7985
7986 pub fn context_menu_visible(&self) -> bool {
7987 !self.edit_prediction_preview_is_active()
7988 && self
7989 .context_menu
7990 .borrow()
7991 .as_ref()
7992 .map_or(false, |menu| menu.visible())
7993 }
7994
7995 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7996 self.context_menu
7997 .borrow()
7998 .as_ref()
7999 .map(|menu| menu.origin())
8000 }
8001
8002 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8003 self.context_menu_options = Some(options);
8004 }
8005
8006 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8007 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8008
8009 fn render_edit_prediction_popover(
8010 &mut self,
8011 text_bounds: &Bounds<Pixels>,
8012 content_origin: gpui::Point<Pixels>,
8013 right_margin: Pixels,
8014 editor_snapshot: &EditorSnapshot,
8015 visible_row_range: Range<DisplayRow>,
8016 scroll_top: f32,
8017 scroll_bottom: f32,
8018 line_layouts: &[LineWithInvisibles],
8019 line_height: Pixels,
8020 scroll_pixel_position: gpui::Point<Pixels>,
8021 newest_selection_head: Option<DisplayPoint>,
8022 editor_width: Pixels,
8023 style: &EditorStyle,
8024 window: &mut Window,
8025 cx: &mut App,
8026 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8027 if self.mode().is_minimap() {
8028 return None;
8029 }
8030 let active_inline_completion = self.active_inline_completion.as_ref()?;
8031
8032 if self.edit_prediction_visible_in_cursor_popover(true) {
8033 return None;
8034 }
8035
8036 match &active_inline_completion.completion {
8037 InlineCompletion::Move { target, .. } => {
8038 let target_display_point = target.to_display_point(editor_snapshot);
8039
8040 if self.edit_prediction_requires_modifier() {
8041 if !self.edit_prediction_preview_is_active() {
8042 return None;
8043 }
8044
8045 self.render_edit_prediction_modifier_jump_popover(
8046 text_bounds,
8047 content_origin,
8048 visible_row_range,
8049 line_layouts,
8050 line_height,
8051 scroll_pixel_position,
8052 newest_selection_head,
8053 target_display_point,
8054 window,
8055 cx,
8056 )
8057 } else {
8058 self.render_edit_prediction_eager_jump_popover(
8059 text_bounds,
8060 content_origin,
8061 editor_snapshot,
8062 visible_row_range,
8063 scroll_top,
8064 scroll_bottom,
8065 line_height,
8066 scroll_pixel_position,
8067 target_display_point,
8068 editor_width,
8069 window,
8070 cx,
8071 )
8072 }
8073 }
8074 InlineCompletion::Edit {
8075 display_mode: EditDisplayMode::Inline,
8076 ..
8077 } => None,
8078 InlineCompletion::Edit {
8079 display_mode: EditDisplayMode::TabAccept,
8080 edits,
8081 ..
8082 } => {
8083 let range = &edits.first()?.0;
8084 let target_display_point = range.end.to_display_point(editor_snapshot);
8085
8086 self.render_edit_prediction_end_of_line_popover(
8087 "Accept",
8088 editor_snapshot,
8089 visible_row_range,
8090 target_display_point,
8091 line_height,
8092 scroll_pixel_position,
8093 content_origin,
8094 editor_width,
8095 window,
8096 cx,
8097 )
8098 }
8099 InlineCompletion::Edit {
8100 edits,
8101 edit_preview,
8102 display_mode: EditDisplayMode::DiffPopover,
8103 snapshot,
8104 } => self.render_edit_prediction_diff_popover(
8105 text_bounds,
8106 content_origin,
8107 right_margin,
8108 editor_snapshot,
8109 visible_row_range,
8110 line_layouts,
8111 line_height,
8112 scroll_pixel_position,
8113 newest_selection_head,
8114 editor_width,
8115 style,
8116 edits,
8117 edit_preview,
8118 snapshot,
8119 window,
8120 cx,
8121 ),
8122 }
8123 }
8124
8125 fn render_edit_prediction_modifier_jump_popover(
8126 &mut self,
8127 text_bounds: &Bounds<Pixels>,
8128 content_origin: gpui::Point<Pixels>,
8129 visible_row_range: Range<DisplayRow>,
8130 line_layouts: &[LineWithInvisibles],
8131 line_height: Pixels,
8132 scroll_pixel_position: gpui::Point<Pixels>,
8133 newest_selection_head: Option<DisplayPoint>,
8134 target_display_point: DisplayPoint,
8135 window: &mut Window,
8136 cx: &mut App,
8137 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8138 let scrolled_content_origin =
8139 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8140
8141 const SCROLL_PADDING_Y: Pixels = px(12.);
8142
8143 if target_display_point.row() < visible_row_range.start {
8144 return self.render_edit_prediction_scroll_popover(
8145 |_| SCROLL_PADDING_Y,
8146 IconName::ArrowUp,
8147 visible_row_range,
8148 line_layouts,
8149 newest_selection_head,
8150 scrolled_content_origin,
8151 window,
8152 cx,
8153 );
8154 } else if target_display_point.row() >= visible_row_range.end {
8155 return self.render_edit_prediction_scroll_popover(
8156 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8157 IconName::ArrowDown,
8158 visible_row_range,
8159 line_layouts,
8160 newest_selection_head,
8161 scrolled_content_origin,
8162 window,
8163 cx,
8164 );
8165 }
8166
8167 const POLE_WIDTH: Pixels = px(2.);
8168
8169 let line_layout =
8170 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8171 let target_column = target_display_point.column() as usize;
8172
8173 let target_x = line_layout.x_for_index(target_column);
8174 let target_y =
8175 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8176
8177 let flag_on_right = target_x < text_bounds.size.width / 2.;
8178
8179 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8180 border_color.l += 0.001;
8181
8182 let mut element = v_flex()
8183 .items_end()
8184 .when(flag_on_right, |el| el.items_start())
8185 .child(if flag_on_right {
8186 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8187 .rounded_bl(px(0.))
8188 .rounded_tl(px(0.))
8189 .border_l_2()
8190 .border_color(border_color)
8191 } else {
8192 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8193 .rounded_br(px(0.))
8194 .rounded_tr(px(0.))
8195 .border_r_2()
8196 .border_color(border_color)
8197 })
8198 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8199 .into_any();
8200
8201 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8202
8203 let mut origin = scrolled_content_origin + point(target_x, target_y)
8204 - point(
8205 if flag_on_right {
8206 POLE_WIDTH
8207 } else {
8208 size.width - POLE_WIDTH
8209 },
8210 size.height - line_height,
8211 );
8212
8213 origin.x = origin.x.max(content_origin.x);
8214
8215 element.prepaint_at(origin, window, cx);
8216
8217 Some((element, origin))
8218 }
8219
8220 fn render_edit_prediction_scroll_popover(
8221 &mut self,
8222 to_y: impl Fn(Size<Pixels>) -> Pixels,
8223 scroll_icon: IconName,
8224 visible_row_range: Range<DisplayRow>,
8225 line_layouts: &[LineWithInvisibles],
8226 newest_selection_head: Option<DisplayPoint>,
8227 scrolled_content_origin: gpui::Point<Pixels>,
8228 window: &mut Window,
8229 cx: &mut App,
8230 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8231 let mut element = self
8232 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8233 .into_any();
8234
8235 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8236
8237 let cursor = newest_selection_head?;
8238 let cursor_row_layout =
8239 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8240 let cursor_column = cursor.column() as usize;
8241
8242 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8243
8244 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8245
8246 element.prepaint_at(origin, window, cx);
8247 Some((element, origin))
8248 }
8249
8250 fn render_edit_prediction_eager_jump_popover(
8251 &mut self,
8252 text_bounds: &Bounds<Pixels>,
8253 content_origin: gpui::Point<Pixels>,
8254 editor_snapshot: &EditorSnapshot,
8255 visible_row_range: Range<DisplayRow>,
8256 scroll_top: f32,
8257 scroll_bottom: f32,
8258 line_height: Pixels,
8259 scroll_pixel_position: gpui::Point<Pixels>,
8260 target_display_point: DisplayPoint,
8261 editor_width: Pixels,
8262 window: &mut Window,
8263 cx: &mut App,
8264 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8265 if target_display_point.row().as_f32() < scroll_top {
8266 let mut element = self
8267 .render_edit_prediction_line_popover(
8268 "Jump to Edit",
8269 Some(IconName::ArrowUp),
8270 window,
8271 cx,
8272 )?
8273 .into_any();
8274
8275 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8276 let offset = point(
8277 (text_bounds.size.width - size.width) / 2.,
8278 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8279 );
8280
8281 let origin = text_bounds.origin + offset;
8282 element.prepaint_at(origin, window, cx);
8283 Some((element, origin))
8284 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8285 let mut element = self
8286 .render_edit_prediction_line_popover(
8287 "Jump to Edit",
8288 Some(IconName::ArrowDown),
8289 window,
8290 cx,
8291 )?
8292 .into_any();
8293
8294 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8295 let offset = point(
8296 (text_bounds.size.width - size.width) / 2.,
8297 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8298 );
8299
8300 let origin = text_bounds.origin + offset;
8301 element.prepaint_at(origin, window, cx);
8302 Some((element, origin))
8303 } else {
8304 self.render_edit_prediction_end_of_line_popover(
8305 "Jump to Edit",
8306 editor_snapshot,
8307 visible_row_range,
8308 target_display_point,
8309 line_height,
8310 scroll_pixel_position,
8311 content_origin,
8312 editor_width,
8313 window,
8314 cx,
8315 )
8316 }
8317 }
8318
8319 fn render_edit_prediction_end_of_line_popover(
8320 self: &mut Editor,
8321 label: &'static str,
8322 editor_snapshot: &EditorSnapshot,
8323 visible_row_range: Range<DisplayRow>,
8324 target_display_point: DisplayPoint,
8325 line_height: Pixels,
8326 scroll_pixel_position: gpui::Point<Pixels>,
8327 content_origin: gpui::Point<Pixels>,
8328 editor_width: Pixels,
8329 window: &mut Window,
8330 cx: &mut App,
8331 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8332 let target_line_end = DisplayPoint::new(
8333 target_display_point.row(),
8334 editor_snapshot.line_len(target_display_point.row()),
8335 );
8336
8337 let mut element = self
8338 .render_edit_prediction_line_popover(label, None, window, cx)?
8339 .into_any();
8340
8341 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8342
8343 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8344
8345 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8346 let mut origin = start_point
8347 + line_origin
8348 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8349 origin.x = origin.x.max(content_origin.x);
8350
8351 let max_x = content_origin.x + editor_width - size.width;
8352
8353 if origin.x > max_x {
8354 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8355
8356 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8357 origin.y += offset;
8358 IconName::ArrowUp
8359 } else {
8360 origin.y -= offset;
8361 IconName::ArrowDown
8362 };
8363
8364 element = self
8365 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8366 .into_any();
8367
8368 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8369
8370 origin.x = content_origin.x + editor_width - size.width - px(2.);
8371 }
8372
8373 element.prepaint_at(origin, window, cx);
8374 Some((element, origin))
8375 }
8376
8377 fn render_edit_prediction_diff_popover(
8378 self: &Editor,
8379 text_bounds: &Bounds<Pixels>,
8380 content_origin: gpui::Point<Pixels>,
8381 right_margin: Pixels,
8382 editor_snapshot: &EditorSnapshot,
8383 visible_row_range: Range<DisplayRow>,
8384 line_layouts: &[LineWithInvisibles],
8385 line_height: Pixels,
8386 scroll_pixel_position: gpui::Point<Pixels>,
8387 newest_selection_head: Option<DisplayPoint>,
8388 editor_width: Pixels,
8389 style: &EditorStyle,
8390 edits: &Vec<(Range<Anchor>, String)>,
8391 edit_preview: &Option<language::EditPreview>,
8392 snapshot: &language::BufferSnapshot,
8393 window: &mut Window,
8394 cx: &mut App,
8395 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8396 let edit_start = edits
8397 .first()
8398 .unwrap()
8399 .0
8400 .start
8401 .to_display_point(editor_snapshot);
8402 let edit_end = edits
8403 .last()
8404 .unwrap()
8405 .0
8406 .end
8407 .to_display_point(editor_snapshot);
8408
8409 let is_visible = visible_row_range.contains(&edit_start.row())
8410 || visible_row_range.contains(&edit_end.row());
8411 if !is_visible {
8412 return None;
8413 }
8414
8415 let highlighted_edits =
8416 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8417
8418 let styled_text = highlighted_edits.to_styled_text(&style.text);
8419 let line_count = highlighted_edits.text.lines().count();
8420
8421 const BORDER_WIDTH: Pixels = px(1.);
8422
8423 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8424 let has_keybind = keybind.is_some();
8425
8426 let mut element = h_flex()
8427 .items_start()
8428 .child(
8429 h_flex()
8430 .bg(cx.theme().colors().editor_background)
8431 .border(BORDER_WIDTH)
8432 .shadow_sm()
8433 .border_color(cx.theme().colors().border)
8434 .rounded_l_lg()
8435 .when(line_count > 1, |el| el.rounded_br_lg())
8436 .pr_1()
8437 .child(styled_text),
8438 )
8439 .child(
8440 h_flex()
8441 .h(line_height + BORDER_WIDTH * 2.)
8442 .px_1p5()
8443 .gap_1()
8444 // Workaround: For some reason, there's a gap if we don't do this
8445 .ml(-BORDER_WIDTH)
8446 .shadow(vec![gpui::BoxShadow {
8447 color: gpui::black().opacity(0.05),
8448 offset: point(px(1.), px(1.)),
8449 blur_radius: px(2.),
8450 spread_radius: px(0.),
8451 }])
8452 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8453 .border(BORDER_WIDTH)
8454 .border_color(cx.theme().colors().border)
8455 .rounded_r_lg()
8456 .id("edit_prediction_diff_popover_keybind")
8457 .when(!has_keybind, |el| {
8458 let status_colors = cx.theme().status();
8459
8460 el.bg(status_colors.error_background)
8461 .border_color(status_colors.error.opacity(0.6))
8462 .child(Icon::new(IconName::Info).color(Color::Error))
8463 .cursor_default()
8464 .hoverable_tooltip(move |_window, cx| {
8465 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8466 })
8467 })
8468 .children(keybind),
8469 )
8470 .into_any();
8471
8472 let longest_row =
8473 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8474 let longest_line_width = if visible_row_range.contains(&longest_row) {
8475 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8476 } else {
8477 layout_line(
8478 longest_row,
8479 editor_snapshot,
8480 style,
8481 editor_width,
8482 |_| false,
8483 window,
8484 cx,
8485 )
8486 .width
8487 };
8488
8489 let viewport_bounds =
8490 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8491 right: -right_margin,
8492 ..Default::default()
8493 });
8494
8495 let x_after_longest =
8496 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8497 - scroll_pixel_position.x;
8498
8499 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8500
8501 // Fully visible if it can be displayed within the window (allow overlapping other
8502 // panes). However, this is only allowed if the popover starts within text_bounds.
8503 let can_position_to_the_right = x_after_longest < text_bounds.right()
8504 && x_after_longest + element_bounds.width < viewport_bounds.right();
8505
8506 let mut origin = if can_position_to_the_right {
8507 point(
8508 x_after_longest,
8509 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8510 - scroll_pixel_position.y,
8511 )
8512 } else {
8513 let cursor_row = newest_selection_head.map(|head| head.row());
8514 let above_edit = edit_start
8515 .row()
8516 .0
8517 .checked_sub(line_count as u32)
8518 .map(DisplayRow);
8519 let below_edit = Some(edit_end.row() + 1);
8520 let above_cursor =
8521 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8522 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8523
8524 // Place the edit popover adjacent to the edit if there is a location
8525 // available that is onscreen and does not obscure the cursor. Otherwise,
8526 // place it adjacent to the cursor.
8527 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8528 .into_iter()
8529 .flatten()
8530 .find(|&start_row| {
8531 let end_row = start_row + line_count as u32;
8532 visible_row_range.contains(&start_row)
8533 && visible_row_range.contains(&end_row)
8534 && cursor_row.map_or(true, |cursor_row| {
8535 !((start_row..end_row).contains(&cursor_row))
8536 })
8537 })?;
8538
8539 content_origin
8540 + point(
8541 -scroll_pixel_position.x,
8542 row_target.as_f32() * line_height - scroll_pixel_position.y,
8543 )
8544 };
8545
8546 origin.x -= BORDER_WIDTH;
8547
8548 window.defer_draw(element, origin, 1);
8549
8550 // Do not return an element, since it will already be drawn due to defer_draw.
8551 None
8552 }
8553
8554 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8555 px(30.)
8556 }
8557
8558 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8559 if self.read_only(cx) {
8560 cx.theme().players().read_only()
8561 } else {
8562 self.style.as_ref().unwrap().local_player
8563 }
8564 }
8565
8566 fn render_edit_prediction_accept_keybind(
8567 &self,
8568 window: &mut Window,
8569 cx: &App,
8570 ) -> Option<AnyElement> {
8571 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8572 let accept_keystroke = accept_binding.keystroke()?;
8573
8574 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8575
8576 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8577 Color::Accent
8578 } else {
8579 Color::Muted
8580 };
8581
8582 h_flex()
8583 .px_0p5()
8584 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8585 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8586 .text_size(TextSize::XSmall.rems(cx))
8587 .child(h_flex().children(ui::render_modifiers(
8588 &accept_keystroke.modifiers,
8589 PlatformStyle::platform(),
8590 Some(modifiers_color),
8591 Some(IconSize::XSmall.rems().into()),
8592 true,
8593 )))
8594 .when(is_platform_style_mac, |parent| {
8595 parent.child(accept_keystroke.key.clone())
8596 })
8597 .when(!is_platform_style_mac, |parent| {
8598 parent.child(
8599 Key::new(
8600 util::capitalize(&accept_keystroke.key),
8601 Some(Color::Default),
8602 )
8603 .size(Some(IconSize::XSmall.rems().into())),
8604 )
8605 })
8606 .into_any()
8607 .into()
8608 }
8609
8610 fn render_edit_prediction_line_popover(
8611 &self,
8612 label: impl Into<SharedString>,
8613 icon: Option<IconName>,
8614 window: &mut Window,
8615 cx: &App,
8616 ) -> Option<Stateful<Div>> {
8617 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8618
8619 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8620 let has_keybind = keybind.is_some();
8621
8622 let result = h_flex()
8623 .id("ep-line-popover")
8624 .py_0p5()
8625 .pl_1()
8626 .pr(padding_right)
8627 .gap_1()
8628 .rounded_md()
8629 .border_1()
8630 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8631 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8632 .shadow_sm()
8633 .when(!has_keybind, |el| {
8634 let status_colors = cx.theme().status();
8635
8636 el.bg(status_colors.error_background)
8637 .border_color(status_colors.error.opacity(0.6))
8638 .pl_2()
8639 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8640 .cursor_default()
8641 .hoverable_tooltip(move |_window, cx| {
8642 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8643 })
8644 })
8645 .children(keybind)
8646 .child(
8647 Label::new(label)
8648 .size(LabelSize::Small)
8649 .when(!has_keybind, |el| {
8650 el.color(cx.theme().status().error.into()).strikethrough()
8651 }),
8652 )
8653 .when(!has_keybind, |el| {
8654 el.child(
8655 h_flex().ml_1().child(
8656 Icon::new(IconName::Info)
8657 .size(IconSize::Small)
8658 .color(cx.theme().status().error.into()),
8659 ),
8660 )
8661 })
8662 .when_some(icon, |element, icon| {
8663 element.child(
8664 div()
8665 .mt(px(1.5))
8666 .child(Icon::new(icon).size(IconSize::Small)),
8667 )
8668 });
8669
8670 Some(result)
8671 }
8672
8673 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8674 let accent_color = cx.theme().colors().text_accent;
8675 let editor_bg_color = cx.theme().colors().editor_background;
8676 editor_bg_color.blend(accent_color.opacity(0.1))
8677 }
8678
8679 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8680 let accent_color = cx.theme().colors().text_accent;
8681 let editor_bg_color = cx.theme().colors().editor_background;
8682 editor_bg_color.blend(accent_color.opacity(0.6))
8683 }
8684
8685 fn render_edit_prediction_cursor_popover(
8686 &self,
8687 min_width: Pixels,
8688 max_width: Pixels,
8689 cursor_point: Point,
8690 style: &EditorStyle,
8691 accept_keystroke: Option<&gpui::Keystroke>,
8692 _window: &Window,
8693 cx: &mut Context<Editor>,
8694 ) -> Option<AnyElement> {
8695 let provider = self.edit_prediction_provider.as_ref()?;
8696
8697 if provider.provider.needs_terms_acceptance(cx) {
8698 return Some(
8699 h_flex()
8700 .min_w(min_width)
8701 .flex_1()
8702 .px_2()
8703 .py_1()
8704 .gap_3()
8705 .elevation_2(cx)
8706 .hover(|style| style.bg(cx.theme().colors().element_hover))
8707 .id("accept-terms")
8708 .cursor_pointer()
8709 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8710 .on_click(cx.listener(|this, _event, window, cx| {
8711 cx.stop_propagation();
8712 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8713 window.dispatch_action(
8714 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8715 cx,
8716 );
8717 }))
8718 .child(
8719 h_flex()
8720 .flex_1()
8721 .gap_2()
8722 .child(Icon::new(IconName::ZedPredict))
8723 .child(Label::new("Accept Terms of Service"))
8724 .child(div().w_full())
8725 .child(
8726 Icon::new(IconName::ArrowUpRight)
8727 .color(Color::Muted)
8728 .size(IconSize::Small),
8729 )
8730 .into_any_element(),
8731 )
8732 .into_any(),
8733 );
8734 }
8735
8736 let is_refreshing = provider.provider.is_refreshing(cx);
8737
8738 fn pending_completion_container() -> Div {
8739 h_flex()
8740 .h_full()
8741 .flex_1()
8742 .gap_2()
8743 .child(Icon::new(IconName::ZedPredict))
8744 }
8745
8746 let completion = match &self.active_inline_completion {
8747 Some(prediction) => {
8748 if !self.has_visible_completions_menu() {
8749 const RADIUS: Pixels = px(6.);
8750 const BORDER_WIDTH: Pixels = px(1.);
8751
8752 return Some(
8753 h_flex()
8754 .elevation_2(cx)
8755 .border(BORDER_WIDTH)
8756 .border_color(cx.theme().colors().border)
8757 .when(accept_keystroke.is_none(), |el| {
8758 el.border_color(cx.theme().status().error)
8759 })
8760 .rounded(RADIUS)
8761 .rounded_tl(px(0.))
8762 .overflow_hidden()
8763 .child(div().px_1p5().child(match &prediction.completion {
8764 InlineCompletion::Move { target, snapshot } => {
8765 use text::ToPoint as _;
8766 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8767 {
8768 Icon::new(IconName::ZedPredictDown)
8769 } else {
8770 Icon::new(IconName::ZedPredictUp)
8771 }
8772 }
8773 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8774 }))
8775 .child(
8776 h_flex()
8777 .gap_1()
8778 .py_1()
8779 .px_2()
8780 .rounded_r(RADIUS - BORDER_WIDTH)
8781 .border_l_1()
8782 .border_color(cx.theme().colors().border)
8783 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8784 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8785 el.child(
8786 Label::new("Hold")
8787 .size(LabelSize::Small)
8788 .when(accept_keystroke.is_none(), |el| {
8789 el.strikethrough()
8790 })
8791 .line_height_style(LineHeightStyle::UiLabel),
8792 )
8793 })
8794 .id("edit_prediction_cursor_popover_keybind")
8795 .when(accept_keystroke.is_none(), |el| {
8796 let status_colors = cx.theme().status();
8797
8798 el.bg(status_colors.error_background)
8799 .border_color(status_colors.error.opacity(0.6))
8800 .child(Icon::new(IconName::Info).color(Color::Error))
8801 .cursor_default()
8802 .hoverable_tooltip(move |_window, cx| {
8803 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8804 .into()
8805 })
8806 })
8807 .when_some(
8808 accept_keystroke.as_ref(),
8809 |el, accept_keystroke| {
8810 el.child(h_flex().children(ui::render_modifiers(
8811 &accept_keystroke.modifiers,
8812 PlatformStyle::platform(),
8813 Some(Color::Default),
8814 Some(IconSize::XSmall.rems().into()),
8815 false,
8816 )))
8817 },
8818 ),
8819 )
8820 .into_any(),
8821 );
8822 }
8823
8824 self.render_edit_prediction_cursor_popover_preview(
8825 prediction,
8826 cursor_point,
8827 style,
8828 cx,
8829 )?
8830 }
8831
8832 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8833 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8834 stale_completion,
8835 cursor_point,
8836 style,
8837 cx,
8838 )?,
8839
8840 None => {
8841 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8842 }
8843 },
8844
8845 None => pending_completion_container().child(Label::new("No Prediction")),
8846 };
8847
8848 let completion = if is_refreshing {
8849 completion
8850 .with_animation(
8851 "loading-completion",
8852 Animation::new(Duration::from_secs(2))
8853 .repeat()
8854 .with_easing(pulsating_between(0.4, 0.8)),
8855 |label, delta| label.opacity(delta),
8856 )
8857 .into_any_element()
8858 } else {
8859 completion.into_any_element()
8860 };
8861
8862 let has_completion = self.active_inline_completion.is_some();
8863
8864 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8865 Some(
8866 h_flex()
8867 .min_w(min_width)
8868 .max_w(max_width)
8869 .flex_1()
8870 .elevation_2(cx)
8871 .border_color(cx.theme().colors().border)
8872 .child(
8873 div()
8874 .flex_1()
8875 .py_1()
8876 .px_2()
8877 .overflow_hidden()
8878 .child(completion),
8879 )
8880 .when_some(accept_keystroke, |el, accept_keystroke| {
8881 if !accept_keystroke.modifiers.modified() {
8882 return el;
8883 }
8884
8885 el.child(
8886 h_flex()
8887 .h_full()
8888 .border_l_1()
8889 .rounded_r_lg()
8890 .border_color(cx.theme().colors().border)
8891 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8892 .gap_1()
8893 .py_1()
8894 .px_2()
8895 .child(
8896 h_flex()
8897 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8898 .when(is_platform_style_mac, |parent| parent.gap_1())
8899 .child(h_flex().children(ui::render_modifiers(
8900 &accept_keystroke.modifiers,
8901 PlatformStyle::platform(),
8902 Some(if !has_completion {
8903 Color::Muted
8904 } else {
8905 Color::Default
8906 }),
8907 None,
8908 false,
8909 ))),
8910 )
8911 .child(Label::new("Preview").into_any_element())
8912 .opacity(if has_completion { 1.0 } else { 0.4 }),
8913 )
8914 })
8915 .into_any(),
8916 )
8917 }
8918
8919 fn render_edit_prediction_cursor_popover_preview(
8920 &self,
8921 completion: &InlineCompletionState,
8922 cursor_point: Point,
8923 style: &EditorStyle,
8924 cx: &mut Context<Editor>,
8925 ) -> Option<Div> {
8926 use text::ToPoint as _;
8927
8928 fn render_relative_row_jump(
8929 prefix: impl Into<String>,
8930 current_row: u32,
8931 target_row: u32,
8932 ) -> Div {
8933 let (row_diff, arrow) = if target_row < current_row {
8934 (current_row - target_row, IconName::ArrowUp)
8935 } else {
8936 (target_row - current_row, IconName::ArrowDown)
8937 };
8938
8939 h_flex()
8940 .child(
8941 Label::new(format!("{}{}", prefix.into(), row_diff))
8942 .color(Color::Muted)
8943 .size(LabelSize::Small),
8944 )
8945 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8946 }
8947
8948 match &completion.completion {
8949 InlineCompletion::Move {
8950 target, snapshot, ..
8951 } => Some(
8952 h_flex()
8953 .px_2()
8954 .gap_2()
8955 .flex_1()
8956 .child(
8957 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8958 Icon::new(IconName::ZedPredictDown)
8959 } else {
8960 Icon::new(IconName::ZedPredictUp)
8961 },
8962 )
8963 .child(Label::new("Jump to Edit")),
8964 ),
8965
8966 InlineCompletion::Edit {
8967 edits,
8968 edit_preview,
8969 snapshot,
8970 display_mode: _,
8971 } => {
8972 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8973
8974 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8975 &snapshot,
8976 &edits,
8977 edit_preview.as_ref()?,
8978 true,
8979 cx,
8980 )
8981 .first_line_preview();
8982
8983 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8984 .with_default_highlights(&style.text, highlighted_edits.highlights);
8985
8986 let preview = h_flex()
8987 .gap_1()
8988 .min_w_16()
8989 .child(styled_text)
8990 .when(has_more_lines, |parent| parent.child("…"));
8991
8992 let left = if first_edit_row != cursor_point.row {
8993 render_relative_row_jump("", cursor_point.row, first_edit_row)
8994 .into_any_element()
8995 } else {
8996 Icon::new(IconName::ZedPredict).into_any_element()
8997 };
8998
8999 Some(
9000 h_flex()
9001 .h_full()
9002 .flex_1()
9003 .gap_2()
9004 .pr_1()
9005 .overflow_x_hidden()
9006 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9007 .child(left)
9008 .child(preview),
9009 )
9010 }
9011 }
9012 }
9013
9014 pub fn render_context_menu(
9015 &self,
9016 style: &EditorStyle,
9017 max_height_in_lines: u32,
9018 window: &mut Window,
9019 cx: &mut Context<Editor>,
9020 ) -> Option<AnyElement> {
9021 let menu = self.context_menu.borrow();
9022 let menu = menu.as_ref()?;
9023 if !menu.visible() {
9024 return None;
9025 };
9026 Some(menu.render(style, max_height_in_lines, window, cx))
9027 }
9028
9029 fn render_context_menu_aside(
9030 &mut self,
9031 max_size: Size<Pixels>,
9032 window: &mut Window,
9033 cx: &mut Context<Editor>,
9034 ) -> Option<AnyElement> {
9035 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9036 if menu.visible() {
9037 menu.render_aside(max_size, window, cx)
9038 } else {
9039 None
9040 }
9041 })
9042 }
9043
9044 fn hide_context_menu(
9045 &mut self,
9046 window: &mut Window,
9047 cx: &mut Context<Self>,
9048 ) -> Option<CodeContextMenu> {
9049 cx.notify();
9050 self.completion_tasks.clear();
9051 let context_menu = self.context_menu.borrow_mut().take();
9052 self.stale_inline_completion_in_menu.take();
9053 self.update_visible_inline_completion(window, cx);
9054 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9055 if let Some(completion_provider) = &self.completion_provider {
9056 completion_provider.selection_changed(None, window, cx);
9057 }
9058 }
9059 context_menu
9060 }
9061
9062 fn show_snippet_choices(
9063 &mut self,
9064 choices: &Vec<String>,
9065 selection: Range<Anchor>,
9066 cx: &mut Context<Self>,
9067 ) {
9068 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9069 (Some(a), Some(b)) if a == b => a,
9070 _ => {
9071 log::error!("expected anchor range to have matching buffer IDs");
9072 return;
9073 }
9074 };
9075 let multi_buffer = self.buffer().read(cx);
9076 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9077 return;
9078 };
9079
9080 let id = post_inc(&mut self.next_completion_id);
9081 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9082 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9083 CompletionsMenu::new_snippet_choices(
9084 id,
9085 true,
9086 choices,
9087 selection,
9088 buffer,
9089 snippet_sort_order,
9090 ),
9091 ));
9092 }
9093
9094 pub fn insert_snippet(
9095 &mut self,
9096 insertion_ranges: &[Range<usize>],
9097 snippet: Snippet,
9098 window: &mut Window,
9099 cx: &mut Context<Self>,
9100 ) -> Result<()> {
9101 struct Tabstop<T> {
9102 is_end_tabstop: bool,
9103 ranges: Vec<Range<T>>,
9104 choices: Option<Vec<String>>,
9105 }
9106
9107 let tabstops = self.buffer.update(cx, |buffer, cx| {
9108 let snippet_text: Arc<str> = snippet.text.clone().into();
9109 let edits = insertion_ranges
9110 .iter()
9111 .cloned()
9112 .map(|range| (range, snippet_text.clone()));
9113 let autoindent_mode = AutoindentMode::Block {
9114 original_indent_columns: Vec::new(),
9115 };
9116 buffer.edit(edits, Some(autoindent_mode), cx);
9117
9118 let snapshot = &*buffer.read(cx);
9119 let snippet = &snippet;
9120 snippet
9121 .tabstops
9122 .iter()
9123 .map(|tabstop| {
9124 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9125 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9126 });
9127 let mut tabstop_ranges = tabstop
9128 .ranges
9129 .iter()
9130 .flat_map(|tabstop_range| {
9131 let mut delta = 0_isize;
9132 insertion_ranges.iter().map(move |insertion_range| {
9133 let insertion_start = insertion_range.start as isize + delta;
9134 delta +=
9135 snippet.text.len() as isize - insertion_range.len() as isize;
9136
9137 let start = ((insertion_start + tabstop_range.start) as usize)
9138 .min(snapshot.len());
9139 let end = ((insertion_start + tabstop_range.end) as usize)
9140 .min(snapshot.len());
9141 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9142 })
9143 })
9144 .collect::<Vec<_>>();
9145 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9146
9147 Tabstop {
9148 is_end_tabstop,
9149 ranges: tabstop_ranges,
9150 choices: tabstop.choices.clone(),
9151 }
9152 })
9153 .collect::<Vec<_>>()
9154 });
9155 if let Some(tabstop) = tabstops.first() {
9156 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9157 // Reverse order so that the first range is the newest created selection.
9158 // Completions will use it and autoscroll will prioritize it.
9159 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9160 });
9161
9162 if let Some(choices) = &tabstop.choices {
9163 if let Some(selection) = tabstop.ranges.first() {
9164 self.show_snippet_choices(choices, selection.clone(), cx)
9165 }
9166 }
9167
9168 // If we're already at the last tabstop and it's at the end of the snippet,
9169 // we're done, we don't need to keep the state around.
9170 if !tabstop.is_end_tabstop {
9171 let choices = tabstops
9172 .iter()
9173 .map(|tabstop| tabstop.choices.clone())
9174 .collect();
9175
9176 let ranges = tabstops
9177 .into_iter()
9178 .map(|tabstop| tabstop.ranges)
9179 .collect::<Vec<_>>();
9180
9181 self.snippet_stack.push(SnippetState {
9182 active_index: 0,
9183 ranges,
9184 choices,
9185 });
9186 }
9187
9188 // Check whether the just-entered snippet ends with an auto-closable bracket.
9189 if self.autoclose_regions.is_empty() {
9190 let snapshot = self.buffer.read(cx).snapshot(cx);
9191 for selection in &mut self.selections.all::<Point>(cx) {
9192 let selection_head = selection.head();
9193 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9194 continue;
9195 };
9196
9197 let mut bracket_pair = None;
9198 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9199 let prev_chars = snapshot
9200 .reversed_chars_at(selection_head)
9201 .collect::<String>();
9202 for (pair, enabled) in scope.brackets() {
9203 if enabled
9204 && pair.close
9205 && prev_chars.starts_with(pair.start.as_str())
9206 && next_chars.starts_with(pair.end.as_str())
9207 {
9208 bracket_pair = Some(pair.clone());
9209 break;
9210 }
9211 }
9212 if let Some(pair) = bracket_pair {
9213 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9214 let autoclose_enabled =
9215 self.use_autoclose && snapshot_settings.use_autoclose;
9216 if autoclose_enabled {
9217 let start = snapshot.anchor_after(selection_head);
9218 let end = snapshot.anchor_after(selection_head);
9219 self.autoclose_regions.push(AutocloseRegion {
9220 selection_id: selection.id,
9221 range: start..end,
9222 pair,
9223 });
9224 }
9225 }
9226 }
9227 }
9228 }
9229 Ok(())
9230 }
9231
9232 pub fn move_to_next_snippet_tabstop(
9233 &mut self,
9234 window: &mut Window,
9235 cx: &mut Context<Self>,
9236 ) -> bool {
9237 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9238 }
9239
9240 pub fn move_to_prev_snippet_tabstop(
9241 &mut self,
9242 window: &mut Window,
9243 cx: &mut Context<Self>,
9244 ) -> bool {
9245 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9246 }
9247
9248 pub fn move_to_snippet_tabstop(
9249 &mut self,
9250 bias: Bias,
9251 window: &mut Window,
9252 cx: &mut Context<Self>,
9253 ) -> bool {
9254 if let Some(mut snippet) = self.snippet_stack.pop() {
9255 match bias {
9256 Bias::Left => {
9257 if snippet.active_index > 0 {
9258 snippet.active_index -= 1;
9259 } else {
9260 self.snippet_stack.push(snippet);
9261 return false;
9262 }
9263 }
9264 Bias::Right => {
9265 if snippet.active_index + 1 < snippet.ranges.len() {
9266 snippet.active_index += 1;
9267 } else {
9268 self.snippet_stack.push(snippet);
9269 return false;
9270 }
9271 }
9272 }
9273 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9274 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9275 // Reverse order so that the first range is the newest created selection.
9276 // Completions will use it and autoscroll will prioritize it.
9277 s.select_ranges(current_ranges.iter().rev().cloned())
9278 });
9279
9280 if let Some(choices) = &snippet.choices[snippet.active_index] {
9281 if let Some(selection) = current_ranges.first() {
9282 self.show_snippet_choices(&choices, selection.clone(), cx);
9283 }
9284 }
9285
9286 // If snippet state is not at the last tabstop, push it back on the stack
9287 if snippet.active_index + 1 < snippet.ranges.len() {
9288 self.snippet_stack.push(snippet);
9289 }
9290 return true;
9291 }
9292 }
9293
9294 false
9295 }
9296
9297 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9298 self.transact(window, cx, |this, window, cx| {
9299 this.select_all(&SelectAll, window, cx);
9300 this.insert("", window, cx);
9301 });
9302 }
9303
9304 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9305 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9306 self.transact(window, cx, |this, window, cx| {
9307 this.select_autoclose_pair(window, cx);
9308 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9309 if !this.linked_edit_ranges.is_empty() {
9310 let selections = this.selections.all::<MultiBufferPoint>(cx);
9311 let snapshot = this.buffer.read(cx).snapshot(cx);
9312
9313 for selection in selections.iter() {
9314 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9315 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9316 if selection_start.buffer_id != selection_end.buffer_id {
9317 continue;
9318 }
9319 if let Some(ranges) =
9320 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9321 {
9322 for (buffer, entries) in ranges {
9323 linked_ranges.entry(buffer).or_default().extend(entries);
9324 }
9325 }
9326 }
9327 }
9328
9329 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9330 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9331 for selection in &mut selections {
9332 if selection.is_empty() {
9333 let old_head = selection.head();
9334 let mut new_head =
9335 movement::left(&display_map, old_head.to_display_point(&display_map))
9336 .to_point(&display_map);
9337 if let Some((buffer, line_buffer_range)) = display_map
9338 .buffer_snapshot
9339 .buffer_line_for_row(MultiBufferRow(old_head.row))
9340 {
9341 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9342 let indent_len = match indent_size.kind {
9343 IndentKind::Space => {
9344 buffer.settings_at(line_buffer_range.start, cx).tab_size
9345 }
9346 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9347 };
9348 if old_head.column <= indent_size.len && old_head.column > 0 {
9349 let indent_len = indent_len.get();
9350 new_head = cmp::min(
9351 new_head,
9352 MultiBufferPoint::new(
9353 old_head.row,
9354 ((old_head.column - 1) / indent_len) * indent_len,
9355 ),
9356 );
9357 }
9358 }
9359
9360 selection.set_head(new_head, SelectionGoal::None);
9361 }
9362 }
9363
9364 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9365 s.select(selections)
9366 });
9367 this.insert("", window, cx);
9368 let empty_str: Arc<str> = Arc::from("");
9369 for (buffer, edits) in linked_ranges {
9370 let snapshot = buffer.read(cx).snapshot();
9371 use text::ToPoint as TP;
9372
9373 let edits = edits
9374 .into_iter()
9375 .map(|range| {
9376 let end_point = TP::to_point(&range.end, &snapshot);
9377 let mut start_point = TP::to_point(&range.start, &snapshot);
9378
9379 if end_point == start_point {
9380 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9381 .saturating_sub(1);
9382 start_point =
9383 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9384 };
9385
9386 (start_point..end_point, empty_str.clone())
9387 })
9388 .sorted_by_key(|(range, _)| range.start)
9389 .collect::<Vec<_>>();
9390 buffer.update(cx, |this, cx| {
9391 this.edit(edits, None, cx);
9392 })
9393 }
9394 this.refresh_inline_completion(true, false, window, cx);
9395 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9396 });
9397 }
9398
9399 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9400 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9401 self.transact(window, cx, |this, window, cx| {
9402 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9403 s.move_with(|map, selection| {
9404 if selection.is_empty() {
9405 let cursor = movement::right(map, selection.head());
9406 selection.end = cursor;
9407 selection.reversed = true;
9408 selection.goal = SelectionGoal::None;
9409 }
9410 })
9411 });
9412 this.insert("", window, cx);
9413 this.refresh_inline_completion(true, false, window, cx);
9414 });
9415 }
9416
9417 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9418 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9419 if self.move_to_prev_snippet_tabstop(window, cx) {
9420 return;
9421 }
9422 self.outdent(&Outdent, window, cx);
9423 }
9424
9425 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9426 if self.move_to_next_snippet_tabstop(window, cx) {
9427 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9428 return;
9429 }
9430 if self.read_only(cx) {
9431 return;
9432 }
9433 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9434 let mut selections = self.selections.all_adjusted(cx);
9435 let buffer = self.buffer.read(cx);
9436 let snapshot = buffer.snapshot(cx);
9437 let rows_iter = selections.iter().map(|s| s.head().row);
9438 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9439
9440 let has_some_cursor_in_whitespace = selections
9441 .iter()
9442 .filter(|selection| selection.is_empty())
9443 .any(|selection| {
9444 let cursor = selection.head();
9445 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9446 cursor.column < current_indent.len
9447 });
9448
9449 let mut edits = Vec::new();
9450 let mut prev_edited_row = 0;
9451 let mut row_delta = 0;
9452 for selection in &mut selections {
9453 if selection.start.row != prev_edited_row {
9454 row_delta = 0;
9455 }
9456 prev_edited_row = selection.end.row;
9457
9458 // If the selection is non-empty, then increase the indentation of the selected lines.
9459 if !selection.is_empty() {
9460 row_delta =
9461 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9462 continue;
9463 }
9464
9465 let cursor = selection.head();
9466 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9467 if let Some(suggested_indent) =
9468 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9469 {
9470 // Don't do anything if already at suggested indent
9471 // and there is any other cursor which is not
9472 if has_some_cursor_in_whitespace
9473 && cursor.column == current_indent.len
9474 && current_indent.len == suggested_indent.len
9475 {
9476 continue;
9477 }
9478
9479 // Adjust line and move cursor to suggested indent
9480 // if cursor is not at suggested indent
9481 if cursor.column < suggested_indent.len
9482 && cursor.column <= current_indent.len
9483 && current_indent.len <= suggested_indent.len
9484 {
9485 selection.start = Point::new(cursor.row, suggested_indent.len);
9486 selection.end = selection.start;
9487 if row_delta == 0 {
9488 edits.extend(Buffer::edit_for_indent_size_adjustment(
9489 cursor.row,
9490 current_indent,
9491 suggested_indent,
9492 ));
9493 row_delta = suggested_indent.len - current_indent.len;
9494 }
9495 continue;
9496 }
9497
9498 // If current indent is more than suggested indent
9499 // only move cursor to current indent and skip indent
9500 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9501 selection.start = Point::new(cursor.row, current_indent.len);
9502 selection.end = selection.start;
9503 continue;
9504 }
9505 }
9506
9507 // Otherwise, insert a hard or soft tab.
9508 let settings = buffer.language_settings_at(cursor, cx);
9509 let tab_size = if settings.hard_tabs {
9510 IndentSize::tab()
9511 } else {
9512 let tab_size = settings.tab_size.get();
9513 let indent_remainder = snapshot
9514 .text_for_range(Point::new(cursor.row, 0)..cursor)
9515 .flat_map(str::chars)
9516 .fold(row_delta % tab_size, |counter: u32, c| {
9517 if c == '\t' {
9518 0
9519 } else {
9520 (counter + 1) % tab_size
9521 }
9522 });
9523
9524 let chars_to_next_tab_stop = tab_size - indent_remainder;
9525 IndentSize::spaces(chars_to_next_tab_stop)
9526 };
9527 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9528 selection.end = selection.start;
9529 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9530 row_delta += tab_size.len;
9531 }
9532
9533 self.transact(window, cx, |this, window, cx| {
9534 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9535 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9536 s.select(selections)
9537 });
9538 this.refresh_inline_completion(true, false, window, cx);
9539 });
9540 }
9541
9542 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9543 if self.read_only(cx) {
9544 return;
9545 }
9546 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9547 let mut selections = self.selections.all::<Point>(cx);
9548 let mut prev_edited_row = 0;
9549 let mut row_delta = 0;
9550 let mut edits = Vec::new();
9551 let buffer = self.buffer.read(cx);
9552 let snapshot = buffer.snapshot(cx);
9553 for selection in &mut selections {
9554 if selection.start.row != prev_edited_row {
9555 row_delta = 0;
9556 }
9557 prev_edited_row = selection.end.row;
9558
9559 row_delta =
9560 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9561 }
9562
9563 self.transact(window, cx, |this, window, cx| {
9564 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9565 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9566 s.select(selections)
9567 });
9568 });
9569 }
9570
9571 fn indent_selection(
9572 buffer: &MultiBuffer,
9573 snapshot: &MultiBufferSnapshot,
9574 selection: &mut Selection<Point>,
9575 edits: &mut Vec<(Range<Point>, String)>,
9576 delta_for_start_row: u32,
9577 cx: &App,
9578 ) -> u32 {
9579 let settings = buffer.language_settings_at(selection.start, cx);
9580 let tab_size = settings.tab_size.get();
9581 let indent_kind = if settings.hard_tabs {
9582 IndentKind::Tab
9583 } else {
9584 IndentKind::Space
9585 };
9586 let mut start_row = selection.start.row;
9587 let mut end_row = selection.end.row + 1;
9588
9589 // If a selection ends at the beginning of a line, don't indent
9590 // that last line.
9591 if selection.end.column == 0 && selection.end.row > selection.start.row {
9592 end_row -= 1;
9593 }
9594
9595 // Avoid re-indenting a row that has already been indented by a
9596 // previous selection, but still update this selection's column
9597 // to reflect that indentation.
9598 if delta_for_start_row > 0 {
9599 start_row += 1;
9600 selection.start.column += delta_for_start_row;
9601 if selection.end.row == selection.start.row {
9602 selection.end.column += delta_for_start_row;
9603 }
9604 }
9605
9606 let mut delta_for_end_row = 0;
9607 let has_multiple_rows = start_row + 1 != end_row;
9608 for row in start_row..end_row {
9609 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9610 let indent_delta = match (current_indent.kind, indent_kind) {
9611 (IndentKind::Space, IndentKind::Space) => {
9612 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9613 IndentSize::spaces(columns_to_next_tab_stop)
9614 }
9615 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9616 (_, IndentKind::Tab) => IndentSize::tab(),
9617 };
9618
9619 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9620 0
9621 } else {
9622 selection.start.column
9623 };
9624 let row_start = Point::new(row, start);
9625 edits.push((
9626 row_start..row_start,
9627 indent_delta.chars().collect::<String>(),
9628 ));
9629
9630 // Update this selection's endpoints to reflect the indentation.
9631 if row == selection.start.row {
9632 selection.start.column += indent_delta.len;
9633 }
9634 if row == selection.end.row {
9635 selection.end.column += indent_delta.len;
9636 delta_for_end_row = indent_delta.len;
9637 }
9638 }
9639
9640 if selection.start.row == selection.end.row {
9641 delta_for_start_row + delta_for_end_row
9642 } else {
9643 delta_for_end_row
9644 }
9645 }
9646
9647 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9648 if self.read_only(cx) {
9649 return;
9650 }
9651 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9653 let selections = self.selections.all::<Point>(cx);
9654 let mut deletion_ranges = Vec::new();
9655 let mut last_outdent = None;
9656 {
9657 let buffer = self.buffer.read(cx);
9658 let snapshot = buffer.snapshot(cx);
9659 for selection in &selections {
9660 let settings = buffer.language_settings_at(selection.start, cx);
9661 let tab_size = settings.tab_size.get();
9662 let mut rows = selection.spanned_rows(false, &display_map);
9663
9664 // Avoid re-outdenting a row that has already been outdented by a
9665 // previous selection.
9666 if let Some(last_row) = last_outdent {
9667 if last_row == rows.start {
9668 rows.start = rows.start.next_row();
9669 }
9670 }
9671 let has_multiple_rows = rows.len() > 1;
9672 for row in rows.iter_rows() {
9673 let indent_size = snapshot.indent_size_for_line(row);
9674 if indent_size.len > 0 {
9675 let deletion_len = match indent_size.kind {
9676 IndentKind::Space => {
9677 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9678 if columns_to_prev_tab_stop == 0 {
9679 tab_size
9680 } else {
9681 columns_to_prev_tab_stop
9682 }
9683 }
9684 IndentKind::Tab => 1,
9685 };
9686 let start = if has_multiple_rows
9687 || deletion_len > selection.start.column
9688 || indent_size.len < selection.start.column
9689 {
9690 0
9691 } else {
9692 selection.start.column - deletion_len
9693 };
9694 deletion_ranges.push(
9695 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9696 );
9697 last_outdent = Some(row);
9698 }
9699 }
9700 }
9701 }
9702
9703 self.transact(window, cx, |this, window, cx| {
9704 this.buffer.update(cx, |buffer, cx| {
9705 let empty_str: Arc<str> = Arc::default();
9706 buffer.edit(
9707 deletion_ranges
9708 .into_iter()
9709 .map(|range| (range, empty_str.clone())),
9710 None,
9711 cx,
9712 );
9713 });
9714 let selections = this.selections.all::<usize>(cx);
9715 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9716 s.select(selections)
9717 });
9718 });
9719 }
9720
9721 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9722 if self.read_only(cx) {
9723 return;
9724 }
9725 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9726 let selections = self
9727 .selections
9728 .all::<usize>(cx)
9729 .into_iter()
9730 .map(|s| s.range());
9731
9732 self.transact(window, cx, |this, window, cx| {
9733 this.buffer.update(cx, |buffer, cx| {
9734 buffer.autoindent_ranges(selections, cx);
9735 });
9736 let selections = this.selections.all::<usize>(cx);
9737 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9738 s.select(selections)
9739 });
9740 });
9741 }
9742
9743 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9744 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9746 let selections = self.selections.all::<Point>(cx);
9747
9748 let mut new_cursors = Vec::new();
9749 let mut edit_ranges = Vec::new();
9750 let mut selections = selections.iter().peekable();
9751 while let Some(selection) = selections.next() {
9752 let mut rows = selection.spanned_rows(false, &display_map);
9753 let goal_display_column = selection.head().to_display_point(&display_map).column();
9754
9755 // Accumulate contiguous regions of rows that we want to delete.
9756 while let Some(next_selection) = selections.peek() {
9757 let next_rows = next_selection.spanned_rows(false, &display_map);
9758 if next_rows.start <= rows.end {
9759 rows.end = next_rows.end;
9760 selections.next().unwrap();
9761 } else {
9762 break;
9763 }
9764 }
9765
9766 let buffer = &display_map.buffer_snapshot;
9767 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9768 let edit_end;
9769 let cursor_buffer_row;
9770 if buffer.max_point().row >= rows.end.0 {
9771 // If there's a line after the range, delete the \n from the end of the row range
9772 // and position the cursor on the next line.
9773 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9774 cursor_buffer_row = rows.end;
9775 } else {
9776 // If there isn't a line after the range, delete the \n from the line before the
9777 // start of the row range and position the cursor there.
9778 edit_start = edit_start.saturating_sub(1);
9779 edit_end = buffer.len();
9780 cursor_buffer_row = rows.start.previous_row();
9781 }
9782
9783 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9784 *cursor.column_mut() =
9785 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9786
9787 new_cursors.push((
9788 selection.id,
9789 buffer.anchor_after(cursor.to_point(&display_map)),
9790 ));
9791 edit_ranges.push(edit_start..edit_end);
9792 }
9793
9794 self.transact(window, cx, |this, window, cx| {
9795 let buffer = this.buffer.update(cx, |buffer, cx| {
9796 let empty_str: Arc<str> = Arc::default();
9797 buffer.edit(
9798 edit_ranges
9799 .into_iter()
9800 .map(|range| (range, empty_str.clone())),
9801 None,
9802 cx,
9803 );
9804 buffer.snapshot(cx)
9805 });
9806 let new_selections = new_cursors
9807 .into_iter()
9808 .map(|(id, cursor)| {
9809 let cursor = cursor.to_point(&buffer);
9810 Selection {
9811 id,
9812 start: cursor,
9813 end: cursor,
9814 reversed: false,
9815 goal: SelectionGoal::None,
9816 }
9817 })
9818 .collect();
9819
9820 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9821 s.select(new_selections);
9822 });
9823 });
9824 }
9825
9826 pub fn join_lines_impl(
9827 &mut self,
9828 insert_whitespace: bool,
9829 window: &mut Window,
9830 cx: &mut Context<Self>,
9831 ) {
9832 if self.read_only(cx) {
9833 return;
9834 }
9835 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9836 for selection in self.selections.all::<Point>(cx) {
9837 let start = MultiBufferRow(selection.start.row);
9838 // Treat single line selections as if they include the next line. Otherwise this action
9839 // would do nothing for single line selections individual cursors.
9840 let end = if selection.start.row == selection.end.row {
9841 MultiBufferRow(selection.start.row + 1)
9842 } else {
9843 MultiBufferRow(selection.end.row)
9844 };
9845
9846 if let Some(last_row_range) = row_ranges.last_mut() {
9847 if start <= last_row_range.end {
9848 last_row_range.end = end;
9849 continue;
9850 }
9851 }
9852 row_ranges.push(start..end);
9853 }
9854
9855 let snapshot = self.buffer.read(cx).snapshot(cx);
9856 let mut cursor_positions = Vec::new();
9857 for row_range in &row_ranges {
9858 let anchor = snapshot.anchor_before(Point::new(
9859 row_range.end.previous_row().0,
9860 snapshot.line_len(row_range.end.previous_row()),
9861 ));
9862 cursor_positions.push(anchor..anchor);
9863 }
9864
9865 self.transact(window, cx, |this, window, cx| {
9866 for row_range in row_ranges.into_iter().rev() {
9867 for row in row_range.iter_rows().rev() {
9868 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9869 let next_line_row = row.next_row();
9870 let indent = snapshot.indent_size_for_line(next_line_row);
9871 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9872
9873 let replace =
9874 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9875 " "
9876 } else {
9877 ""
9878 };
9879
9880 this.buffer.update(cx, |buffer, cx| {
9881 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9882 });
9883 }
9884 }
9885
9886 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9887 s.select_anchor_ranges(cursor_positions)
9888 });
9889 });
9890 }
9891
9892 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9893 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9894 self.join_lines_impl(true, window, cx);
9895 }
9896
9897 pub fn sort_lines_case_sensitive(
9898 &mut self,
9899 _: &SortLinesCaseSensitive,
9900 window: &mut Window,
9901 cx: &mut Context<Self>,
9902 ) {
9903 self.manipulate_lines(window, cx, |lines| lines.sort())
9904 }
9905
9906 pub fn sort_lines_case_insensitive(
9907 &mut self,
9908 _: &SortLinesCaseInsensitive,
9909 window: &mut Window,
9910 cx: &mut Context<Self>,
9911 ) {
9912 self.manipulate_lines(window, cx, |lines| {
9913 lines.sort_by_key(|line| line.to_lowercase())
9914 })
9915 }
9916
9917 pub fn unique_lines_case_insensitive(
9918 &mut self,
9919 _: &UniqueLinesCaseInsensitive,
9920 window: &mut Window,
9921 cx: &mut Context<Self>,
9922 ) {
9923 self.manipulate_lines(window, cx, |lines| {
9924 let mut seen = HashSet::default();
9925 lines.retain(|line| seen.insert(line.to_lowercase()));
9926 })
9927 }
9928
9929 pub fn unique_lines_case_sensitive(
9930 &mut self,
9931 _: &UniqueLinesCaseSensitive,
9932 window: &mut Window,
9933 cx: &mut Context<Self>,
9934 ) {
9935 self.manipulate_lines(window, cx, |lines| {
9936 let mut seen = HashSet::default();
9937 lines.retain(|line| seen.insert(*line));
9938 })
9939 }
9940
9941 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9942 let Some(project) = self.project.clone() else {
9943 return;
9944 };
9945 self.reload(project, window, cx)
9946 .detach_and_notify_err(window, cx);
9947 }
9948
9949 pub fn restore_file(
9950 &mut self,
9951 _: &::git::RestoreFile,
9952 window: &mut Window,
9953 cx: &mut Context<Self>,
9954 ) {
9955 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9956 let mut buffer_ids = HashSet::default();
9957 let snapshot = self.buffer().read(cx).snapshot(cx);
9958 for selection in self.selections.all::<usize>(cx) {
9959 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9960 }
9961
9962 let buffer = self.buffer().read(cx);
9963 let ranges = buffer_ids
9964 .into_iter()
9965 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9966 .collect::<Vec<_>>();
9967
9968 self.restore_hunks_in_ranges(ranges, window, cx);
9969 }
9970
9971 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9972 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9973 let selections = self
9974 .selections
9975 .all(cx)
9976 .into_iter()
9977 .map(|s| s.range())
9978 .collect();
9979 self.restore_hunks_in_ranges(selections, window, cx);
9980 }
9981
9982 pub fn restore_hunks_in_ranges(
9983 &mut self,
9984 ranges: Vec<Range<Point>>,
9985 window: &mut Window,
9986 cx: &mut Context<Editor>,
9987 ) {
9988 let mut revert_changes = HashMap::default();
9989 let chunk_by = self
9990 .snapshot(window, cx)
9991 .hunks_for_ranges(ranges)
9992 .into_iter()
9993 .chunk_by(|hunk| hunk.buffer_id);
9994 for (buffer_id, hunks) in &chunk_by {
9995 let hunks = hunks.collect::<Vec<_>>();
9996 for hunk in &hunks {
9997 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9998 }
9999 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10000 }
10001 drop(chunk_by);
10002 if !revert_changes.is_empty() {
10003 self.transact(window, cx, |editor, window, cx| {
10004 editor.restore(revert_changes, window, cx);
10005 });
10006 }
10007 }
10008
10009 pub fn open_active_item_in_terminal(
10010 &mut self,
10011 _: &OpenInTerminal,
10012 window: &mut Window,
10013 cx: &mut Context<Self>,
10014 ) {
10015 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10016 let project_path = buffer.read(cx).project_path(cx)?;
10017 let project = self.project.as_ref()?.read(cx);
10018 let entry = project.entry_for_path(&project_path, cx)?;
10019 let parent = match &entry.canonical_path {
10020 Some(canonical_path) => canonical_path.to_path_buf(),
10021 None => project.absolute_path(&project_path, cx)?,
10022 }
10023 .parent()?
10024 .to_path_buf();
10025 Some(parent)
10026 }) {
10027 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10028 }
10029 }
10030
10031 fn set_breakpoint_context_menu(
10032 &mut self,
10033 display_row: DisplayRow,
10034 position: Option<Anchor>,
10035 clicked_point: gpui::Point<Pixels>,
10036 window: &mut Window,
10037 cx: &mut Context<Self>,
10038 ) {
10039 if !cx.has_flag::<DebuggerFeatureFlag>() {
10040 return;
10041 }
10042 let source = self
10043 .buffer
10044 .read(cx)
10045 .snapshot(cx)
10046 .anchor_before(Point::new(display_row.0, 0u32));
10047
10048 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10049
10050 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10051 self,
10052 source,
10053 clicked_point,
10054 context_menu,
10055 window,
10056 cx,
10057 );
10058 }
10059
10060 fn add_edit_breakpoint_block(
10061 &mut self,
10062 anchor: Anchor,
10063 breakpoint: &Breakpoint,
10064 edit_action: BreakpointPromptEditAction,
10065 window: &mut Window,
10066 cx: &mut Context<Self>,
10067 ) {
10068 let weak_editor = cx.weak_entity();
10069 let bp_prompt = cx.new(|cx| {
10070 BreakpointPromptEditor::new(
10071 weak_editor,
10072 anchor,
10073 breakpoint.clone(),
10074 edit_action,
10075 window,
10076 cx,
10077 )
10078 });
10079
10080 let height = bp_prompt.update(cx, |this, cx| {
10081 this.prompt
10082 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10083 });
10084 let cloned_prompt = bp_prompt.clone();
10085 let blocks = vec![BlockProperties {
10086 style: BlockStyle::Sticky,
10087 placement: BlockPlacement::Above(anchor),
10088 height: Some(height),
10089 render: Arc::new(move |cx| {
10090 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10091 cloned_prompt.clone().into_any_element()
10092 }),
10093 priority: 0,
10094 render_in_minimap: true,
10095 }];
10096
10097 let focus_handle = bp_prompt.focus_handle(cx);
10098 window.focus(&focus_handle);
10099
10100 let block_ids = self.insert_blocks(blocks, None, cx);
10101 bp_prompt.update(cx, |prompt, _| {
10102 prompt.add_block_ids(block_ids);
10103 });
10104 }
10105
10106 pub(crate) fn breakpoint_at_row(
10107 &self,
10108 row: u32,
10109 window: &mut Window,
10110 cx: &mut Context<Self>,
10111 ) -> Option<(Anchor, Breakpoint)> {
10112 let snapshot = self.snapshot(window, cx);
10113 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10114
10115 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10116 }
10117
10118 pub(crate) fn breakpoint_at_anchor(
10119 &self,
10120 breakpoint_position: Anchor,
10121 snapshot: &EditorSnapshot,
10122 cx: &mut Context<Self>,
10123 ) -> Option<(Anchor, Breakpoint)> {
10124 let project = self.project.clone()?;
10125
10126 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10127 snapshot
10128 .buffer_snapshot
10129 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10130 })?;
10131
10132 let enclosing_excerpt = breakpoint_position.excerpt_id;
10133 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10134 let buffer_snapshot = buffer.read(cx).snapshot();
10135
10136 let row = buffer_snapshot
10137 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10138 .row;
10139
10140 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10141 let anchor_end = snapshot
10142 .buffer_snapshot
10143 .anchor_after(Point::new(row, line_len));
10144
10145 let bp = self
10146 .breakpoint_store
10147 .as_ref()?
10148 .read_with(cx, |breakpoint_store, cx| {
10149 breakpoint_store
10150 .breakpoints(
10151 &buffer,
10152 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10153 &buffer_snapshot,
10154 cx,
10155 )
10156 .next()
10157 .and_then(|(bp, _)| {
10158 let breakpoint_row = buffer_snapshot
10159 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10160 .row;
10161
10162 if breakpoint_row == row {
10163 snapshot
10164 .buffer_snapshot
10165 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10166 .map(|position| (position, bp.bp.clone()))
10167 } else {
10168 None
10169 }
10170 })
10171 });
10172 bp
10173 }
10174
10175 pub fn edit_log_breakpoint(
10176 &mut self,
10177 _: &EditLogBreakpoint,
10178 window: &mut Window,
10179 cx: &mut Context<Self>,
10180 ) {
10181 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10182 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10183 message: None,
10184 state: BreakpointState::Enabled,
10185 condition: None,
10186 hit_condition: None,
10187 });
10188
10189 self.add_edit_breakpoint_block(
10190 anchor,
10191 &breakpoint,
10192 BreakpointPromptEditAction::Log,
10193 window,
10194 cx,
10195 );
10196 }
10197 }
10198
10199 fn breakpoints_at_cursors(
10200 &self,
10201 window: &mut Window,
10202 cx: &mut Context<Self>,
10203 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10204 let snapshot = self.snapshot(window, cx);
10205 let cursors = self
10206 .selections
10207 .disjoint_anchors()
10208 .into_iter()
10209 .map(|selection| {
10210 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10211
10212 let breakpoint_position = self
10213 .breakpoint_at_row(cursor_position.row, window, cx)
10214 .map(|bp| bp.0)
10215 .unwrap_or_else(|| {
10216 snapshot
10217 .display_snapshot
10218 .buffer_snapshot
10219 .anchor_after(Point::new(cursor_position.row, 0))
10220 });
10221
10222 let breakpoint = self
10223 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10224 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10225
10226 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10227 })
10228 // 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.
10229 .collect::<HashMap<Anchor, _>>();
10230
10231 cursors.into_iter().collect()
10232 }
10233
10234 pub fn enable_breakpoint(
10235 &mut self,
10236 _: &crate::actions::EnableBreakpoint,
10237 window: &mut Window,
10238 cx: &mut Context<Self>,
10239 ) {
10240 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10241 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10242 continue;
10243 };
10244 self.edit_breakpoint_at_anchor(
10245 anchor,
10246 breakpoint,
10247 BreakpointEditAction::InvertState,
10248 cx,
10249 );
10250 }
10251 }
10252
10253 pub fn disable_breakpoint(
10254 &mut self,
10255 _: &crate::actions::DisableBreakpoint,
10256 window: &mut Window,
10257 cx: &mut Context<Self>,
10258 ) {
10259 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10260 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10261 continue;
10262 };
10263 self.edit_breakpoint_at_anchor(
10264 anchor,
10265 breakpoint,
10266 BreakpointEditAction::InvertState,
10267 cx,
10268 );
10269 }
10270 }
10271
10272 pub fn toggle_breakpoint(
10273 &mut self,
10274 _: &crate::actions::ToggleBreakpoint,
10275 window: &mut Window,
10276 cx: &mut Context<Self>,
10277 ) {
10278 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10279 if let Some(breakpoint) = breakpoint {
10280 self.edit_breakpoint_at_anchor(
10281 anchor,
10282 breakpoint,
10283 BreakpointEditAction::Toggle,
10284 cx,
10285 );
10286 } else {
10287 self.edit_breakpoint_at_anchor(
10288 anchor,
10289 Breakpoint::new_standard(),
10290 BreakpointEditAction::Toggle,
10291 cx,
10292 );
10293 }
10294 }
10295 }
10296
10297 pub fn edit_breakpoint_at_anchor(
10298 &mut self,
10299 breakpoint_position: Anchor,
10300 breakpoint: Breakpoint,
10301 edit_action: BreakpointEditAction,
10302 cx: &mut Context<Self>,
10303 ) {
10304 let Some(breakpoint_store) = &self.breakpoint_store else {
10305 return;
10306 };
10307
10308 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10309 if breakpoint_position == Anchor::min() {
10310 self.buffer()
10311 .read(cx)
10312 .excerpt_buffer_ids()
10313 .into_iter()
10314 .next()
10315 } else {
10316 None
10317 }
10318 }) else {
10319 return;
10320 };
10321
10322 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10323 return;
10324 };
10325
10326 breakpoint_store.update(cx, |breakpoint_store, cx| {
10327 breakpoint_store.toggle_breakpoint(
10328 buffer,
10329 BreakpointWithPosition {
10330 position: breakpoint_position.text_anchor,
10331 bp: breakpoint,
10332 },
10333 edit_action,
10334 cx,
10335 );
10336 });
10337
10338 cx.notify();
10339 }
10340
10341 #[cfg(any(test, feature = "test-support"))]
10342 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10343 self.breakpoint_store.clone()
10344 }
10345
10346 pub fn prepare_restore_change(
10347 &self,
10348 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10349 hunk: &MultiBufferDiffHunk,
10350 cx: &mut App,
10351 ) -> Option<()> {
10352 if hunk.is_created_file() {
10353 return None;
10354 }
10355 let buffer = self.buffer.read(cx);
10356 let diff = buffer.diff_for(hunk.buffer_id)?;
10357 let buffer = buffer.buffer(hunk.buffer_id)?;
10358 let buffer = buffer.read(cx);
10359 let original_text = diff
10360 .read(cx)
10361 .base_text()
10362 .as_rope()
10363 .slice(hunk.diff_base_byte_range.clone());
10364 let buffer_snapshot = buffer.snapshot();
10365 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10366 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10367 probe
10368 .0
10369 .start
10370 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10371 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10372 }) {
10373 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10374 Some(())
10375 } else {
10376 None
10377 }
10378 }
10379
10380 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10381 self.manipulate_lines(window, cx, |lines| lines.reverse())
10382 }
10383
10384 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10385 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10386 }
10387
10388 fn manipulate_lines<Fn>(
10389 &mut self,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 mut callback: Fn,
10393 ) where
10394 Fn: FnMut(&mut Vec<&str>),
10395 {
10396 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10397
10398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10399 let buffer = self.buffer.read(cx).snapshot(cx);
10400
10401 let mut edits = Vec::new();
10402
10403 let selections = self.selections.all::<Point>(cx);
10404 let mut selections = selections.iter().peekable();
10405 let mut contiguous_row_selections = Vec::new();
10406 let mut new_selections = Vec::new();
10407 let mut added_lines = 0;
10408 let mut removed_lines = 0;
10409
10410 while let Some(selection) = selections.next() {
10411 let (start_row, end_row) = consume_contiguous_rows(
10412 &mut contiguous_row_selections,
10413 selection,
10414 &display_map,
10415 &mut selections,
10416 );
10417
10418 let start_point = Point::new(start_row.0, 0);
10419 let end_point = Point::new(
10420 end_row.previous_row().0,
10421 buffer.line_len(end_row.previous_row()),
10422 );
10423 let text = buffer
10424 .text_for_range(start_point..end_point)
10425 .collect::<String>();
10426
10427 let mut lines = text.split('\n').collect_vec();
10428
10429 let lines_before = lines.len();
10430 callback(&mut lines);
10431 let lines_after = lines.len();
10432
10433 edits.push((start_point..end_point, lines.join("\n")));
10434
10435 // Selections must change based on added and removed line count
10436 let start_row =
10437 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10438 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10439 new_selections.push(Selection {
10440 id: selection.id,
10441 start: start_row,
10442 end: end_row,
10443 goal: SelectionGoal::None,
10444 reversed: selection.reversed,
10445 });
10446
10447 if lines_after > lines_before {
10448 added_lines += lines_after - lines_before;
10449 } else if lines_before > lines_after {
10450 removed_lines += lines_before - lines_after;
10451 }
10452 }
10453
10454 self.transact(window, cx, |this, window, cx| {
10455 let buffer = this.buffer.update(cx, |buffer, cx| {
10456 buffer.edit(edits, None, cx);
10457 buffer.snapshot(cx)
10458 });
10459
10460 // Recalculate offsets on newly edited buffer
10461 let new_selections = new_selections
10462 .iter()
10463 .map(|s| {
10464 let start_point = Point::new(s.start.0, 0);
10465 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10466 Selection {
10467 id: s.id,
10468 start: buffer.point_to_offset(start_point),
10469 end: buffer.point_to_offset(end_point),
10470 goal: s.goal,
10471 reversed: s.reversed,
10472 }
10473 })
10474 .collect();
10475
10476 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10477 s.select(new_selections);
10478 });
10479
10480 this.request_autoscroll(Autoscroll::fit(), cx);
10481 });
10482 }
10483
10484 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10485 self.manipulate_text(window, cx, |text| {
10486 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10487 if has_upper_case_characters {
10488 text.to_lowercase()
10489 } else {
10490 text.to_uppercase()
10491 }
10492 })
10493 }
10494
10495 pub fn convert_to_upper_case(
10496 &mut self,
10497 _: &ConvertToUpperCase,
10498 window: &mut Window,
10499 cx: &mut Context<Self>,
10500 ) {
10501 self.manipulate_text(window, cx, |text| text.to_uppercase())
10502 }
10503
10504 pub fn convert_to_lower_case(
10505 &mut self,
10506 _: &ConvertToLowerCase,
10507 window: &mut Window,
10508 cx: &mut Context<Self>,
10509 ) {
10510 self.manipulate_text(window, cx, |text| text.to_lowercase())
10511 }
10512
10513 pub fn convert_to_title_case(
10514 &mut self,
10515 _: &ConvertToTitleCase,
10516 window: &mut Window,
10517 cx: &mut Context<Self>,
10518 ) {
10519 self.manipulate_text(window, cx, |text| {
10520 text.split('\n')
10521 .map(|line| line.to_case(Case::Title))
10522 .join("\n")
10523 })
10524 }
10525
10526 pub fn convert_to_snake_case(
10527 &mut self,
10528 _: &ConvertToSnakeCase,
10529 window: &mut Window,
10530 cx: &mut Context<Self>,
10531 ) {
10532 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10533 }
10534
10535 pub fn convert_to_kebab_case(
10536 &mut self,
10537 _: &ConvertToKebabCase,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10542 }
10543
10544 pub fn convert_to_upper_camel_case(
10545 &mut self,
10546 _: &ConvertToUpperCamelCase,
10547 window: &mut Window,
10548 cx: &mut Context<Self>,
10549 ) {
10550 self.manipulate_text(window, cx, |text| {
10551 text.split('\n')
10552 .map(|line| line.to_case(Case::UpperCamel))
10553 .join("\n")
10554 })
10555 }
10556
10557 pub fn convert_to_lower_camel_case(
10558 &mut self,
10559 _: &ConvertToLowerCamelCase,
10560 window: &mut Window,
10561 cx: &mut Context<Self>,
10562 ) {
10563 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10564 }
10565
10566 pub fn convert_to_opposite_case(
10567 &mut self,
10568 _: &ConvertToOppositeCase,
10569 window: &mut Window,
10570 cx: &mut Context<Self>,
10571 ) {
10572 self.manipulate_text(window, cx, |text| {
10573 text.chars()
10574 .fold(String::with_capacity(text.len()), |mut t, c| {
10575 if c.is_uppercase() {
10576 t.extend(c.to_lowercase());
10577 } else {
10578 t.extend(c.to_uppercase());
10579 }
10580 t
10581 })
10582 })
10583 }
10584
10585 pub fn convert_to_rot13(
10586 &mut self,
10587 _: &ConvertToRot13,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.manipulate_text(window, cx, |text| {
10592 text.chars()
10593 .map(|c| match c {
10594 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10595 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10596 _ => c,
10597 })
10598 .collect()
10599 })
10600 }
10601
10602 pub fn convert_to_rot47(
10603 &mut self,
10604 _: &ConvertToRot47,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.manipulate_text(window, cx, |text| {
10609 text.chars()
10610 .map(|c| {
10611 let code_point = c as u32;
10612 if code_point >= 33 && code_point <= 126 {
10613 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10614 }
10615 c
10616 })
10617 .collect()
10618 })
10619 }
10620
10621 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10622 where
10623 Fn: FnMut(&str) -> String,
10624 {
10625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10626 let buffer = self.buffer.read(cx).snapshot(cx);
10627
10628 let mut new_selections = Vec::new();
10629 let mut edits = Vec::new();
10630 let mut selection_adjustment = 0i32;
10631
10632 for selection in self.selections.all::<usize>(cx) {
10633 let selection_is_empty = selection.is_empty();
10634
10635 let (start, end) = if selection_is_empty {
10636 let word_range = movement::surrounding_word(
10637 &display_map,
10638 selection.start.to_display_point(&display_map),
10639 );
10640 let start = word_range.start.to_offset(&display_map, Bias::Left);
10641 let end = word_range.end.to_offset(&display_map, Bias::Left);
10642 (start, end)
10643 } else {
10644 (selection.start, selection.end)
10645 };
10646
10647 let text = buffer.text_for_range(start..end).collect::<String>();
10648 let old_length = text.len() as i32;
10649 let text = callback(&text);
10650
10651 new_selections.push(Selection {
10652 start: (start as i32 - selection_adjustment) as usize,
10653 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10654 goal: SelectionGoal::None,
10655 ..selection
10656 });
10657
10658 selection_adjustment += old_length - text.len() as i32;
10659
10660 edits.push((start..end, text));
10661 }
10662
10663 self.transact(window, cx, |this, window, cx| {
10664 this.buffer.update(cx, |buffer, cx| {
10665 buffer.edit(edits, None, cx);
10666 });
10667
10668 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10669 s.select(new_selections);
10670 });
10671
10672 this.request_autoscroll(Autoscroll::fit(), cx);
10673 });
10674 }
10675
10676 pub fn move_selection_on_drop(
10677 &mut self,
10678 selection: &Selection<Anchor>,
10679 target: DisplayPoint,
10680 is_cut: bool,
10681 window: &mut Window,
10682 cx: &mut Context<Self>,
10683 ) {
10684 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10685 let buffer = &display_map.buffer_snapshot;
10686 let mut edits = Vec::new();
10687 let insert_point = display_map
10688 .clip_point(target, Bias::Left)
10689 .to_point(&display_map);
10690 let text = buffer
10691 .text_for_range(selection.start..selection.end)
10692 .collect::<String>();
10693 if is_cut {
10694 edits.push(((selection.start..selection.end), String::new()));
10695 }
10696 let insert_anchor = buffer.anchor_before(insert_point);
10697 edits.push(((insert_anchor..insert_anchor), text));
10698 let last_edit_start = insert_anchor.bias_left(buffer);
10699 let last_edit_end = insert_anchor.bias_right(buffer);
10700 self.transact(window, cx, |this, window, cx| {
10701 this.buffer.update(cx, |buffer, cx| {
10702 buffer.edit(edits, None, cx);
10703 });
10704 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10705 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10706 });
10707 });
10708 }
10709
10710 pub fn clear_selection_drag_state(&mut self) {
10711 self.selection_drag_state = SelectionDragState::None;
10712 }
10713
10714 pub fn duplicate(
10715 &mut self,
10716 upwards: bool,
10717 whole_lines: bool,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) {
10721 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10722
10723 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10724 let buffer = &display_map.buffer_snapshot;
10725 let selections = self.selections.all::<Point>(cx);
10726
10727 let mut edits = Vec::new();
10728 let mut selections_iter = selections.iter().peekable();
10729 while let Some(selection) = selections_iter.next() {
10730 let mut rows = selection.spanned_rows(false, &display_map);
10731 // duplicate line-wise
10732 if whole_lines || selection.start == selection.end {
10733 // Avoid duplicating the same lines twice.
10734 while let Some(next_selection) = selections_iter.peek() {
10735 let next_rows = next_selection.spanned_rows(false, &display_map);
10736 if next_rows.start < rows.end {
10737 rows.end = next_rows.end;
10738 selections_iter.next().unwrap();
10739 } else {
10740 break;
10741 }
10742 }
10743
10744 // Copy the text from the selected row region and splice it either at the start
10745 // or end of the region.
10746 let start = Point::new(rows.start.0, 0);
10747 let end = Point::new(
10748 rows.end.previous_row().0,
10749 buffer.line_len(rows.end.previous_row()),
10750 );
10751 let text = buffer
10752 .text_for_range(start..end)
10753 .chain(Some("\n"))
10754 .collect::<String>();
10755 let insert_location = if upwards {
10756 Point::new(rows.end.0, 0)
10757 } else {
10758 start
10759 };
10760 edits.push((insert_location..insert_location, text));
10761 } else {
10762 // duplicate character-wise
10763 let start = selection.start;
10764 let end = selection.end;
10765 let text = buffer.text_for_range(start..end).collect::<String>();
10766 edits.push((selection.end..selection.end, text));
10767 }
10768 }
10769
10770 self.transact(window, cx, |this, _, cx| {
10771 this.buffer.update(cx, |buffer, cx| {
10772 buffer.edit(edits, None, cx);
10773 });
10774
10775 this.request_autoscroll(Autoscroll::fit(), cx);
10776 });
10777 }
10778
10779 pub fn duplicate_line_up(
10780 &mut self,
10781 _: &DuplicateLineUp,
10782 window: &mut Window,
10783 cx: &mut Context<Self>,
10784 ) {
10785 self.duplicate(true, true, window, cx);
10786 }
10787
10788 pub fn duplicate_line_down(
10789 &mut self,
10790 _: &DuplicateLineDown,
10791 window: &mut Window,
10792 cx: &mut Context<Self>,
10793 ) {
10794 self.duplicate(false, true, window, cx);
10795 }
10796
10797 pub fn duplicate_selection(
10798 &mut self,
10799 _: &DuplicateSelection,
10800 window: &mut Window,
10801 cx: &mut Context<Self>,
10802 ) {
10803 self.duplicate(false, false, window, cx);
10804 }
10805
10806 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10807 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10808
10809 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10810 let buffer = self.buffer.read(cx).snapshot(cx);
10811
10812 let mut edits = Vec::new();
10813 let mut unfold_ranges = Vec::new();
10814 let mut refold_creases = Vec::new();
10815
10816 let selections = self.selections.all::<Point>(cx);
10817 let mut selections = selections.iter().peekable();
10818 let mut contiguous_row_selections = Vec::new();
10819 let mut new_selections = Vec::new();
10820
10821 while let Some(selection) = selections.next() {
10822 // Find all the selections that span a contiguous row range
10823 let (start_row, end_row) = consume_contiguous_rows(
10824 &mut contiguous_row_selections,
10825 selection,
10826 &display_map,
10827 &mut selections,
10828 );
10829
10830 // Move the text spanned by the row range to be before the line preceding the row range
10831 if start_row.0 > 0 {
10832 let range_to_move = Point::new(
10833 start_row.previous_row().0,
10834 buffer.line_len(start_row.previous_row()),
10835 )
10836 ..Point::new(
10837 end_row.previous_row().0,
10838 buffer.line_len(end_row.previous_row()),
10839 );
10840 let insertion_point = display_map
10841 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10842 .0;
10843
10844 // Don't move lines across excerpts
10845 if buffer
10846 .excerpt_containing(insertion_point..range_to_move.end)
10847 .is_some()
10848 {
10849 let text = buffer
10850 .text_for_range(range_to_move.clone())
10851 .flat_map(|s| s.chars())
10852 .skip(1)
10853 .chain(['\n'])
10854 .collect::<String>();
10855
10856 edits.push((
10857 buffer.anchor_after(range_to_move.start)
10858 ..buffer.anchor_before(range_to_move.end),
10859 String::new(),
10860 ));
10861 let insertion_anchor = buffer.anchor_after(insertion_point);
10862 edits.push((insertion_anchor..insertion_anchor, text));
10863
10864 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10865
10866 // Move selections up
10867 new_selections.extend(contiguous_row_selections.drain(..).map(
10868 |mut selection| {
10869 selection.start.row -= row_delta;
10870 selection.end.row -= row_delta;
10871 selection
10872 },
10873 ));
10874
10875 // Move folds up
10876 unfold_ranges.push(range_to_move.clone());
10877 for fold in display_map.folds_in_range(
10878 buffer.anchor_before(range_to_move.start)
10879 ..buffer.anchor_after(range_to_move.end),
10880 ) {
10881 let mut start = fold.range.start.to_point(&buffer);
10882 let mut end = fold.range.end.to_point(&buffer);
10883 start.row -= row_delta;
10884 end.row -= row_delta;
10885 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10886 }
10887 }
10888 }
10889
10890 // If we didn't move line(s), preserve the existing selections
10891 new_selections.append(&mut contiguous_row_selections);
10892 }
10893
10894 self.transact(window, cx, |this, window, cx| {
10895 this.unfold_ranges(&unfold_ranges, true, true, cx);
10896 this.buffer.update(cx, |buffer, cx| {
10897 for (range, text) in edits {
10898 buffer.edit([(range, text)], None, cx);
10899 }
10900 });
10901 this.fold_creases(refold_creases, true, window, cx);
10902 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10903 s.select(new_selections);
10904 })
10905 });
10906 }
10907
10908 pub fn move_line_down(
10909 &mut self,
10910 _: &MoveLineDown,
10911 window: &mut Window,
10912 cx: &mut Context<Self>,
10913 ) {
10914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10915
10916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10917 let buffer = self.buffer.read(cx).snapshot(cx);
10918
10919 let mut edits = Vec::new();
10920 let mut unfold_ranges = Vec::new();
10921 let mut refold_creases = Vec::new();
10922
10923 let selections = self.selections.all::<Point>(cx);
10924 let mut selections = selections.iter().peekable();
10925 let mut contiguous_row_selections = Vec::new();
10926 let mut new_selections = Vec::new();
10927
10928 while let Some(selection) = selections.next() {
10929 // Find all the selections that span a contiguous row range
10930 let (start_row, end_row) = consume_contiguous_rows(
10931 &mut contiguous_row_selections,
10932 selection,
10933 &display_map,
10934 &mut selections,
10935 );
10936
10937 // Move the text spanned by the row range to be after the last line of the row range
10938 if end_row.0 <= buffer.max_point().row {
10939 let range_to_move =
10940 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10941 let insertion_point = display_map
10942 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10943 .0;
10944
10945 // Don't move lines across excerpt boundaries
10946 if buffer
10947 .excerpt_containing(range_to_move.start..insertion_point)
10948 .is_some()
10949 {
10950 let mut text = String::from("\n");
10951 text.extend(buffer.text_for_range(range_to_move.clone()));
10952 text.pop(); // Drop trailing newline
10953 edits.push((
10954 buffer.anchor_after(range_to_move.start)
10955 ..buffer.anchor_before(range_to_move.end),
10956 String::new(),
10957 ));
10958 let insertion_anchor = buffer.anchor_after(insertion_point);
10959 edits.push((insertion_anchor..insertion_anchor, text));
10960
10961 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10962
10963 // Move selections down
10964 new_selections.extend(contiguous_row_selections.drain(..).map(
10965 |mut selection| {
10966 selection.start.row += row_delta;
10967 selection.end.row += row_delta;
10968 selection
10969 },
10970 ));
10971
10972 // Move folds down
10973 unfold_ranges.push(range_to_move.clone());
10974 for fold in display_map.folds_in_range(
10975 buffer.anchor_before(range_to_move.start)
10976 ..buffer.anchor_after(range_to_move.end),
10977 ) {
10978 let mut start = fold.range.start.to_point(&buffer);
10979 let mut end = fold.range.end.to_point(&buffer);
10980 start.row += row_delta;
10981 end.row += row_delta;
10982 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10983 }
10984 }
10985 }
10986
10987 // If we didn't move line(s), preserve the existing selections
10988 new_selections.append(&mut contiguous_row_selections);
10989 }
10990
10991 self.transact(window, cx, |this, window, cx| {
10992 this.unfold_ranges(&unfold_ranges, true, true, cx);
10993 this.buffer.update(cx, |buffer, cx| {
10994 for (range, text) in edits {
10995 buffer.edit([(range, text)], None, cx);
10996 }
10997 });
10998 this.fold_creases(refold_creases, true, window, cx);
10999 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11000 s.select(new_selections)
11001 });
11002 });
11003 }
11004
11005 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11006 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11007 let text_layout_details = &self.text_layout_details(window);
11008 self.transact(window, cx, |this, window, cx| {
11009 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11010 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11011 s.move_with(|display_map, selection| {
11012 if !selection.is_empty() {
11013 return;
11014 }
11015
11016 let mut head = selection.head();
11017 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11018 if head.column() == display_map.line_len(head.row()) {
11019 transpose_offset = display_map
11020 .buffer_snapshot
11021 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11022 }
11023
11024 if transpose_offset == 0 {
11025 return;
11026 }
11027
11028 *head.column_mut() += 1;
11029 head = display_map.clip_point(head, Bias::Right);
11030 let goal = SelectionGoal::HorizontalPosition(
11031 display_map
11032 .x_for_display_point(head, text_layout_details)
11033 .into(),
11034 );
11035 selection.collapse_to(head, goal);
11036
11037 let transpose_start = display_map
11038 .buffer_snapshot
11039 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11040 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11041 let transpose_end = display_map
11042 .buffer_snapshot
11043 .clip_offset(transpose_offset + 1, Bias::Right);
11044 if let Some(ch) =
11045 display_map.buffer_snapshot.chars_at(transpose_start).next()
11046 {
11047 edits.push((transpose_start..transpose_offset, String::new()));
11048 edits.push((transpose_end..transpose_end, ch.to_string()));
11049 }
11050 }
11051 });
11052 edits
11053 });
11054 this.buffer
11055 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11056 let selections = this.selections.all::<usize>(cx);
11057 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11058 s.select(selections);
11059 });
11060 });
11061 }
11062
11063 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11064 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11065 self.rewrap_impl(RewrapOptions::default(), cx)
11066 }
11067
11068 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11069 let buffer = self.buffer.read(cx).snapshot(cx);
11070 let selections = self.selections.all::<Point>(cx);
11071
11072 // Shrink and split selections to respect paragraph boundaries.
11073 let ranges = selections.into_iter().flat_map(|selection| {
11074 let language_settings = buffer.language_settings_at(selection.head(), cx);
11075 let language_scope = buffer.language_scope_at(selection.head());
11076
11077 let Some(start_row) = (selection.start.row..=selection.end.row)
11078 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11079 else {
11080 return vec![];
11081 };
11082 let Some(end_row) = (selection.start.row..=selection.end.row)
11083 .rev()
11084 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11085 else {
11086 return vec![];
11087 };
11088
11089 let mut row = start_row;
11090 let mut ranges = Vec::new();
11091 while let Some(blank_row) =
11092 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11093 {
11094 let next_paragraph_start = (blank_row + 1..=end_row)
11095 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11096 .unwrap();
11097 ranges.push((
11098 language_settings.clone(),
11099 language_scope.clone(),
11100 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11101 ));
11102 row = next_paragraph_start;
11103 }
11104 ranges.push((
11105 language_settings.clone(),
11106 language_scope.clone(),
11107 Point::new(row, 0)..Point::new(end_row, 0),
11108 ));
11109
11110 ranges
11111 });
11112
11113 let mut edits = Vec::new();
11114 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11115
11116 for (language_settings, language_scope, range) in ranges {
11117 let mut start_row = range.start.row;
11118 let mut end_row = range.end.row;
11119
11120 // Skip selections that overlap with a range that has already been rewrapped.
11121 let selection_range = start_row..end_row;
11122 if rewrapped_row_ranges
11123 .iter()
11124 .any(|range| range.overlaps(&selection_range))
11125 {
11126 continue;
11127 }
11128
11129 let tab_size = language_settings.tab_size;
11130
11131 // Since not all lines in the selection may be at the same indent
11132 // level, choose the indent size that is the most common between all
11133 // of the lines.
11134 //
11135 // If there is a tie, we use the deepest indent.
11136 let (indent_size, indent_end) = {
11137 let mut indent_size_occurrences = HashMap::default();
11138 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11139
11140 for row in start_row..=end_row {
11141 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11142 rows_by_indent_size.entry(indent).or_default().push(row);
11143 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11144 }
11145
11146 let indent_size = indent_size_occurrences
11147 .into_iter()
11148 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11149 .map(|(indent, _)| indent)
11150 .unwrap_or_default();
11151 let row = rows_by_indent_size[&indent_size][0];
11152 let indent_end = Point::new(row, indent_size.len);
11153
11154 (indent_size, indent_end)
11155 };
11156
11157 let mut line_prefix = indent_size.chars().collect::<String>();
11158
11159 let mut inside_comment = false;
11160 if let Some(comment_prefix) = language_scope.and_then(|language| {
11161 language
11162 .line_comment_prefixes()
11163 .iter()
11164 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11165 .cloned()
11166 }) {
11167 line_prefix.push_str(&comment_prefix);
11168 inside_comment = true;
11169 }
11170
11171 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11172 RewrapBehavior::InComments => inside_comment,
11173 RewrapBehavior::InSelections => !range.is_empty(),
11174 RewrapBehavior::Anywhere => true,
11175 };
11176
11177 let should_rewrap = options.override_language_settings
11178 || allow_rewrap_based_on_language
11179 || self.hard_wrap.is_some();
11180 if !should_rewrap {
11181 continue;
11182 }
11183
11184 if range.is_empty() {
11185 'expand_upwards: while start_row > 0 {
11186 let prev_row = start_row - 1;
11187 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11188 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11189 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11190 {
11191 start_row = prev_row;
11192 } else {
11193 break 'expand_upwards;
11194 }
11195 }
11196
11197 'expand_downwards: while end_row < buffer.max_point().row {
11198 let next_row = end_row + 1;
11199 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11200 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11201 && !buffer.is_line_blank(MultiBufferRow(next_row))
11202 {
11203 end_row = next_row;
11204 } else {
11205 break 'expand_downwards;
11206 }
11207 }
11208 }
11209
11210 let start = Point::new(start_row, 0);
11211 let start_offset = start.to_offset(&buffer);
11212 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11213 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11214 let Some(lines_without_prefixes) = selection_text
11215 .lines()
11216 .map(|line| {
11217 line.strip_prefix(&line_prefix)
11218 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11219 .with_context(|| {
11220 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11221 })
11222 })
11223 .collect::<Result<Vec<_>, _>>()
11224 .log_err()
11225 else {
11226 continue;
11227 };
11228
11229 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11230 buffer
11231 .language_settings_at(Point::new(start_row, 0), cx)
11232 .preferred_line_length as usize
11233 });
11234 let wrapped_text = wrap_with_prefix(
11235 line_prefix,
11236 lines_without_prefixes.join("\n"),
11237 wrap_column,
11238 tab_size,
11239 options.preserve_existing_whitespace,
11240 );
11241
11242 // TODO: should always use char-based diff while still supporting cursor behavior that
11243 // matches vim.
11244 let mut diff_options = DiffOptions::default();
11245 if options.override_language_settings {
11246 diff_options.max_word_diff_len = 0;
11247 diff_options.max_word_diff_line_count = 0;
11248 } else {
11249 diff_options.max_word_diff_len = usize::MAX;
11250 diff_options.max_word_diff_line_count = usize::MAX;
11251 }
11252
11253 for (old_range, new_text) in
11254 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11255 {
11256 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11257 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11258 edits.push((edit_start..edit_end, new_text));
11259 }
11260
11261 rewrapped_row_ranges.push(start_row..=end_row);
11262 }
11263
11264 self.buffer
11265 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11266 }
11267
11268 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11269 let mut text = String::new();
11270 let buffer = self.buffer.read(cx).snapshot(cx);
11271 let mut selections = self.selections.all::<Point>(cx);
11272 let mut clipboard_selections = Vec::with_capacity(selections.len());
11273 {
11274 let max_point = buffer.max_point();
11275 let mut is_first = true;
11276 for selection in &mut selections {
11277 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11278 if is_entire_line {
11279 selection.start = Point::new(selection.start.row, 0);
11280 if !selection.is_empty() && selection.end.column == 0 {
11281 selection.end = cmp::min(max_point, selection.end);
11282 } else {
11283 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11284 }
11285 selection.goal = SelectionGoal::None;
11286 }
11287 if is_first {
11288 is_first = false;
11289 } else {
11290 text += "\n";
11291 }
11292 let mut len = 0;
11293 for chunk in buffer.text_for_range(selection.start..selection.end) {
11294 text.push_str(chunk);
11295 len += chunk.len();
11296 }
11297 clipboard_selections.push(ClipboardSelection {
11298 len,
11299 is_entire_line,
11300 first_line_indent: buffer
11301 .indent_size_for_line(MultiBufferRow(selection.start.row))
11302 .len,
11303 });
11304 }
11305 }
11306
11307 self.transact(window, cx, |this, window, cx| {
11308 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11309 s.select(selections);
11310 });
11311 this.insert("", window, cx);
11312 });
11313 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11314 }
11315
11316 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11317 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11318 let item = self.cut_common(window, cx);
11319 cx.write_to_clipboard(item);
11320 }
11321
11322 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11323 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11324 self.change_selections(None, window, cx, |s| {
11325 s.move_with(|snapshot, sel| {
11326 if sel.is_empty() {
11327 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11328 }
11329 });
11330 });
11331 let item = self.cut_common(window, cx);
11332 cx.set_global(KillRing(item))
11333 }
11334
11335 pub fn kill_ring_yank(
11336 &mut self,
11337 _: &KillRingYank,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11342 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11343 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11344 (kill_ring.text().to_string(), kill_ring.metadata_json())
11345 } else {
11346 return;
11347 }
11348 } else {
11349 return;
11350 };
11351 self.do_paste(&text, metadata, false, window, cx);
11352 }
11353
11354 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11355 self.do_copy(true, cx);
11356 }
11357
11358 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11359 self.do_copy(false, cx);
11360 }
11361
11362 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11363 let selections = self.selections.all::<Point>(cx);
11364 let buffer = self.buffer.read(cx).read(cx);
11365 let mut text = String::new();
11366
11367 let mut clipboard_selections = Vec::with_capacity(selections.len());
11368 {
11369 let max_point = buffer.max_point();
11370 let mut is_first = true;
11371 for selection in &selections {
11372 let mut start = selection.start;
11373 let mut end = selection.end;
11374 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11375 if is_entire_line {
11376 start = Point::new(start.row, 0);
11377 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11378 }
11379
11380 let mut trimmed_selections = Vec::new();
11381 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11382 let row = MultiBufferRow(start.row);
11383 let first_indent = buffer.indent_size_for_line(row);
11384 if first_indent.len == 0 || start.column > first_indent.len {
11385 trimmed_selections.push(start..end);
11386 } else {
11387 trimmed_selections.push(
11388 Point::new(row.0, first_indent.len)
11389 ..Point::new(row.0, buffer.line_len(row)),
11390 );
11391 for row in start.row + 1..=end.row {
11392 let mut line_len = buffer.line_len(MultiBufferRow(row));
11393 if row == end.row {
11394 line_len = end.column;
11395 }
11396 if line_len == 0 {
11397 trimmed_selections
11398 .push(Point::new(row, 0)..Point::new(row, line_len));
11399 continue;
11400 }
11401 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11402 if row_indent_size.len >= first_indent.len {
11403 trimmed_selections.push(
11404 Point::new(row, first_indent.len)..Point::new(row, line_len),
11405 );
11406 } else {
11407 trimmed_selections.clear();
11408 trimmed_selections.push(start..end);
11409 break;
11410 }
11411 }
11412 }
11413 } else {
11414 trimmed_selections.push(start..end);
11415 }
11416
11417 for trimmed_range in trimmed_selections {
11418 if is_first {
11419 is_first = false;
11420 } else {
11421 text += "\n";
11422 }
11423 let mut len = 0;
11424 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11425 text.push_str(chunk);
11426 len += chunk.len();
11427 }
11428 clipboard_selections.push(ClipboardSelection {
11429 len,
11430 is_entire_line,
11431 first_line_indent: buffer
11432 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11433 .len,
11434 });
11435 }
11436 }
11437 }
11438
11439 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11440 text,
11441 clipboard_selections,
11442 ));
11443 }
11444
11445 pub fn do_paste(
11446 &mut self,
11447 text: &String,
11448 clipboard_selections: Option<Vec<ClipboardSelection>>,
11449 handle_entire_lines: bool,
11450 window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 if self.read_only(cx) {
11454 return;
11455 }
11456
11457 let clipboard_text = Cow::Borrowed(text);
11458
11459 self.transact(window, cx, |this, window, cx| {
11460 if let Some(mut clipboard_selections) = clipboard_selections {
11461 let old_selections = this.selections.all::<usize>(cx);
11462 let all_selections_were_entire_line =
11463 clipboard_selections.iter().all(|s| s.is_entire_line);
11464 let first_selection_indent_column =
11465 clipboard_selections.first().map(|s| s.first_line_indent);
11466 if clipboard_selections.len() != old_selections.len() {
11467 clipboard_selections.drain(..);
11468 }
11469 let cursor_offset = this.selections.last::<usize>(cx).head();
11470 let mut auto_indent_on_paste = true;
11471
11472 this.buffer.update(cx, |buffer, cx| {
11473 let snapshot = buffer.read(cx);
11474 auto_indent_on_paste = snapshot
11475 .language_settings_at(cursor_offset, cx)
11476 .auto_indent_on_paste;
11477
11478 let mut start_offset = 0;
11479 let mut edits = Vec::new();
11480 let mut original_indent_columns = Vec::new();
11481 for (ix, selection) in old_selections.iter().enumerate() {
11482 let to_insert;
11483 let entire_line;
11484 let original_indent_column;
11485 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11486 let end_offset = start_offset + clipboard_selection.len;
11487 to_insert = &clipboard_text[start_offset..end_offset];
11488 entire_line = clipboard_selection.is_entire_line;
11489 start_offset = end_offset + 1;
11490 original_indent_column = Some(clipboard_selection.first_line_indent);
11491 } else {
11492 to_insert = clipboard_text.as_str();
11493 entire_line = all_selections_were_entire_line;
11494 original_indent_column = first_selection_indent_column
11495 }
11496
11497 // If the corresponding selection was empty when this slice of the
11498 // clipboard text was written, then the entire line containing the
11499 // selection was copied. If this selection is also currently empty,
11500 // then paste the line before the current line of the buffer.
11501 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11502 let column = selection.start.to_point(&snapshot).column as usize;
11503 let line_start = selection.start - column;
11504 line_start..line_start
11505 } else {
11506 selection.range()
11507 };
11508
11509 edits.push((range, to_insert));
11510 original_indent_columns.push(original_indent_column);
11511 }
11512 drop(snapshot);
11513
11514 buffer.edit(
11515 edits,
11516 if auto_indent_on_paste {
11517 Some(AutoindentMode::Block {
11518 original_indent_columns,
11519 })
11520 } else {
11521 None
11522 },
11523 cx,
11524 );
11525 });
11526
11527 let selections = this.selections.all::<usize>(cx);
11528 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11529 s.select(selections)
11530 });
11531 } else {
11532 this.insert(&clipboard_text, window, cx);
11533 }
11534 });
11535 }
11536
11537 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11538 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11539 if let Some(item) = cx.read_from_clipboard() {
11540 let entries = item.entries();
11541
11542 match entries.first() {
11543 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11544 // of all the pasted entries.
11545 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11546 .do_paste(
11547 clipboard_string.text(),
11548 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11549 true,
11550 window,
11551 cx,
11552 ),
11553 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11554 }
11555 }
11556 }
11557
11558 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11559 if self.read_only(cx) {
11560 return;
11561 }
11562
11563 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11564
11565 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11566 if let Some((selections, _)) =
11567 self.selection_history.transaction(transaction_id).cloned()
11568 {
11569 self.change_selections(None, window, cx, |s| {
11570 s.select_anchors(selections.to_vec());
11571 });
11572 } else {
11573 log::error!(
11574 "No entry in selection_history found for undo. \
11575 This may correspond to a bug where undo does not update the selection. \
11576 If this is occurring, please add details to \
11577 https://github.com/zed-industries/zed/issues/22692"
11578 );
11579 }
11580 self.request_autoscroll(Autoscroll::fit(), cx);
11581 self.unmark_text(window, cx);
11582 self.refresh_inline_completion(true, false, window, cx);
11583 cx.emit(EditorEvent::Edited { transaction_id });
11584 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11585 }
11586 }
11587
11588 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11589 if self.read_only(cx) {
11590 return;
11591 }
11592
11593 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11594
11595 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11596 if let Some((_, Some(selections))) =
11597 self.selection_history.transaction(transaction_id).cloned()
11598 {
11599 self.change_selections(None, window, cx, |s| {
11600 s.select_anchors(selections.to_vec());
11601 });
11602 } else {
11603 log::error!(
11604 "No entry in selection_history found for redo. \
11605 This may correspond to a bug where undo does not update the selection. \
11606 If this is occurring, please add details to \
11607 https://github.com/zed-industries/zed/issues/22692"
11608 );
11609 }
11610 self.request_autoscroll(Autoscroll::fit(), cx);
11611 self.unmark_text(window, cx);
11612 self.refresh_inline_completion(true, false, window, cx);
11613 cx.emit(EditorEvent::Edited { transaction_id });
11614 }
11615 }
11616
11617 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11618 self.buffer
11619 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11620 }
11621
11622 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11623 self.buffer
11624 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11625 }
11626
11627 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11628 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11629 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11630 s.move_with(|map, selection| {
11631 let cursor = if selection.is_empty() {
11632 movement::left(map, selection.start)
11633 } else {
11634 selection.start
11635 };
11636 selection.collapse_to(cursor, SelectionGoal::None);
11637 });
11638 })
11639 }
11640
11641 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11642 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11643 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11644 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11645 })
11646 }
11647
11648 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11649 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11650 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11651 s.move_with(|map, selection| {
11652 let cursor = if selection.is_empty() {
11653 movement::right(map, selection.end)
11654 } else {
11655 selection.end
11656 };
11657 selection.collapse_to(cursor, SelectionGoal::None)
11658 });
11659 })
11660 }
11661
11662 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11663 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11664 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11665 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11666 })
11667 }
11668
11669 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11670 if self.take_rename(true, window, cx).is_some() {
11671 return;
11672 }
11673
11674 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11675 cx.propagate();
11676 return;
11677 }
11678
11679 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11680
11681 let text_layout_details = &self.text_layout_details(window);
11682 let selection_count = self.selections.count();
11683 let first_selection = self.selections.first_anchor();
11684
11685 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11686 s.move_with(|map, selection| {
11687 if !selection.is_empty() {
11688 selection.goal = SelectionGoal::None;
11689 }
11690 let (cursor, goal) = movement::up(
11691 map,
11692 selection.start,
11693 selection.goal,
11694 false,
11695 text_layout_details,
11696 );
11697 selection.collapse_to(cursor, goal);
11698 });
11699 });
11700
11701 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11702 {
11703 cx.propagate();
11704 }
11705 }
11706
11707 pub fn move_up_by_lines(
11708 &mut self,
11709 action: &MoveUpByLines,
11710 window: &mut Window,
11711 cx: &mut Context<Self>,
11712 ) {
11713 if self.take_rename(true, window, cx).is_some() {
11714 return;
11715 }
11716
11717 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11718 cx.propagate();
11719 return;
11720 }
11721
11722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11723
11724 let text_layout_details = &self.text_layout_details(window);
11725
11726 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11727 s.move_with(|map, selection| {
11728 if !selection.is_empty() {
11729 selection.goal = SelectionGoal::None;
11730 }
11731 let (cursor, goal) = movement::up_by_rows(
11732 map,
11733 selection.start,
11734 action.lines,
11735 selection.goal,
11736 false,
11737 text_layout_details,
11738 );
11739 selection.collapse_to(cursor, goal);
11740 });
11741 })
11742 }
11743
11744 pub fn move_down_by_lines(
11745 &mut self,
11746 action: &MoveDownByLines,
11747 window: &mut Window,
11748 cx: &mut Context<Self>,
11749 ) {
11750 if self.take_rename(true, window, cx).is_some() {
11751 return;
11752 }
11753
11754 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11755 cx.propagate();
11756 return;
11757 }
11758
11759 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11760
11761 let text_layout_details = &self.text_layout_details(window);
11762
11763 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11764 s.move_with(|map, selection| {
11765 if !selection.is_empty() {
11766 selection.goal = SelectionGoal::None;
11767 }
11768 let (cursor, goal) = movement::down_by_rows(
11769 map,
11770 selection.start,
11771 action.lines,
11772 selection.goal,
11773 false,
11774 text_layout_details,
11775 );
11776 selection.collapse_to(cursor, goal);
11777 });
11778 })
11779 }
11780
11781 pub fn select_down_by_lines(
11782 &mut self,
11783 action: &SelectDownByLines,
11784 window: &mut Window,
11785 cx: &mut Context<Self>,
11786 ) {
11787 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11788 let text_layout_details = &self.text_layout_details(window);
11789 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11790 s.move_heads_with(|map, head, goal| {
11791 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11792 })
11793 })
11794 }
11795
11796 pub fn select_up_by_lines(
11797 &mut self,
11798 action: &SelectUpByLines,
11799 window: &mut Window,
11800 cx: &mut Context<Self>,
11801 ) {
11802 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11803 let text_layout_details = &self.text_layout_details(window);
11804 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11805 s.move_heads_with(|map, head, goal| {
11806 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11807 })
11808 })
11809 }
11810
11811 pub fn select_page_up(
11812 &mut self,
11813 _: &SelectPageUp,
11814 window: &mut Window,
11815 cx: &mut Context<Self>,
11816 ) {
11817 let Some(row_count) = self.visible_row_count() else {
11818 return;
11819 };
11820
11821 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11822
11823 let text_layout_details = &self.text_layout_details(window);
11824
11825 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11826 s.move_heads_with(|map, head, goal| {
11827 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11828 })
11829 })
11830 }
11831
11832 pub fn move_page_up(
11833 &mut self,
11834 action: &MovePageUp,
11835 window: &mut Window,
11836 cx: &mut Context<Self>,
11837 ) {
11838 if self.take_rename(true, window, cx).is_some() {
11839 return;
11840 }
11841
11842 if self
11843 .context_menu
11844 .borrow_mut()
11845 .as_mut()
11846 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11847 .unwrap_or(false)
11848 {
11849 return;
11850 }
11851
11852 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11853 cx.propagate();
11854 return;
11855 }
11856
11857 let Some(row_count) = self.visible_row_count() else {
11858 return;
11859 };
11860
11861 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11862
11863 let autoscroll = if action.center_cursor {
11864 Autoscroll::center()
11865 } else {
11866 Autoscroll::fit()
11867 };
11868
11869 let text_layout_details = &self.text_layout_details(window);
11870
11871 self.change_selections(Some(autoscroll), window, cx, |s| {
11872 s.move_with(|map, selection| {
11873 if !selection.is_empty() {
11874 selection.goal = SelectionGoal::None;
11875 }
11876 let (cursor, goal) = movement::up_by_rows(
11877 map,
11878 selection.end,
11879 row_count,
11880 selection.goal,
11881 false,
11882 text_layout_details,
11883 );
11884 selection.collapse_to(cursor, goal);
11885 });
11886 });
11887 }
11888
11889 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11890 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11891 let text_layout_details = &self.text_layout_details(window);
11892 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11893 s.move_heads_with(|map, head, goal| {
11894 movement::up(map, head, goal, false, text_layout_details)
11895 })
11896 })
11897 }
11898
11899 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11900 self.take_rename(true, window, cx);
11901
11902 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11903 cx.propagate();
11904 return;
11905 }
11906
11907 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11908
11909 let text_layout_details = &self.text_layout_details(window);
11910 let selection_count = self.selections.count();
11911 let first_selection = self.selections.first_anchor();
11912
11913 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11914 s.move_with(|map, selection| {
11915 if !selection.is_empty() {
11916 selection.goal = SelectionGoal::None;
11917 }
11918 let (cursor, goal) = movement::down(
11919 map,
11920 selection.end,
11921 selection.goal,
11922 false,
11923 text_layout_details,
11924 );
11925 selection.collapse_to(cursor, goal);
11926 });
11927 });
11928
11929 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11930 {
11931 cx.propagate();
11932 }
11933 }
11934
11935 pub fn select_page_down(
11936 &mut self,
11937 _: &SelectPageDown,
11938 window: &mut Window,
11939 cx: &mut Context<Self>,
11940 ) {
11941 let Some(row_count) = self.visible_row_count() else {
11942 return;
11943 };
11944
11945 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11946
11947 let text_layout_details = &self.text_layout_details(window);
11948
11949 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11950 s.move_heads_with(|map, head, goal| {
11951 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11952 })
11953 })
11954 }
11955
11956 pub fn move_page_down(
11957 &mut self,
11958 action: &MovePageDown,
11959 window: &mut Window,
11960 cx: &mut Context<Self>,
11961 ) {
11962 if self.take_rename(true, window, cx).is_some() {
11963 return;
11964 }
11965
11966 if self
11967 .context_menu
11968 .borrow_mut()
11969 .as_mut()
11970 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11971 .unwrap_or(false)
11972 {
11973 return;
11974 }
11975
11976 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11977 cx.propagate();
11978 return;
11979 }
11980
11981 let Some(row_count) = self.visible_row_count() else {
11982 return;
11983 };
11984
11985 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11986
11987 let autoscroll = if action.center_cursor {
11988 Autoscroll::center()
11989 } else {
11990 Autoscroll::fit()
11991 };
11992
11993 let text_layout_details = &self.text_layout_details(window);
11994 self.change_selections(Some(autoscroll), window, cx, |s| {
11995 s.move_with(|map, selection| {
11996 if !selection.is_empty() {
11997 selection.goal = SelectionGoal::None;
11998 }
11999 let (cursor, goal) = movement::down_by_rows(
12000 map,
12001 selection.end,
12002 row_count,
12003 selection.goal,
12004 false,
12005 text_layout_details,
12006 );
12007 selection.collapse_to(cursor, goal);
12008 });
12009 });
12010 }
12011
12012 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12013 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12014 let text_layout_details = &self.text_layout_details(window);
12015 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12016 s.move_heads_with(|map, head, goal| {
12017 movement::down(map, head, goal, false, text_layout_details)
12018 })
12019 });
12020 }
12021
12022 pub fn context_menu_first(
12023 &mut self,
12024 _: &ContextMenuFirst,
12025 window: &mut Window,
12026 cx: &mut Context<Self>,
12027 ) {
12028 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12029 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12030 }
12031 }
12032
12033 pub fn context_menu_prev(
12034 &mut self,
12035 _: &ContextMenuPrevious,
12036 window: &mut Window,
12037 cx: &mut Context<Self>,
12038 ) {
12039 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12040 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12041 }
12042 }
12043
12044 pub fn context_menu_next(
12045 &mut self,
12046 _: &ContextMenuNext,
12047 window: &mut Window,
12048 cx: &mut Context<Self>,
12049 ) {
12050 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12051 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12052 }
12053 }
12054
12055 pub fn context_menu_last(
12056 &mut self,
12057 _: &ContextMenuLast,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) {
12061 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12062 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12063 }
12064 }
12065
12066 pub fn move_to_previous_word_start(
12067 &mut self,
12068 _: &MoveToPreviousWordStart,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12073 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12074 s.move_cursors_with(|map, head, _| {
12075 (
12076 movement::previous_word_start(map, head),
12077 SelectionGoal::None,
12078 )
12079 });
12080 })
12081 }
12082
12083 pub fn move_to_previous_subword_start(
12084 &mut self,
12085 _: &MoveToPreviousSubwordStart,
12086 window: &mut Window,
12087 cx: &mut Context<Self>,
12088 ) {
12089 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12090 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12091 s.move_cursors_with(|map, head, _| {
12092 (
12093 movement::previous_subword_start(map, head),
12094 SelectionGoal::None,
12095 )
12096 });
12097 })
12098 }
12099
12100 pub fn select_to_previous_word_start(
12101 &mut self,
12102 _: &SelectToPreviousWordStart,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12107 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12108 s.move_heads_with(|map, head, _| {
12109 (
12110 movement::previous_word_start(map, head),
12111 SelectionGoal::None,
12112 )
12113 });
12114 })
12115 }
12116
12117 pub fn select_to_previous_subword_start(
12118 &mut self,
12119 _: &SelectToPreviousSubwordStart,
12120 window: &mut Window,
12121 cx: &mut Context<Self>,
12122 ) {
12123 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12124 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12125 s.move_heads_with(|map, head, _| {
12126 (
12127 movement::previous_subword_start(map, head),
12128 SelectionGoal::None,
12129 )
12130 });
12131 })
12132 }
12133
12134 pub fn delete_to_previous_word_start(
12135 &mut self,
12136 action: &DeleteToPreviousWordStart,
12137 window: &mut Window,
12138 cx: &mut Context<Self>,
12139 ) {
12140 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12141 self.transact(window, cx, |this, window, cx| {
12142 this.select_autoclose_pair(window, cx);
12143 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12144 s.move_with(|map, selection| {
12145 if selection.is_empty() {
12146 let cursor = if action.ignore_newlines {
12147 movement::previous_word_start(map, selection.head())
12148 } else {
12149 movement::previous_word_start_or_newline(map, selection.head())
12150 };
12151 selection.set_head(cursor, SelectionGoal::None);
12152 }
12153 });
12154 });
12155 this.insert("", window, cx);
12156 });
12157 }
12158
12159 pub fn delete_to_previous_subword_start(
12160 &mut self,
12161 _: &DeleteToPreviousSubwordStart,
12162 window: &mut Window,
12163 cx: &mut Context<Self>,
12164 ) {
12165 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12166 self.transact(window, cx, |this, window, cx| {
12167 this.select_autoclose_pair(window, cx);
12168 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12169 s.move_with(|map, selection| {
12170 if selection.is_empty() {
12171 let cursor = movement::previous_subword_start(map, selection.head());
12172 selection.set_head(cursor, SelectionGoal::None);
12173 }
12174 });
12175 });
12176 this.insert("", window, cx);
12177 });
12178 }
12179
12180 pub fn move_to_next_word_end(
12181 &mut self,
12182 _: &MoveToNextWordEnd,
12183 window: &mut Window,
12184 cx: &mut Context<Self>,
12185 ) {
12186 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12188 s.move_cursors_with(|map, head, _| {
12189 (movement::next_word_end(map, head), SelectionGoal::None)
12190 });
12191 })
12192 }
12193
12194 pub fn move_to_next_subword_end(
12195 &mut self,
12196 _: &MoveToNextSubwordEnd,
12197 window: &mut Window,
12198 cx: &mut Context<Self>,
12199 ) {
12200 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12201 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12202 s.move_cursors_with(|map, head, _| {
12203 (movement::next_subword_end(map, head), SelectionGoal::None)
12204 });
12205 })
12206 }
12207
12208 pub fn select_to_next_word_end(
12209 &mut self,
12210 _: &SelectToNextWordEnd,
12211 window: &mut Window,
12212 cx: &mut Context<Self>,
12213 ) {
12214 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12215 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12216 s.move_heads_with(|map, head, _| {
12217 (movement::next_word_end(map, head), SelectionGoal::None)
12218 });
12219 })
12220 }
12221
12222 pub fn select_to_next_subword_end(
12223 &mut self,
12224 _: &SelectToNextSubwordEnd,
12225 window: &mut Window,
12226 cx: &mut Context<Self>,
12227 ) {
12228 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12229 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12230 s.move_heads_with(|map, head, _| {
12231 (movement::next_subword_end(map, head), SelectionGoal::None)
12232 });
12233 })
12234 }
12235
12236 pub fn delete_to_next_word_end(
12237 &mut self,
12238 action: &DeleteToNextWordEnd,
12239 window: &mut Window,
12240 cx: &mut Context<Self>,
12241 ) {
12242 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12243 self.transact(window, cx, |this, window, cx| {
12244 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12245 s.move_with(|map, selection| {
12246 if selection.is_empty() {
12247 let cursor = if action.ignore_newlines {
12248 movement::next_word_end(map, selection.head())
12249 } else {
12250 movement::next_word_end_or_newline(map, selection.head())
12251 };
12252 selection.set_head(cursor, SelectionGoal::None);
12253 }
12254 });
12255 });
12256 this.insert("", window, cx);
12257 });
12258 }
12259
12260 pub fn delete_to_next_subword_end(
12261 &mut self,
12262 _: &DeleteToNextSubwordEnd,
12263 window: &mut Window,
12264 cx: &mut Context<Self>,
12265 ) {
12266 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12267 self.transact(window, cx, |this, window, cx| {
12268 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12269 s.move_with(|map, selection| {
12270 if selection.is_empty() {
12271 let cursor = movement::next_subword_end(map, selection.head());
12272 selection.set_head(cursor, SelectionGoal::None);
12273 }
12274 });
12275 });
12276 this.insert("", window, cx);
12277 });
12278 }
12279
12280 pub fn move_to_beginning_of_line(
12281 &mut self,
12282 action: &MoveToBeginningOfLine,
12283 window: &mut Window,
12284 cx: &mut Context<Self>,
12285 ) {
12286 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12287 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12288 s.move_cursors_with(|map, head, _| {
12289 (
12290 movement::indented_line_beginning(
12291 map,
12292 head,
12293 action.stop_at_soft_wraps,
12294 action.stop_at_indent,
12295 ),
12296 SelectionGoal::None,
12297 )
12298 });
12299 })
12300 }
12301
12302 pub fn select_to_beginning_of_line(
12303 &mut self,
12304 action: &SelectToBeginningOfLine,
12305 window: &mut Window,
12306 cx: &mut Context<Self>,
12307 ) {
12308 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12309 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12310 s.move_heads_with(|map, head, _| {
12311 (
12312 movement::indented_line_beginning(
12313 map,
12314 head,
12315 action.stop_at_soft_wraps,
12316 action.stop_at_indent,
12317 ),
12318 SelectionGoal::None,
12319 )
12320 });
12321 });
12322 }
12323
12324 pub fn delete_to_beginning_of_line(
12325 &mut self,
12326 action: &DeleteToBeginningOfLine,
12327 window: &mut Window,
12328 cx: &mut Context<Self>,
12329 ) {
12330 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12331 self.transact(window, cx, |this, window, cx| {
12332 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12333 s.move_with(|_, selection| {
12334 selection.reversed = true;
12335 });
12336 });
12337
12338 this.select_to_beginning_of_line(
12339 &SelectToBeginningOfLine {
12340 stop_at_soft_wraps: false,
12341 stop_at_indent: action.stop_at_indent,
12342 },
12343 window,
12344 cx,
12345 );
12346 this.backspace(&Backspace, window, cx);
12347 });
12348 }
12349
12350 pub fn move_to_end_of_line(
12351 &mut self,
12352 action: &MoveToEndOfLine,
12353 window: &mut Window,
12354 cx: &mut Context<Self>,
12355 ) {
12356 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12357 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12358 s.move_cursors_with(|map, head, _| {
12359 (
12360 movement::line_end(map, head, action.stop_at_soft_wraps),
12361 SelectionGoal::None,
12362 )
12363 });
12364 })
12365 }
12366
12367 pub fn select_to_end_of_line(
12368 &mut self,
12369 action: &SelectToEndOfLine,
12370 window: &mut Window,
12371 cx: &mut Context<Self>,
12372 ) {
12373 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12374 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12375 s.move_heads_with(|map, head, _| {
12376 (
12377 movement::line_end(map, head, action.stop_at_soft_wraps),
12378 SelectionGoal::None,
12379 )
12380 });
12381 })
12382 }
12383
12384 pub fn delete_to_end_of_line(
12385 &mut self,
12386 _: &DeleteToEndOfLine,
12387 window: &mut Window,
12388 cx: &mut Context<Self>,
12389 ) {
12390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12391 self.transact(window, cx, |this, window, cx| {
12392 this.select_to_end_of_line(
12393 &SelectToEndOfLine {
12394 stop_at_soft_wraps: false,
12395 },
12396 window,
12397 cx,
12398 );
12399 this.delete(&Delete, window, cx);
12400 });
12401 }
12402
12403 pub fn cut_to_end_of_line(
12404 &mut self,
12405 _: &CutToEndOfLine,
12406 window: &mut Window,
12407 cx: &mut Context<Self>,
12408 ) {
12409 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12410 self.transact(window, cx, |this, window, cx| {
12411 this.select_to_end_of_line(
12412 &SelectToEndOfLine {
12413 stop_at_soft_wraps: false,
12414 },
12415 window,
12416 cx,
12417 );
12418 this.cut(&Cut, window, cx);
12419 });
12420 }
12421
12422 pub fn move_to_start_of_paragraph(
12423 &mut self,
12424 _: &MoveToStartOfParagraph,
12425 window: &mut Window,
12426 cx: &mut Context<Self>,
12427 ) {
12428 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12429 cx.propagate();
12430 return;
12431 }
12432 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12433 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12434 s.move_with(|map, selection| {
12435 selection.collapse_to(
12436 movement::start_of_paragraph(map, selection.head(), 1),
12437 SelectionGoal::None,
12438 )
12439 });
12440 })
12441 }
12442
12443 pub fn move_to_end_of_paragraph(
12444 &mut self,
12445 _: &MoveToEndOfParagraph,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12450 cx.propagate();
12451 return;
12452 }
12453 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12454 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12455 s.move_with(|map, selection| {
12456 selection.collapse_to(
12457 movement::end_of_paragraph(map, selection.head(), 1),
12458 SelectionGoal::None,
12459 )
12460 });
12461 })
12462 }
12463
12464 pub fn select_to_start_of_paragraph(
12465 &mut self,
12466 _: &SelectToStartOfParagraph,
12467 window: &mut Window,
12468 cx: &mut Context<Self>,
12469 ) {
12470 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12471 cx.propagate();
12472 return;
12473 }
12474 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12475 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12476 s.move_heads_with(|map, head, _| {
12477 (
12478 movement::start_of_paragraph(map, head, 1),
12479 SelectionGoal::None,
12480 )
12481 });
12482 })
12483 }
12484
12485 pub fn select_to_end_of_paragraph(
12486 &mut self,
12487 _: &SelectToEndOfParagraph,
12488 window: &mut Window,
12489 cx: &mut Context<Self>,
12490 ) {
12491 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12492 cx.propagate();
12493 return;
12494 }
12495 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12496 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12497 s.move_heads_with(|map, head, _| {
12498 (
12499 movement::end_of_paragraph(map, head, 1),
12500 SelectionGoal::None,
12501 )
12502 });
12503 })
12504 }
12505
12506 pub fn move_to_start_of_excerpt(
12507 &mut self,
12508 _: &MoveToStartOfExcerpt,
12509 window: &mut Window,
12510 cx: &mut Context<Self>,
12511 ) {
12512 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12513 cx.propagate();
12514 return;
12515 }
12516 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12517 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12518 s.move_with(|map, selection| {
12519 selection.collapse_to(
12520 movement::start_of_excerpt(
12521 map,
12522 selection.head(),
12523 workspace::searchable::Direction::Prev,
12524 ),
12525 SelectionGoal::None,
12526 )
12527 });
12528 })
12529 }
12530
12531 pub fn move_to_start_of_next_excerpt(
12532 &mut self,
12533 _: &MoveToStartOfNextExcerpt,
12534 window: &mut Window,
12535 cx: &mut Context<Self>,
12536 ) {
12537 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12538 cx.propagate();
12539 return;
12540 }
12541
12542 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12543 s.move_with(|map, selection| {
12544 selection.collapse_to(
12545 movement::start_of_excerpt(
12546 map,
12547 selection.head(),
12548 workspace::searchable::Direction::Next,
12549 ),
12550 SelectionGoal::None,
12551 )
12552 });
12553 })
12554 }
12555
12556 pub fn move_to_end_of_excerpt(
12557 &mut self,
12558 _: &MoveToEndOfExcerpt,
12559 window: &mut Window,
12560 cx: &mut Context<Self>,
12561 ) {
12562 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12563 cx.propagate();
12564 return;
12565 }
12566 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12567 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12568 s.move_with(|map, selection| {
12569 selection.collapse_to(
12570 movement::end_of_excerpt(
12571 map,
12572 selection.head(),
12573 workspace::searchable::Direction::Next,
12574 ),
12575 SelectionGoal::None,
12576 )
12577 });
12578 })
12579 }
12580
12581 pub fn move_to_end_of_previous_excerpt(
12582 &mut self,
12583 _: &MoveToEndOfPreviousExcerpt,
12584 window: &mut Window,
12585 cx: &mut Context<Self>,
12586 ) {
12587 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12588 cx.propagate();
12589 return;
12590 }
12591 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12593 s.move_with(|map, selection| {
12594 selection.collapse_to(
12595 movement::end_of_excerpt(
12596 map,
12597 selection.head(),
12598 workspace::searchable::Direction::Prev,
12599 ),
12600 SelectionGoal::None,
12601 )
12602 });
12603 })
12604 }
12605
12606 pub fn select_to_start_of_excerpt(
12607 &mut self,
12608 _: &SelectToStartOfExcerpt,
12609 window: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12613 cx.propagate();
12614 return;
12615 }
12616 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12617 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12618 s.move_heads_with(|map, head, _| {
12619 (
12620 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12621 SelectionGoal::None,
12622 )
12623 });
12624 })
12625 }
12626
12627 pub fn select_to_start_of_next_excerpt(
12628 &mut self,
12629 _: &SelectToStartOfNextExcerpt,
12630 window: &mut Window,
12631 cx: &mut Context<Self>,
12632 ) {
12633 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12634 cx.propagate();
12635 return;
12636 }
12637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12638 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12639 s.move_heads_with(|map, head, _| {
12640 (
12641 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12642 SelectionGoal::None,
12643 )
12644 });
12645 })
12646 }
12647
12648 pub fn select_to_end_of_excerpt(
12649 &mut self,
12650 _: &SelectToEndOfExcerpt,
12651 window: &mut Window,
12652 cx: &mut Context<Self>,
12653 ) {
12654 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12655 cx.propagate();
12656 return;
12657 }
12658 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12659 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12660 s.move_heads_with(|map, head, _| {
12661 (
12662 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12663 SelectionGoal::None,
12664 )
12665 });
12666 })
12667 }
12668
12669 pub fn select_to_end_of_previous_excerpt(
12670 &mut self,
12671 _: &SelectToEndOfPreviousExcerpt,
12672 window: &mut Window,
12673 cx: &mut Context<Self>,
12674 ) {
12675 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12676 cx.propagate();
12677 return;
12678 }
12679 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12680 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12681 s.move_heads_with(|map, head, _| {
12682 (
12683 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12684 SelectionGoal::None,
12685 )
12686 });
12687 })
12688 }
12689
12690 pub fn move_to_beginning(
12691 &mut self,
12692 _: &MoveToBeginning,
12693 window: &mut Window,
12694 cx: &mut Context<Self>,
12695 ) {
12696 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12697 cx.propagate();
12698 return;
12699 }
12700 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12701 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12702 s.select_ranges(vec![0..0]);
12703 });
12704 }
12705
12706 pub fn select_to_beginning(
12707 &mut self,
12708 _: &SelectToBeginning,
12709 window: &mut Window,
12710 cx: &mut Context<Self>,
12711 ) {
12712 let mut selection = self.selections.last::<Point>(cx);
12713 selection.set_head(Point::zero(), SelectionGoal::None);
12714 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12715 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12716 s.select(vec![selection]);
12717 });
12718 }
12719
12720 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12721 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12722 cx.propagate();
12723 return;
12724 }
12725 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12726 let cursor = self.buffer.read(cx).read(cx).len();
12727 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12728 s.select_ranges(vec![cursor..cursor])
12729 });
12730 }
12731
12732 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12733 self.nav_history = nav_history;
12734 }
12735
12736 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12737 self.nav_history.as_ref()
12738 }
12739
12740 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12741 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12742 }
12743
12744 fn push_to_nav_history(
12745 &mut self,
12746 cursor_anchor: Anchor,
12747 new_position: Option<Point>,
12748 is_deactivate: bool,
12749 cx: &mut Context<Self>,
12750 ) {
12751 if let Some(nav_history) = self.nav_history.as_mut() {
12752 let buffer = self.buffer.read(cx).read(cx);
12753 let cursor_position = cursor_anchor.to_point(&buffer);
12754 let scroll_state = self.scroll_manager.anchor();
12755 let scroll_top_row = scroll_state.top_row(&buffer);
12756 drop(buffer);
12757
12758 if let Some(new_position) = new_position {
12759 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12760 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12761 return;
12762 }
12763 }
12764
12765 nav_history.push(
12766 Some(NavigationData {
12767 cursor_anchor,
12768 cursor_position,
12769 scroll_anchor: scroll_state,
12770 scroll_top_row,
12771 }),
12772 cx,
12773 );
12774 cx.emit(EditorEvent::PushedToNavHistory {
12775 anchor: cursor_anchor,
12776 is_deactivate,
12777 })
12778 }
12779 }
12780
12781 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12782 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12783 let buffer = self.buffer.read(cx).snapshot(cx);
12784 let mut selection = self.selections.first::<usize>(cx);
12785 selection.set_head(buffer.len(), SelectionGoal::None);
12786 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12787 s.select(vec![selection]);
12788 });
12789 }
12790
12791 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12792 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12793 let end = self.buffer.read(cx).read(cx).len();
12794 self.change_selections(None, window, cx, |s| {
12795 s.select_ranges(vec![0..end]);
12796 });
12797 }
12798
12799 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12800 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12801 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12802 let mut selections = self.selections.all::<Point>(cx);
12803 let max_point = display_map.buffer_snapshot.max_point();
12804 for selection in &mut selections {
12805 let rows = selection.spanned_rows(true, &display_map);
12806 selection.start = Point::new(rows.start.0, 0);
12807 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12808 selection.reversed = false;
12809 }
12810 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12811 s.select(selections);
12812 });
12813 }
12814
12815 pub fn split_selection_into_lines(
12816 &mut self,
12817 _: &SplitSelectionIntoLines,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 let selections = self
12822 .selections
12823 .all::<Point>(cx)
12824 .into_iter()
12825 .map(|selection| selection.start..selection.end)
12826 .collect::<Vec<_>>();
12827 self.unfold_ranges(&selections, true, true, cx);
12828
12829 let mut new_selection_ranges = Vec::new();
12830 {
12831 let buffer = self.buffer.read(cx).read(cx);
12832 for selection in selections {
12833 for row in selection.start.row..selection.end.row {
12834 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12835 new_selection_ranges.push(cursor..cursor);
12836 }
12837
12838 let is_multiline_selection = selection.start.row != selection.end.row;
12839 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12840 // so this action feels more ergonomic when paired with other selection operations
12841 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12842 if !should_skip_last {
12843 new_selection_ranges.push(selection.end..selection.end);
12844 }
12845 }
12846 }
12847 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12848 s.select_ranges(new_selection_ranges);
12849 });
12850 }
12851
12852 pub fn add_selection_above(
12853 &mut self,
12854 _: &AddSelectionAbove,
12855 window: &mut Window,
12856 cx: &mut Context<Self>,
12857 ) {
12858 self.add_selection(true, window, cx);
12859 }
12860
12861 pub fn add_selection_below(
12862 &mut self,
12863 _: &AddSelectionBelow,
12864 window: &mut Window,
12865 cx: &mut Context<Self>,
12866 ) {
12867 self.add_selection(false, window, cx);
12868 }
12869
12870 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12871 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12872
12873 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12874 let all_selections = self.selections.all::<Point>(cx);
12875 let text_layout_details = self.text_layout_details(window);
12876
12877 let (mut columnar_selections, new_selections_to_columnarize) = {
12878 if let Some(state) = self.add_selections_state.as_ref() {
12879 let columnar_selection_ids: HashSet<_> = state
12880 .groups
12881 .iter()
12882 .flat_map(|group| group.stack.iter())
12883 .copied()
12884 .collect();
12885
12886 all_selections
12887 .into_iter()
12888 .partition(|s| columnar_selection_ids.contains(&s.id))
12889 } else {
12890 (Vec::new(), all_selections)
12891 }
12892 };
12893
12894 let mut state = self
12895 .add_selections_state
12896 .take()
12897 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12898
12899 for selection in new_selections_to_columnarize {
12900 let range = selection.display_range(&display_map).sorted();
12901 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12902 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12903 let positions = start_x.min(end_x)..start_x.max(end_x);
12904 let mut stack = Vec::new();
12905 for row in range.start.row().0..=range.end.row().0 {
12906 if let Some(selection) = self.selections.build_columnar_selection(
12907 &display_map,
12908 DisplayRow(row),
12909 &positions,
12910 selection.reversed,
12911 &text_layout_details,
12912 ) {
12913 stack.push(selection.id);
12914 columnar_selections.push(selection);
12915 }
12916 }
12917 if !stack.is_empty() {
12918 if above {
12919 stack.reverse();
12920 }
12921 state.groups.push(AddSelectionsGroup { above, stack });
12922 }
12923 }
12924
12925 let mut final_selections = Vec::new();
12926 let end_row = if above {
12927 DisplayRow(0)
12928 } else {
12929 display_map.max_point().row()
12930 };
12931
12932 let mut last_added_item_per_group = HashMap::default();
12933 for group in state.groups.iter_mut() {
12934 if let Some(last_id) = group.stack.last() {
12935 last_added_item_per_group.insert(*last_id, group);
12936 }
12937 }
12938
12939 for selection in columnar_selections {
12940 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12941 if above == group.above {
12942 let range = selection.display_range(&display_map).sorted();
12943 debug_assert_eq!(range.start.row(), range.end.row());
12944 let mut row = range.start.row();
12945 let positions =
12946 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12947 px(start)..px(end)
12948 } else {
12949 let start_x =
12950 display_map.x_for_display_point(range.start, &text_layout_details);
12951 let end_x =
12952 display_map.x_for_display_point(range.end, &text_layout_details);
12953 start_x.min(end_x)..start_x.max(end_x)
12954 };
12955
12956 let mut maybe_new_selection = None;
12957 while row != end_row {
12958 if above {
12959 row.0 -= 1;
12960 } else {
12961 row.0 += 1;
12962 }
12963 if let Some(new_selection) = self.selections.build_columnar_selection(
12964 &display_map,
12965 row,
12966 &positions,
12967 selection.reversed,
12968 &text_layout_details,
12969 ) {
12970 maybe_new_selection = Some(new_selection);
12971 break;
12972 }
12973 }
12974
12975 if let Some(new_selection) = maybe_new_selection {
12976 group.stack.push(new_selection.id);
12977 if above {
12978 final_selections.push(new_selection);
12979 final_selections.push(selection);
12980 } else {
12981 final_selections.push(selection);
12982 final_selections.push(new_selection);
12983 }
12984 } else {
12985 final_selections.push(selection);
12986 }
12987 } else {
12988 group.stack.pop();
12989 }
12990 } else {
12991 final_selections.push(selection);
12992 }
12993 }
12994
12995 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12996 s.select(final_selections);
12997 });
12998
12999 let final_selection_ids: HashSet<_> = self
13000 .selections
13001 .all::<Point>(cx)
13002 .iter()
13003 .map(|s| s.id)
13004 .collect();
13005 state.groups.retain_mut(|group| {
13006 // selections might get merged above so we remove invalid items from stacks
13007 group.stack.retain(|id| final_selection_ids.contains(id));
13008
13009 // single selection in stack can be treated as initial state
13010 group.stack.len() > 1
13011 });
13012
13013 if !state.groups.is_empty() {
13014 self.add_selections_state = Some(state);
13015 }
13016 }
13017
13018 fn select_match_ranges(
13019 &mut self,
13020 range: Range<usize>,
13021 reversed: bool,
13022 replace_newest: bool,
13023 auto_scroll: Option<Autoscroll>,
13024 window: &mut Window,
13025 cx: &mut Context<Editor>,
13026 ) {
13027 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13028 self.change_selections(auto_scroll, window, cx, |s| {
13029 if replace_newest {
13030 s.delete(s.newest_anchor().id);
13031 }
13032 if reversed {
13033 s.insert_range(range.end..range.start);
13034 } else {
13035 s.insert_range(range);
13036 }
13037 });
13038 }
13039
13040 pub fn select_next_match_internal(
13041 &mut self,
13042 display_map: &DisplaySnapshot,
13043 replace_newest: bool,
13044 autoscroll: Option<Autoscroll>,
13045 window: &mut Window,
13046 cx: &mut Context<Self>,
13047 ) -> Result<()> {
13048 let buffer = &display_map.buffer_snapshot;
13049 let mut selections = self.selections.all::<usize>(cx);
13050 if let Some(mut select_next_state) = self.select_next_state.take() {
13051 let query = &select_next_state.query;
13052 if !select_next_state.done {
13053 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13054 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13055 let mut next_selected_range = None;
13056
13057 let bytes_after_last_selection =
13058 buffer.bytes_in_range(last_selection.end..buffer.len());
13059 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13060 let query_matches = query
13061 .stream_find_iter(bytes_after_last_selection)
13062 .map(|result| (last_selection.end, result))
13063 .chain(
13064 query
13065 .stream_find_iter(bytes_before_first_selection)
13066 .map(|result| (0, result)),
13067 );
13068
13069 for (start_offset, query_match) in query_matches {
13070 let query_match = query_match.unwrap(); // can only fail due to I/O
13071 let offset_range =
13072 start_offset + query_match.start()..start_offset + query_match.end();
13073 let display_range = offset_range.start.to_display_point(display_map)
13074 ..offset_range.end.to_display_point(display_map);
13075
13076 if !select_next_state.wordwise
13077 || (!movement::is_inside_word(display_map, display_range.start)
13078 && !movement::is_inside_word(display_map, display_range.end))
13079 {
13080 // TODO: This is n^2, because we might check all the selections
13081 if !selections
13082 .iter()
13083 .any(|selection| selection.range().overlaps(&offset_range))
13084 {
13085 next_selected_range = Some(offset_range);
13086 break;
13087 }
13088 }
13089 }
13090
13091 if let Some(next_selected_range) = next_selected_range {
13092 self.select_match_ranges(
13093 next_selected_range,
13094 last_selection.reversed,
13095 replace_newest,
13096 autoscroll,
13097 window,
13098 cx,
13099 );
13100 } else {
13101 select_next_state.done = true;
13102 }
13103 }
13104
13105 self.select_next_state = Some(select_next_state);
13106 } else {
13107 let mut only_carets = true;
13108 let mut same_text_selected = true;
13109 let mut selected_text = None;
13110
13111 let mut selections_iter = selections.iter().peekable();
13112 while let Some(selection) = selections_iter.next() {
13113 if selection.start != selection.end {
13114 only_carets = false;
13115 }
13116
13117 if same_text_selected {
13118 if selected_text.is_none() {
13119 selected_text =
13120 Some(buffer.text_for_range(selection.range()).collect::<String>());
13121 }
13122
13123 if let Some(next_selection) = selections_iter.peek() {
13124 if next_selection.range().len() == selection.range().len() {
13125 let next_selected_text = buffer
13126 .text_for_range(next_selection.range())
13127 .collect::<String>();
13128 if Some(next_selected_text) != selected_text {
13129 same_text_selected = false;
13130 selected_text = None;
13131 }
13132 } else {
13133 same_text_selected = false;
13134 selected_text = None;
13135 }
13136 }
13137 }
13138 }
13139
13140 if only_carets {
13141 for selection in &mut selections {
13142 let word_range = movement::surrounding_word(
13143 display_map,
13144 selection.start.to_display_point(display_map),
13145 );
13146 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13147 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13148 selection.goal = SelectionGoal::None;
13149 selection.reversed = false;
13150 self.select_match_ranges(
13151 selection.start..selection.end,
13152 selection.reversed,
13153 replace_newest,
13154 autoscroll,
13155 window,
13156 cx,
13157 );
13158 }
13159
13160 if selections.len() == 1 {
13161 let selection = selections
13162 .last()
13163 .expect("ensured that there's only one selection");
13164 let query = buffer
13165 .text_for_range(selection.start..selection.end)
13166 .collect::<String>();
13167 let is_empty = query.is_empty();
13168 let select_state = SelectNextState {
13169 query: AhoCorasick::new(&[query])?,
13170 wordwise: true,
13171 done: is_empty,
13172 };
13173 self.select_next_state = Some(select_state);
13174 } else {
13175 self.select_next_state = None;
13176 }
13177 } else if let Some(selected_text) = selected_text {
13178 self.select_next_state = Some(SelectNextState {
13179 query: AhoCorasick::new(&[selected_text])?,
13180 wordwise: false,
13181 done: false,
13182 });
13183 self.select_next_match_internal(
13184 display_map,
13185 replace_newest,
13186 autoscroll,
13187 window,
13188 cx,
13189 )?;
13190 }
13191 }
13192 Ok(())
13193 }
13194
13195 pub fn select_all_matches(
13196 &mut self,
13197 _action: &SelectAllMatches,
13198 window: &mut Window,
13199 cx: &mut Context<Self>,
13200 ) -> Result<()> {
13201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13202
13203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13204
13205 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13206 let Some(select_next_state) = self.select_next_state.as_mut() else {
13207 return Ok(());
13208 };
13209 if select_next_state.done {
13210 return Ok(());
13211 }
13212
13213 let mut new_selections = Vec::new();
13214
13215 let reversed = self.selections.oldest::<usize>(cx).reversed;
13216 let buffer = &display_map.buffer_snapshot;
13217 let query_matches = select_next_state
13218 .query
13219 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13220
13221 for query_match in query_matches.into_iter() {
13222 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13223 let offset_range = if reversed {
13224 query_match.end()..query_match.start()
13225 } else {
13226 query_match.start()..query_match.end()
13227 };
13228 let display_range = offset_range.start.to_display_point(&display_map)
13229 ..offset_range.end.to_display_point(&display_map);
13230
13231 if !select_next_state.wordwise
13232 || (!movement::is_inside_word(&display_map, display_range.start)
13233 && !movement::is_inside_word(&display_map, display_range.end))
13234 {
13235 new_selections.push(offset_range.start..offset_range.end);
13236 }
13237 }
13238
13239 select_next_state.done = true;
13240 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13241 self.change_selections(None, window, cx, |selections| {
13242 selections.select_ranges(new_selections)
13243 });
13244
13245 Ok(())
13246 }
13247
13248 pub fn select_next(
13249 &mut self,
13250 action: &SelectNext,
13251 window: &mut Window,
13252 cx: &mut Context<Self>,
13253 ) -> Result<()> {
13254 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13255 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13256 self.select_next_match_internal(
13257 &display_map,
13258 action.replace_newest,
13259 Some(Autoscroll::newest()),
13260 window,
13261 cx,
13262 )?;
13263 Ok(())
13264 }
13265
13266 pub fn select_previous(
13267 &mut self,
13268 action: &SelectPrevious,
13269 window: &mut Window,
13270 cx: &mut Context<Self>,
13271 ) -> Result<()> {
13272 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13273 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13274 let buffer = &display_map.buffer_snapshot;
13275 let mut selections = self.selections.all::<usize>(cx);
13276 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13277 let query = &select_prev_state.query;
13278 if !select_prev_state.done {
13279 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13280 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13281 let mut next_selected_range = None;
13282 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13283 let bytes_before_last_selection =
13284 buffer.reversed_bytes_in_range(0..last_selection.start);
13285 let bytes_after_first_selection =
13286 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13287 let query_matches = query
13288 .stream_find_iter(bytes_before_last_selection)
13289 .map(|result| (last_selection.start, result))
13290 .chain(
13291 query
13292 .stream_find_iter(bytes_after_first_selection)
13293 .map(|result| (buffer.len(), result)),
13294 );
13295 for (end_offset, query_match) in query_matches {
13296 let query_match = query_match.unwrap(); // can only fail due to I/O
13297 let offset_range =
13298 end_offset - query_match.end()..end_offset - query_match.start();
13299 let display_range = offset_range.start.to_display_point(&display_map)
13300 ..offset_range.end.to_display_point(&display_map);
13301
13302 if !select_prev_state.wordwise
13303 || (!movement::is_inside_word(&display_map, display_range.start)
13304 && !movement::is_inside_word(&display_map, display_range.end))
13305 {
13306 next_selected_range = Some(offset_range);
13307 break;
13308 }
13309 }
13310
13311 if let Some(next_selected_range) = next_selected_range {
13312 self.select_match_ranges(
13313 next_selected_range,
13314 last_selection.reversed,
13315 action.replace_newest,
13316 Some(Autoscroll::newest()),
13317 window,
13318 cx,
13319 );
13320 } else {
13321 select_prev_state.done = true;
13322 }
13323 }
13324
13325 self.select_prev_state = Some(select_prev_state);
13326 } else {
13327 let mut only_carets = true;
13328 let mut same_text_selected = true;
13329 let mut selected_text = None;
13330
13331 let mut selections_iter = selections.iter().peekable();
13332 while let Some(selection) = selections_iter.next() {
13333 if selection.start != selection.end {
13334 only_carets = false;
13335 }
13336
13337 if same_text_selected {
13338 if selected_text.is_none() {
13339 selected_text =
13340 Some(buffer.text_for_range(selection.range()).collect::<String>());
13341 }
13342
13343 if let Some(next_selection) = selections_iter.peek() {
13344 if next_selection.range().len() == selection.range().len() {
13345 let next_selected_text = buffer
13346 .text_for_range(next_selection.range())
13347 .collect::<String>();
13348 if Some(next_selected_text) != selected_text {
13349 same_text_selected = false;
13350 selected_text = None;
13351 }
13352 } else {
13353 same_text_selected = false;
13354 selected_text = None;
13355 }
13356 }
13357 }
13358 }
13359
13360 if only_carets {
13361 for selection in &mut selections {
13362 let word_range = movement::surrounding_word(
13363 &display_map,
13364 selection.start.to_display_point(&display_map),
13365 );
13366 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13367 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13368 selection.goal = SelectionGoal::None;
13369 selection.reversed = false;
13370 self.select_match_ranges(
13371 selection.start..selection.end,
13372 selection.reversed,
13373 action.replace_newest,
13374 Some(Autoscroll::newest()),
13375 window,
13376 cx,
13377 );
13378 }
13379 if selections.len() == 1 {
13380 let selection = selections
13381 .last()
13382 .expect("ensured that there's only one selection");
13383 let query = buffer
13384 .text_for_range(selection.start..selection.end)
13385 .collect::<String>();
13386 let is_empty = query.is_empty();
13387 let select_state = SelectNextState {
13388 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13389 wordwise: true,
13390 done: is_empty,
13391 };
13392 self.select_prev_state = Some(select_state);
13393 } else {
13394 self.select_prev_state = None;
13395 }
13396 } else if let Some(selected_text) = selected_text {
13397 self.select_prev_state = Some(SelectNextState {
13398 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13399 wordwise: false,
13400 done: false,
13401 });
13402 self.select_previous(action, window, cx)?;
13403 }
13404 }
13405 Ok(())
13406 }
13407
13408 pub fn find_next_match(
13409 &mut self,
13410 _: &FindNextMatch,
13411 window: &mut Window,
13412 cx: &mut Context<Self>,
13413 ) -> Result<()> {
13414 let selections = self.selections.disjoint_anchors();
13415 match selections.first() {
13416 Some(first) if selections.len() >= 2 => {
13417 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13418 s.select_ranges([first.range()]);
13419 });
13420 }
13421 _ => self.select_next(
13422 &SelectNext {
13423 replace_newest: true,
13424 },
13425 window,
13426 cx,
13427 )?,
13428 }
13429 Ok(())
13430 }
13431
13432 pub fn find_previous_match(
13433 &mut self,
13434 _: &FindPreviousMatch,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) -> Result<()> {
13438 let selections = self.selections.disjoint_anchors();
13439 match selections.last() {
13440 Some(last) if selections.len() >= 2 => {
13441 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13442 s.select_ranges([last.range()]);
13443 });
13444 }
13445 _ => self.select_previous(
13446 &SelectPrevious {
13447 replace_newest: true,
13448 },
13449 window,
13450 cx,
13451 )?,
13452 }
13453 Ok(())
13454 }
13455
13456 pub fn toggle_comments(
13457 &mut self,
13458 action: &ToggleComments,
13459 window: &mut Window,
13460 cx: &mut Context<Self>,
13461 ) {
13462 if self.read_only(cx) {
13463 return;
13464 }
13465 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13466 let text_layout_details = &self.text_layout_details(window);
13467 self.transact(window, cx, |this, window, cx| {
13468 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13469 let mut edits = Vec::new();
13470 let mut selection_edit_ranges = Vec::new();
13471 let mut last_toggled_row = None;
13472 let snapshot = this.buffer.read(cx).read(cx);
13473 let empty_str: Arc<str> = Arc::default();
13474 let mut suffixes_inserted = Vec::new();
13475 let ignore_indent = action.ignore_indent;
13476
13477 fn comment_prefix_range(
13478 snapshot: &MultiBufferSnapshot,
13479 row: MultiBufferRow,
13480 comment_prefix: &str,
13481 comment_prefix_whitespace: &str,
13482 ignore_indent: bool,
13483 ) -> Range<Point> {
13484 let indent_size = if ignore_indent {
13485 0
13486 } else {
13487 snapshot.indent_size_for_line(row).len
13488 };
13489
13490 let start = Point::new(row.0, indent_size);
13491
13492 let mut line_bytes = snapshot
13493 .bytes_in_range(start..snapshot.max_point())
13494 .flatten()
13495 .copied();
13496
13497 // If this line currently begins with the line comment prefix, then record
13498 // the range containing the prefix.
13499 if line_bytes
13500 .by_ref()
13501 .take(comment_prefix.len())
13502 .eq(comment_prefix.bytes())
13503 {
13504 // Include any whitespace that matches the comment prefix.
13505 let matching_whitespace_len = line_bytes
13506 .zip(comment_prefix_whitespace.bytes())
13507 .take_while(|(a, b)| a == b)
13508 .count() as u32;
13509 let end = Point::new(
13510 start.row,
13511 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13512 );
13513 start..end
13514 } else {
13515 start..start
13516 }
13517 }
13518
13519 fn comment_suffix_range(
13520 snapshot: &MultiBufferSnapshot,
13521 row: MultiBufferRow,
13522 comment_suffix: &str,
13523 comment_suffix_has_leading_space: bool,
13524 ) -> Range<Point> {
13525 let end = Point::new(row.0, snapshot.line_len(row));
13526 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13527
13528 let mut line_end_bytes = snapshot
13529 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13530 .flatten()
13531 .copied();
13532
13533 let leading_space_len = if suffix_start_column > 0
13534 && line_end_bytes.next() == Some(b' ')
13535 && comment_suffix_has_leading_space
13536 {
13537 1
13538 } else {
13539 0
13540 };
13541
13542 // If this line currently begins with the line comment prefix, then record
13543 // the range containing the prefix.
13544 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13545 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13546 start..end
13547 } else {
13548 end..end
13549 }
13550 }
13551
13552 // TODO: Handle selections that cross excerpts
13553 for selection in &mut selections {
13554 let start_column = snapshot
13555 .indent_size_for_line(MultiBufferRow(selection.start.row))
13556 .len;
13557 let language = if let Some(language) =
13558 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13559 {
13560 language
13561 } else {
13562 continue;
13563 };
13564
13565 selection_edit_ranges.clear();
13566
13567 // If multiple selections contain a given row, avoid processing that
13568 // row more than once.
13569 let mut start_row = MultiBufferRow(selection.start.row);
13570 if last_toggled_row == Some(start_row) {
13571 start_row = start_row.next_row();
13572 }
13573 let end_row =
13574 if selection.end.row > selection.start.row && selection.end.column == 0 {
13575 MultiBufferRow(selection.end.row - 1)
13576 } else {
13577 MultiBufferRow(selection.end.row)
13578 };
13579 last_toggled_row = Some(end_row);
13580
13581 if start_row > end_row {
13582 continue;
13583 }
13584
13585 // If the language has line comments, toggle those.
13586 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13587
13588 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13589 if ignore_indent {
13590 full_comment_prefixes = full_comment_prefixes
13591 .into_iter()
13592 .map(|s| Arc::from(s.trim_end()))
13593 .collect();
13594 }
13595
13596 if !full_comment_prefixes.is_empty() {
13597 let first_prefix = full_comment_prefixes
13598 .first()
13599 .expect("prefixes is non-empty");
13600 let prefix_trimmed_lengths = full_comment_prefixes
13601 .iter()
13602 .map(|p| p.trim_end_matches(' ').len())
13603 .collect::<SmallVec<[usize; 4]>>();
13604
13605 let mut all_selection_lines_are_comments = true;
13606
13607 for row in start_row.0..=end_row.0 {
13608 let row = MultiBufferRow(row);
13609 if start_row < end_row && snapshot.is_line_blank(row) {
13610 continue;
13611 }
13612
13613 let prefix_range = full_comment_prefixes
13614 .iter()
13615 .zip(prefix_trimmed_lengths.iter().copied())
13616 .map(|(prefix, trimmed_prefix_len)| {
13617 comment_prefix_range(
13618 snapshot.deref(),
13619 row,
13620 &prefix[..trimmed_prefix_len],
13621 &prefix[trimmed_prefix_len..],
13622 ignore_indent,
13623 )
13624 })
13625 .max_by_key(|range| range.end.column - range.start.column)
13626 .expect("prefixes is non-empty");
13627
13628 if prefix_range.is_empty() {
13629 all_selection_lines_are_comments = false;
13630 }
13631
13632 selection_edit_ranges.push(prefix_range);
13633 }
13634
13635 if all_selection_lines_are_comments {
13636 edits.extend(
13637 selection_edit_ranges
13638 .iter()
13639 .cloned()
13640 .map(|range| (range, empty_str.clone())),
13641 );
13642 } else {
13643 let min_column = selection_edit_ranges
13644 .iter()
13645 .map(|range| range.start.column)
13646 .min()
13647 .unwrap_or(0);
13648 edits.extend(selection_edit_ranges.iter().map(|range| {
13649 let position = Point::new(range.start.row, min_column);
13650 (position..position, first_prefix.clone())
13651 }));
13652 }
13653 } else if let Some((full_comment_prefix, comment_suffix)) =
13654 language.block_comment_delimiters()
13655 {
13656 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13657 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13658 let prefix_range = comment_prefix_range(
13659 snapshot.deref(),
13660 start_row,
13661 comment_prefix,
13662 comment_prefix_whitespace,
13663 ignore_indent,
13664 );
13665 let suffix_range = comment_suffix_range(
13666 snapshot.deref(),
13667 end_row,
13668 comment_suffix.trim_start_matches(' '),
13669 comment_suffix.starts_with(' '),
13670 );
13671
13672 if prefix_range.is_empty() || suffix_range.is_empty() {
13673 edits.push((
13674 prefix_range.start..prefix_range.start,
13675 full_comment_prefix.clone(),
13676 ));
13677 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13678 suffixes_inserted.push((end_row, comment_suffix.len()));
13679 } else {
13680 edits.push((prefix_range, empty_str.clone()));
13681 edits.push((suffix_range, empty_str.clone()));
13682 }
13683 } else {
13684 continue;
13685 }
13686 }
13687
13688 drop(snapshot);
13689 this.buffer.update(cx, |buffer, cx| {
13690 buffer.edit(edits, None, cx);
13691 });
13692
13693 // Adjust selections so that they end before any comment suffixes that
13694 // were inserted.
13695 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13696 let mut selections = this.selections.all::<Point>(cx);
13697 let snapshot = this.buffer.read(cx).read(cx);
13698 for selection in &mut selections {
13699 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13700 match row.cmp(&MultiBufferRow(selection.end.row)) {
13701 Ordering::Less => {
13702 suffixes_inserted.next();
13703 continue;
13704 }
13705 Ordering::Greater => break,
13706 Ordering::Equal => {
13707 if selection.end.column == snapshot.line_len(row) {
13708 if selection.is_empty() {
13709 selection.start.column -= suffix_len as u32;
13710 }
13711 selection.end.column -= suffix_len as u32;
13712 }
13713 break;
13714 }
13715 }
13716 }
13717 }
13718
13719 drop(snapshot);
13720 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13721 s.select(selections)
13722 });
13723
13724 let selections = this.selections.all::<Point>(cx);
13725 let selections_on_single_row = selections.windows(2).all(|selections| {
13726 selections[0].start.row == selections[1].start.row
13727 && selections[0].end.row == selections[1].end.row
13728 && selections[0].start.row == selections[0].end.row
13729 });
13730 let selections_selecting = selections
13731 .iter()
13732 .any(|selection| selection.start != selection.end);
13733 let advance_downwards = action.advance_downwards
13734 && selections_on_single_row
13735 && !selections_selecting
13736 && !matches!(this.mode, EditorMode::SingleLine { .. });
13737
13738 if advance_downwards {
13739 let snapshot = this.buffer.read(cx).snapshot(cx);
13740
13741 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13742 s.move_cursors_with(|display_snapshot, display_point, _| {
13743 let mut point = display_point.to_point(display_snapshot);
13744 point.row += 1;
13745 point = snapshot.clip_point(point, Bias::Left);
13746 let display_point = point.to_display_point(display_snapshot);
13747 let goal = SelectionGoal::HorizontalPosition(
13748 display_snapshot
13749 .x_for_display_point(display_point, text_layout_details)
13750 .into(),
13751 );
13752 (display_point, goal)
13753 })
13754 });
13755 }
13756 });
13757 }
13758
13759 pub fn select_enclosing_symbol(
13760 &mut self,
13761 _: &SelectEnclosingSymbol,
13762 window: &mut Window,
13763 cx: &mut Context<Self>,
13764 ) {
13765 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13766
13767 let buffer = self.buffer.read(cx).snapshot(cx);
13768 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13769
13770 fn update_selection(
13771 selection: &Selection<usize>,
13772 buffer_snap: &MultiBufferSnapshot,
13773 ) -> Option<Selection<usize>> {
13774 let cursor = selection.head();
13775 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13776 for symbol in symbols.iter().rev() {
13777 let start = symbol.range.start.to_offset(buffer_snap);
13778 let end = symbol.range.end.to_offset(buffer_snap);
13779 let new_range = start..end;
13780 if start < selection.start || end > selection.end {
13781 return Some(Selection {
13782 id: selection.id,
13783 start: new_range.start,
13784 end: new_range.end,
13785 goal: SelectionGoal::None,
13786 reversed: selection.reversed,
13787 });
13788 }
13789 }
13790 None
13791 }
13792
13793 let mut selected_larger_symbol = false;
13794 let new_selections = old_selections
13795 .iter()
13796 .map(|selection| match update_selection(selection, &buffer) {
13797 Some(new_selection) => {
13798 if new_selection.range() != selection.range() {
13799 selected_larger_symbol = true;
13800 }
13801 new_selection
13802 }
13803 None => selection.clone(),
13804 })
13805 .collect::<Vec<_>>();
13806
13807 if selected_larger_symbol {
13808 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13809 s.select(new_selections);
13810 });
13811 }
13812 }
13813
13814 pub fn select_larger_syntax_node(
13815 &mut self,
13816 _: &SelectLargerSyntaxNode,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) {
13820 let Some(visible_row_count) = self.visible_row_count() else {
13821 return;
13822 };
13823 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13824 if old_selections.is_empty() {
13825 return;
13826 }
13827
13828 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13829
13830 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13831 let buffer = self.buffer.read(cx).snapshot(cx);
13832
13833 let mut selected_larger_node = false;
13834 let mut new_selections = old_selections
13835 .iter()
13836 .map(|selection| {
13837 let old_range = selection.start..selection.end;
13838
13839 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13840 // manually select word at selection
13841 if ["string_content", "inline"].contains(&node.kind()) {
13842 let word_range = {
13843 let display_point = buffer
13844 .offset_to_point(old_range.start)
13845 .to_display_point(&display_map);
13846 let Range { start, end } =
13847 movement::surrounding_word(&display_map, display_point);
13848 start.to_point(&display_map).to_offset(&buffer)
13849 ..end.to_point(&display_map).to_offset(&buffer)
13850 };
13851 // ignore if word is already selected
13852 if !word_range.is_empty() && old_range != word_range {
13853 let last_word_range = {
13854 let display_point = buffer
13855 .offset_to_point(old_range.end)
13856 .to_display_point(&display_map);
13857 let Range { start, end } =
13858 movement::surrounding_word(&display_map, display_point);
13859 start.to_point(&display_map).to_offset(&buffer)
13860 ..end.to_point(&display_map).to_offset(&buffer)
13861 };
13862 // only select word if start and end point belongs to same word
13863 if word_range == last_word_range {
13864 selected_larger_node = true;
13865 return Selection {
13866 id: selection.id,
13867 start: word_range.start,
13868 end: word_range.end,
13869 goal: SelectionGoal::None,
13870 reversed: selection.reversed,
13871 };
13872 }
13873 }
13874 }
13875 }
13876
13877 let mut new_range = old_range.clone();
13878 while let Some((_node, containing_range)) =
13879 buffer.syntax_ancestor(new_range.clone())
13880 {
13881 new_range = match containing_range {
13882 MultiOrSingleBufferOffsetRange::Single(_) => break,
13883 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13884 };
13885 if !display_map.intersects_fold(new_range.start)
13886 && !display_map.intersects_fold(new_range.end)
13887 {
13888 break;
13889 }
13890 }
13891
13892 selected_larger_node |= new_range != old_range;
13893 Selection {
13894 id: selection.id,
13895 start: new_range.start,
13896 end: new_range.end,
13897 goal: SelectionGoal::None,
13898 reversed: selection.reversed,
13899 }
13900 })
13901 .collect::<Vec<_>>();
13902
13903 if !selected_larger_node {
13904 return; // don't put this call in the history
13905 }
13906
13907 // scroll based on transformation done to the last selection created by the user
13908 let (last_old, last_new) = old_selections
13909 .last()
13910 .zip(new_selections.last().cloned())
13911 .expect("old_selections isn't empty");
13912
13913 // revert selection
13914 let is_selection_reversed = {
13915 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13916 new_selections.last_mut().expect("checked above").reversed =
13917 should_newest_selection_be_reversed;
13918 should_newest_selection_be_reversed
13919 };
13920
13921 if selected_larger_node {
13922 self.select_syntax_node_history.disable_clearing = true;
13923 self.change_selections(None, window, cx, |s| {
13924 s.select(new_selections.clone());
13925 });
13926 self.select_syntax_node_history.disable_clearing = false;
13927 }
13928
13929 let start_row = last_new.start.to_display_point(&display_map).row().0;
13930 let end_row = last_new.end.to_display_point(&display_map).row().0;
13931 let selection_height = end_row - start_row + 1;
13932 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13933
13934 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13935 let scroll_behavior = if fits_on_the_screen {
13936 self.request_autoscroll(Autoscroll::fit(), cx);
13937 SelectSyntaxNodeScrollBehavior::FitSelection
13938 } else if is_selection_reversed {
13939 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13940 SelectSyntaxNodeScrollBehavior::CursorTop
13941 } else {
13942 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13943 SelectSyntaxNodeScrollBehavior::CursorBottom
13944 };
13945
13946 self.select_syntax_node_history.push((
13947 old_selections,
13948 scroll_behavior,
13949 is_selection_reversed,
13950 ));
13951 }
13952
13953 pub fn select_smaller_syntax_node(
13954 &mut self,
13955 _: &SelectSmallerSyntaxNode,
13956 window: &mut Window,
13957 cx: &mut Context<Self>,
13958 ) {
13959 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13960
13961 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13962 self.select_syntax_node_history.pop()
13963 {
13964 if let Some(selection) = selections.last_mut() {
13965 selection.reversed = is_selection_reversed;
13966 }
13967
13968 self.select_syntax_node_history.disable_clearing = true;
13969 self.change_selections(None, window, cx, |s| {
13970 s.select(selections.to_vec());
13971 });
13972 self.select_syntax_node_history.disable_clearing = false;
13973
13974 match scroll_behavior {
13975 SelectSyntaxNodeScrollBehavior::CursorTop => {
13976 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13977 }
13978 SelectSyntaxNodeScrollBehavior::FitSelection => {
13979 self.request_autoscroll(Autoscroll::fit(), cx);
13980 }
13981 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13982 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13983 }
13984 }
13985 }
13986 }
13987
13988 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13989 if !EditorSettings::get_global(cx).gutter.runnables {
13990 self.clear_tasks();
13991 return Task::ready(());
13992 }
13993 let project = self.project.as_ref().map(Entity::downgrade);
13994 let task_sources = self.lsp_task_sources(cx);
13995 let multi_buffer = self.buffer.downgrade();
13996 cx.spawn_in(window, async move |editor, cx| {
13997 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13998 let Some(project) = project.and_then(|p| p.upgrade()) else {
13999 return;
14000 };
14001 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14002 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14003 }) else {
14004 return;
14005 };
14006
14007 let hide_runnables = project
14008 .update(cx, |project, cx| {
14009 // Do not display any test indicators in non-dev server remote projects.
14010 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14011 })
14012 .unwrap_or(true);
14013 if hide_runnables {
14014 return;
14015 }
14016 let new_rows =
14017 cx.background_spawn({
14018 let snapshot = display_snapshot.clone();
14019 async move {
14020 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14021 }
14022 })
14023 .await;
14024 let Ok(lsp_tasks) =
14025 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14026 else {
14027 return;
14028 };
14029 let lsp_tasks = lsp_tasks.await;
14030
14031 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14032 lsp_tasks
14033 .into_iter()
14034 .flat_map(|(kind, tasks)| {
14035 tasks.into_iter().filter_map(move |(location, task)| {
14036 Some((kind.clone(), location?, task))
14037 })
14038 })
14039 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14040 let buffer = location.target.buffer;
14041 let buffer_snapshot = buffer.read(cx).snapshot();
14042 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14043 |(excerpt_id, snapshot, _)| {
14044 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14045 display_snapshot
14046 .buffer_snapshot
14047 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14048 } else {
14049 None
14050 }
14051 },
14052 );
14053 if let Some(offset) = offset {
14054 let task_buffer_range =
14055 location.target.range.to_point(&buffer_snapshot);
14056 let context_buffer_range =
14057 task_buffer_range.to_offset(&buffer_snapshot);
14058 let context_range = BufferOffset(context_buffer_range.start)
14059 ..BufferOffset(context_buffer_range.end);
14060
14061 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14062 .or_insert_with(|| RunnableTasks {
14063 templates: Vec::new(),
14064 offset,
14065 column: task_buffer_range.start.column,
14066 extra_variables: HashMap::default(),
14067 context_range,
14068 })
14069 .templates
14070 .push((kind, task.original_task().clone()));
14071 }
14072
14073 acc
14074 })
14075 }) else {
14076 return;
14077 };
14078
14079 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14080 buffer.language_settings(cx).tasks.prefer_lsp
14081 }) else {
14082 return;
14083 };
14084
14085 let rows = Self::runnable_rows(
14086 project,
14087 display_snapshot,
14088 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14089 new_rows,
14090 cx.clone(),
14091 )
14092 .await;
14093 editor
14094 .update(cx, |editor, _| {
14095 editor.clear_tasks();
14096 for (key, mut value) in rows {
14097 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14098 value.templates.extend(lsp_tasks.templates);
14099 }
14100
14101 editor.insert_tasks(key, value);
14102 }
14103 for (key, value) in lsp_tasks_by_rows {
14104 editor.insert_tasks(key, value);
14105 }
14106 })
14107 .ok();
14108 })
14109 }
14110 fn fetch_runnable_ranges(
14111 snapshot: &DisplaySnapshot,
14112 range: Range<Anchor>,
14113 ) -> Vec<language::RunnableRange> {
14114 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14115 }
14116
14117 fn runnable_rows(
14118 project: Entity<Project>,
14119 snapshot: DisplaySnapshot,
14120 prefer_lsp: bool,
14121 runnable_ranges: Vec<RunnableRange>,
14122 cx: AsyncWindowContext,
14123 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14124 cx.spawn(async move |cx| {
14125 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14126 for mut runnable in runnable_ranges {
14127 let Some(tasks) = cx
14128 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14129 .ok()
14130 else {
14131 continue;
14132 };
14133 let mut tasks = tasks.await;
14134
14135 if prefer_lsp {
14136 tasks.retain(|(task_kind, _)| {
14137 !matches!(task_kind, TaskSourceKind::Language { .. })
14138 });
14139 }
14140 if tasks.is_empty() {
14141 continue;
14142 }
14143
14144 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14145 let Some(row) = snapshot
14146 .buffer_snapshot
14147 .buffer_line_for_row(MultiBufferRow(point.row))
14148 .map(|(_, range)| range.start.row)
14149 else {
14150 continue;
14151 };
14152
14153 let context_range =
14154 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14155 runnable_rows.push((
14156 (runnable.buffer_id, row),
14157 RunnableTasks {
14158 templates: tasks,
14159 offset: snapshot
14160 .buffer_snapshot
14161 .anchor_before(runnable.run_range.start),
14162 context_range,
14163 column: point.column,
14164 extra_variables: runnable.extra_captures,
14165 },
14166 ));
14167 }
14168 runnable_rows
14169 })
14170 }
14171
14172 fn templates_with_tags(
14173 project: &Entity<Project>,
14174 runnable: &mut Runnable,
14175 cx: &mut App,
14176 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14177 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14178 let (worktree_id, file) = project
14179 .buffer_for_id(runnable.buffer, cx)
14180 .and_then(|buffer| buffer.read(cx).file())
14181 .map(|file| (file.worktree_id(cx), file.clone()))
14182 .unzip();
14183
14184 (
14185 project.task_store().read(cx).task_inventory().cloned(),
14186 worktree_id,
14187 file,
14188 )
14189 });
14190
14191 let tags = mem::take(&mut runnable.tags);
14192 let language = runnable.language.clone();
14193 cx.spawn(async move |cx| {
14194 let mut templates_with_tags = Vec::new();
14195 if let Some(inventory) = inventory {
14196 for RunnableTag(tag) in tags {
14197 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14198 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14199 }) else {
14200 return templates_with_tags;
14201 };
14202 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14203 move |(_, template)| {
14204 template.tags.iter().any(|source_tag| source_tag == &tag)
14205 },
14206 ));
14207 }
14208 }
14209 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14210
14211 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14212 // Strongest source wins; if we have worktree tag binding, prefer that to
14213 // global and language bindings;
14214 // if we have a global binding, prefer that to language binding.
14215 let first_mismatch = templates_with_tags
14216 .iter()
14217 .position(|(tag_source, _)| tag_source != leading_tag_source);
14218 if let Some(index) = first_mismatch {
14219 templates_with_tags.truncate(index);
14220 }
14221 }
14222
14223 templates_with_tags
14224 })
14225 }
14226
14227 pub fn move_to_enclosing_bracket(
14228 &mut self,
14229 _: &MoveToEnclosingBracket,
14230 window: &mut Window,
14231 cx: &mut Context<Self>,
14232 ) {
14233 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14234 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14235 s.move_offsets_with(|snapshot, selection| {
14236 let Some(enclosing_bracket_ranges) =
14237 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14238 else {
14239 return;
14240 };
14241
14242 let mut best_length = usize::MAX;
14243 let mut best_inside = false;
14244 let mut best_in_bracket_range = false;
14245 let mut best_destination = None;
14246 for (open, close) in enclosing_bracket_ranges {
14247 let close = close.to_inclusive();
14248 let length = close.end() - open.start;
14249 let inside = selection.start >= open.end && selection.end <= *close.start();
14250 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14251 || close.contains(&selection.head());
14252
14253 // If best is next to a bracket and current isn't, skip
14254 if !in_bracket_range && best_in_bracket_range {
14255 continue;
14256 }
14257
14258 // Prefer smaller lengths unless best is inside and current isn't
14259 if length > best_length && (best_inside || !inside) {
14260 continue;
14261 }
14262
14263 best_length = length;
14264 best_inside = inside;
14265 best_in_bracket_range = in_bracket_range;
14266 best_destination = Some(
14267 if close.contains(&selection.start) && close.contains(&selection.end) {
14268 if inside { open.end } else { open.start }
14269 } else if inside {
14270 *close.start()
14271 } else {
14272 *close.end()
14273 },
14274 );
14275 }
14276
14277 if let Some(destination) = best_destination {
14278 selection.collapse_to(destination, SelectionGoal::None);
14279 }
14280 })
14281 });
14282 }
14283
14284 pub fn undo_selection(
14285 &mut self,
14286 _: &UndoSelection,
14287 window: &mut Window,
14288 cx: &mut Context<Self>,
14289 ) {
14290 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14291 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14292 self.selection_history.mode = SelectionHistoryMode::Undoing;
14293 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14294 this.end_selection(window, cx);
14295 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14296 s.select_anchors(entry.selections.to_vec())
14297 });
14298 });
14299 self.selection_history.mode = SelectionHistoryMode::Normal;
14300
14301 self.select_next_state = entry.select_next_state;
14302 self.select_prev_state = entry.select_prev_state;
14303 self.add_selections_state = entry.add_selections_state;
14304 }
14305 }
14306
14307 pub fn redo_selection(
14308 &mut self,
14309 _: &RedoSelection,
14310 window: &mut Window,
14311 cx: &mut Context<Self>,
14312 ) {
14313 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14314 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14315 self.selection_history.mode = SelectionHistoryMode::Redoing;
14316 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14317 this.end_selection(window, cx);
14318 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14319 s.select_anchors(entry.selections.to_vec())
14320 });
14321 });
14322 self.selection_history.mode = SelectionHistoryMode::Normal;
14323
14324 self.select_next_state = entry.select_next_state;
14325 self.select_prev_state = entry.select_prev_state;
14326 self.add_selections_state = entry.add_selections_state;
14327 }
14328 }
14329
14330 pub fn expand_excerpts(
14331 &mut self,
14332 action: &ExpandExcerpts,
14333 _: &mut Window,
14334 cx: &mut Context<Self>,
14335 ) {
14336 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14337 }
14338
14339 pub fn expand_excerpts_down(
14340 &mut self,
14341 action: &ExpandExcerptsDown,
14342 _: &mut Window,
14343 cx: &mut Context<Self>,
14344 ) {
14345 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14346 }
14347
14348 pub fn expand_excerpts_up(
14349 &mut self,
14350 action: &ExpandExcerptsUp,
14351 _: &mut Window,
14352 cx: &mut Context<Self>,
14353 ) {
14354 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14355 }
14356
14357 pub fn expand_excerpts_for_direction(
14358 &mut self,
14359 lines: u32,
14360 direction: ExpandExcerptDirection,
14361
14362 cx: &mut Context<Self>,
14363 ) {
14364 let selections = self.selections.disjoint_anchors();
14365
14366 let lines = if lines == 0 {
14367 EditorSettings::get_global(cx).expand_excerpt_lines
14368 } else {
14369 lines
14370 };
14371
14372 self.buffer.update(cx, |buffer, cx| {
14373 let snapshot = buffer.snapshot(cx);
14374 let mut excerpt_ids = selections
14375 .iter()
14376 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14377 .collect::<Vec<_>>();
14378 excerpt_ids.sort();
14379 excerpt_ids.dedup();
14380 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14381 })
14382 }
14383
14384 pub fn expand_excerpt(
14385 &mut self,
14386 excerpt: ExcerptId,
14387 direction: ExpandExcerptDirection,
14388 window: &mut Window,
14389 cx: &mut Context<Self>,
14390 ) {
14391 let current_scroll_position = self.scroll_position(cx);
14392 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14393 let mut should_scroll_up = false;
14394
14395 if direction == ExpandExcerptDirection::Down {
14396 let multi_buffer = self.buffer.read(cx);
14397 let snapshot = multi_buffer.snapshot(cx);
14398 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14399 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14400 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14401 let buffer_snapshot = buffer.read(cx).snapshot();
14402 let excerpt_end_row =
14403 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14404 let last_row = buffer_snapshot.max_point().row;
14405 let lines_below = last_row.saturating_sub(excerpt_end_row);
14406 should_scroll_up = lines_below >= lines_to_expand;
14407 }
14408 }
14409 }
14410 }
14411
14412 self.buffer.update(cx, |buffer, cx| {
14413 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14414 });
14415
14416 if should_scroll_up {
14417 let new_scroll_position =
14418 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14419 self.set_scroll_position(new_scroll_position, window, cx);
14420 }
14421 }
14422
14423 pub fn go_to_singleton_buffer_point(
14424 &mut self,
14425 point: Point,
14426 window: &mut Window,
14427 cx: &mut Context<Self>,
14428 ) {
14429 self.go_to_singleton_buffer_range(point..point, window, cx);
14430 }
14431
14432 pub fn go_to_singleton_buffer_range(
14433 &mut self,
14434 range: Range<Point>,
14435 window: &mut Window,
14436 cx: &mut Context<Self>,
14437 ) {
14438 let multibuffer = self.buffer().read(cx);
14439 let Some(buffer) = multibuffer.as_singleton() else {
14440 return;
14441 };
14442 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14443 return;
14444 };
14445 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14446 return;
14447 };
14448 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14449 s.select_anchor_ranges([start..end])
14450 });
14451 }
14452
14453 pub fn go_to_diagnostic(
14454 &mut self,
14455 _: &GoToDiagnostic,
14456 window: &mut Window,
14457 cx: &mut Context<Self>,
14458 ) {
14459 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14460 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14461 }
14462
14463 pub fn go_to_prev_diagnostic(
14464 &mut self,
14465 _: &GoToPreviousDiagnostic,
14466 window: &mut Window,
14467 cx: &mut Context<Self>,
14468 ) {
14469 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14470 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14471 }
14472
14473 pub fn go_to_diagnostic_impl(
14474 &mut self,
14475 direction: Direction,
14476 window: &mut Window,
14477 cx: &mut Context<Self>,
14478 ) {
14479 let buffer = self.buffer.read(cx).snapshot(cx);
14480 let selection = self.selections.newest::<usize>(cx);
14481
14482 let mut active_group_id = None;
14483 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14484 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14485 active_group_id = Some(active_group.group_id);
14486 }
14487 }
14488
14489 fn filtered(
14490 snapshot: EditorSnapshot,
14491 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14492 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14493 diagnostics
14494 .filter(|entry| entry.range.start != entry.range.end)
14495 .filter(|entry| !entry.diagnostic.is_unnecessary)
14496 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14497 }
14498
14499 let snapshot = self.snapshot(window, cx);
14500 let before = filtered(
14501 snapshot.clone(),
14502 buffer
14503 .diagnostics_in_range(0..selection.start)
14504 .filter(|entry| entry.range.start <= selection.start),
14505 );
14506 let after = filtered(
14507 snapshot,
14508 buffer
14509 .diagnostics_in_range(selection.start..buffer.len())
14510 .filter(|entry| entry.range.start >= selection.start),
14511 );
14512
14513 let mut found: Option<DiagnosticEntry<usize>> = None;
14514 if direction == Direction::Prev {
14515 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14516 {
14517 for diagnostic in prev_diagnostics.into_iter().rev() {
14518 if diagnostic.range.start != selection.start
14519 || active_group_id
14520 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14521 {
14522 found = Some(diagnostic);
14523 break 'outer;
14524 }
14525 }
14526 }
14527 } else {
14528 for diagnostic in after.chain(before) {
14529 if diagnostic.range.start != selection.start
14530 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14531 {
14532 found = Some(diagnostic);
14533 break;
14534 }
14535 }
14536 }
14537 let Some(next_diagnostic) = found else {
14538 return;
14539 };
14540
14541 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14542 return;
14543 };
14544 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14545 s.select_ranges(vec![
14546 next_diagnostic.range.start..next_diagnostic.range.start,
14547 ])
14548 });
14549 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14550 self.refresh_inline_completion(false, true, window, cx);
14551 }
14552
14553 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14554 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14555 let snapshot = self.snapshot(window, cx);
14556 let selection = self.selections.newest::<Point>(cx);
14557 self.go_to_hunk_before_or_after_position(
14558 &snapshot,
14559 selection.head(),
14560 Direction::Next,
14561 window,
14562 cx,
14563 );
14564 }
14565
14566 pub fn go_to_hunk_before_or_after_position(
14567 &mut self,
14568 snapshot: &EditorSnapshot,
14569 position: Point,
14570 direction: Direction,
14571 window: &mut Window,
14572 cx: &mut Context<Editor>,
14573 ) {
14574 let row = if direction == Direction::Next {
14575 self.hunk_after_position(snapshot, position)
14576 .map(|hunk| hunk.row_range.start)
14577 } else {
14578 self.hunk_before_position(snapshot, position)
14579 };
14580
14581 if let Some(row) = row {
14582 let destination = Point::new(row.0, 0);
14583 let autoscroll = Autoscroll::center();
14584
14585 self.unfold_ranges(&[destination..destination], false, false, cx);
14586 self.change_selections(Some(autoscroll), window, cx, |s| {
14587 s.select_ranges([destination..destination]);
14588 });
14589 }
14590 }
14591
14592 fn hunk_after_position(
14593 &mut self,
14594 snapshot: &EditorSnapshot,
14595 position: Point,
14596 ) -> Option<MultiBufferDiffHunk> {
14597 snapshot
14598 .buffer_snapshot
14599 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14600 .find(|hunk| hunk.row_range.start.0 > position.row)
14601 .or_else(|| {
14602 snapshot
14603 .buffer_snapshot
14604 .diff_hunks_in_range(Point::zero()..position)
14605 .find(|hunk| hunk.row_range.end.0 < position.row)
14606 })
14607 }
14608
14609 fn go_to_prev_hunk(
14610 &mut self,
14611 _: &GoToPreviousHunk,
14612 window: &mut Window,
14613 cx: &mut Context<Self>,
14614 ) {
14615 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14616 let snapshot = self.snapshot(window, cx);
14617 let selection = self.selections.newest::<Point>(cx);
14618 self.go_to_hunk_before_or_after_position(
14619 &snapshot,
14620 selection.head(),
14621 Direction::Prev,
14622 window,
14623 cx,
14624 );
14625 }
14626
14627 fn hunk_before_position(
14628 &mut self,
14629 snapshot: &EditorSnapshot,
14630 position: Point,
14631 ) -> Option<MultiBufferRow> {
14632 snapshot
14633 .buffer_snapshot
14634 .diff_hunk_before(position)
14635 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14636 }
14637
14638 fn go_to_next_change(
14639 &mut self,
14640 _: &GoToNextChange,
14641 window: &mut Window,
14642 cx: &mut Context<Self>,
14643 ) {
14644 if let Some(selections) = self
14645 .change_list
14646 .next_change(1, Direction::Next)
14647 .map(|s| s.to_vec())
14648 {
14649 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14650 let map = s.display_map();
14651 s.select_display_ranges(selections.iter().map(|a| {
14652 let point = a.to_display_point(&map);
14653 point..point
14654 }))
14655 })
14656 }
14657 }
14658
14659 fn go_to_previous_change(
14660 &mut self,
14661 _: &GoToPreviousChange,
14662 window: &mut Window,
14663 cx: &mut Context<Self>,
14664 ) {
14665 if let Some(selections) = self
14666 .change_list
14667 .next_change(1, Direction::Prev)
14668 .map(|s| s.to_vec())
14669 {
14670 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14671 let map = s.display_map();
14672 s.select_display_ranges(selections.iter().map(|a| {
14673 let point = a.to_display_point(&map);
14674 point..point
14675 }))
14676 })
14677 }
14678 }
14679
14680 fn go_to_line<T: 'static>(
14681 &mut self,
14682 position: Anchor,
14683 highlight_color: Option<Hsla>,
14684 window: &mut Window,
14685 cx: &mut Context<Self>,
14686 ) {
14687 let snapshot = self.snapshot(window, cx).display_snapshot;
14688 let position = position.to_point(&snapshot.buffer_snapshot);
14689 let start = snapshot
14690 .buffer_snapshot
14691 .clip_point(Point::new(position.row, 0), Bias::Left);
14692 let end = start + Point::new(1, 0);
14693 let start = snapshot.buffer_snapshot.anchor_before(start);
14694 let end = snapshot.buffer_snapshot.anchor_before(end);
14695
14696 self.highlight_rows::<T>(
14697 start..end,
14698 highlight_color
14699 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14700 Default::default(),
14701 cx,
14702 );
14703
14704 if self.buffer.read(cx).is_singleton() {
14705 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14706 }
14707 }
14708
14709 pub fn go_to_definition(
14710 &mut self,
14711 _: &GoToDefinition,
14712 window: &mut Window,
14713 cx: &mut Context<Self>,
14714 ) -> Task<Result<Navigated>> {
14715 let definition =
14716 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14717 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14718 cx.spawn_in(window, async move |editor, cx| {
14719 if definition.await? == Navigated::Yes {
14720 return Ok(Navigated::Yes);
14721 }
14722 match fallback_strategy {
14723 GoToDefinitionFallback::None => Ok(Navigated::No),
14724 GoToDefinitionFallback::FindAllReferences => {
14725 match editor.update_in(cx, |editor, window, cx| {
14726 editor.find_all_references(&FindAllReferences, window, cx)
14727 })? {
14728 Some(references) => references.await,
14729 None => Ok(Navigated::No),
14730 }
14731 }
14732 }
14733 })
14734 }
14735
14736 pub fn go_to_declaration(
14737 &mut self,
14738 _: &GoToDeclaration,
14739 window: &mut Window,
14740 cx: &mut Context<Self>,
14741 ) -> Task<Result<Navigated>> {
14742 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14743 }
14744
14745 pub fn go_to_declaration_split(
14746 &mut self,
14747 _: &GoToDeclaration,
14748 window: &mut Window,
14749 cx: &mut Context<Self>,
14750 ) -> Task<Result<Navigated>> {
14751 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14752 }
14753
14754 pub fn go_to_implementation(
14755 &mut self,
14756 _: &GoToImplementation,
14757 window: &mut Window,
14758 cx: &mut Context<Self>,
14759 ) -> Task<Result<Navigated>> {
14760 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14761 }
14762
14763 pub fn go_to_implementation_split(
14764 &mut self,
14765 _: &GoToImplementationSplit,
14766 window: &mut Window,
14767 cx: &mut Context<Self>,
14768 ) -> Task<Result<Navigated>> {
14769 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14770 }
14771
14772 pub fn go_to_type_definition(
14773 &mut self,
14774 _: &GoToTypeDefinition,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) -> Task<Result<Navigated>> {
14778 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14779 }
14780
14781 pub fn go_to_definition_split(
14782 &mut self,
14783 _: &GoToDefinitionSplit,
14784 window: &mut Window,
14785 cx: &mut Context<Self>,
14786 ) -> Task<Result<Navigated>> {
14787 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14788 }
14789
14790 pub fn go_to_type_definition_split(
14791 &mut self,
14792 _: &GoToTypeDefinitionSplit,
14793 window: &mut Window,
14794 cx: &mut Context<Self>,
14795 ) -> Task<Result<Navigated>> {
14796 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14797 }
14798
14799 fn go_to_definition_of_kind(
14800 &mut self,
14801 kind: GotoDefinitionKind,
14802 split: bool,
14803 window: &mut Window,
14804 cx: &mut Context<Self>,
14805 ) -> Task<Result<Navigated>> {
14806 let Some(provider) = self.semantics_provider.clone() else {
14807 return Task::ready(Ok(Navigated::No));
14808 };
14809 let head = self.selections.newest::<usize>(cx).head();
14810 let buffer = self.buffer.read(cx);
14811 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14812 text_anchor
14813 } else {
14814 return Task::ready(Ok(Navigated::No));
14815 };
14816
14817 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14818 return Task::ready(Ok(Navigated::No));
14819 };
14820
14821 cx.spawn_in(window, async move |editor, cx| {
14822 let definitions = definitions.await?;
14823 let navigated = editor
14824 .update_in(cx, |editor, window, cx| {
14825 editor.navigate_to_hover_links(
14826 Some(kind),
14827 definitions
14828 .into_iter()
14829 .filter(|location| {
14830 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14831 })
14832 .map(HoverLink::Text)
14833 .collect::<Vec<_>>(),
14834 split,
14835 window,
14836 cx,
14837 )
14838 })?
14839 .await?;
14840 anyhow::Ok(navigated)
14841 })
14842 }
14843
14844 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14845 let selection = self.selections.newest_anchor();
14846 let head = selection.head();
14847 let tail = selection.tail();
14848
14849 let Some((buffer, start_position)) =
14850 self.buffer.read(cx).text_anchor_for_position(head, cx)
14851 else {
14852 return;
14853 };
14854
14855 let end_position = if head != tail {
14856 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14857 return;
14858 };
14859 Some(pos)
14860 } else {
14861 None
14862 };
14863
14864 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14865 let url = if let Some(end_pos) = end_position {
14866 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14867 } else {
14868 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14869 };
14870
14871 if let Some(url) = url {
14872 editor.update(cx, |_, cx| {
14873 cx.open_url(&url);
14874 })
14875 } else {
14876 Ok(())
14877 }
14878 });
14879
14880 url_finder.detach();
14881 }
14882
14883 pub fn open_selected_filename(
14884 &mut self,
14885 _: &OpenSelectedFilename,
14886 window: &mut Window,
14887 cx: &mut Context<Self>,
14888 ) {
14889 let Some(workspace) = self.workspace() else {
14890 return;
14891 };
14892
14893 let position = self.selections.newest_anchor().head();
14894
14895 let Some((buffer, buffer_position)) =
14896 self.buffer.read(cx).text_anchor_for_position(position, cx)
14897 else {
14898 return;
14899 };
14900
14901 let project = self.project.clone();
14902
14903 cx.spawn_in(window, async move |_, cx| {
14904 let result = find_file(&buffer, project, buffer_position, cx).await;
14905
14906 if let Some((_, path)) = result {
14907 workspace
14908 .update_in(cx, |workspace, window, cx| {
14909 workspace.open_resolved_path(path, window, cx)
14910 })?
14911 .await?;
14912 }
14913 anyhow::Ok(())
14914 })
14915 .detach();
14916 }
14917
14918 pub(crate) fn navigate_to_hover_links(
14919 &mut self,
14920 kind: Option<GotoDefinitionKind>,
14921 mut definitions: Vec<HoverLink>,
14922 split: bool,
14923 window: &mut Window,
14924 cx: &mut Context<Editor>,
14925 ) -> Task<Result<Navigated>> {
14926 // If there is one definition, just open it directly
14927 if definitions.len() == 1 {
14928 let definition = definitions.pop().unwrap();
14929
14930 enum TargetTaskResult {
14931 Location(Option<Location>),
14932 AlreadyNavigated,
14933 }
14934
14935 let target_task = match definition {
14936 HoverLink::Text(link) => {
14937 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14938 }
14939 HoverLink::InlayHint(lsp_location, server_id) => {
14940 let computation =
14941 self.compute_target_location(lsp_location, server_id, window, cx);
14942 cx.background_spawn(async move {
14943 let location = computation.await?;
14944 Ok(TargetTaskResult::Location(location))
14945 })
14946 }
14947 HoverLink::Url(url) => {
14948 cx.open_url(&url);
14949 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14950 }
14951 HoverLink::File(path) => {
14952 if let Some(workspace) = self.workspace() {
14953 cx.spawn_in(window, async move |_, cx| {
14954 workspace
14955 .update_in(cx, |workspace, window, cx| {
14956 workspace.open_resolved_path(path, window, cx)
14957 })?
14958 .await
14959 .map(|_| TargetTaskResult::AlreadyNavigated)
14960 })
14961 } else {
14962 Task::ready(Ok(TargetTaskResult::Location(None)))
14963 }
14964 }
14965 };
14966 cx.spawn_in(window, async move |editor, cx| {
14967 let target = match target_task.await.context("target resolution task")? {
14968 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14969 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14970 TargetTaskResult::Location(Some(target)) => target,
14971 };
14972
14973 editor.update_in(cx, |editor, window, cx| {
14974 let Some(workspace) = editor.workspace() else {
14975 return Navigated::No;
14976 };
14977 let pane = workspace.read(cx).active_pane().clone();
14978
14979 let range = target.range.to_point(target.buffer.read(cx));
14980 let range = editor.range_for_match(&range);
14981 let range = collapse_multiline_range(range);
14982
14983 if !split
14984 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14985 {
14986 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14987 } else {
14988 window.defer(cx, move |window, cx| {
14989 let target_editor: Entity<Self> =
14990 workspace.update(cx, |workspace, cx| {
14991 let pane = if split {
14992 workspace.adjacent_pane(window, cx)
14993 } else {
14994 workspace.active_pane().clone()
14995 };
14996
14997 workspace.open_project_item(
14998 pane,
14999 target.buffer.clone(),
15000 true,
15001 true,
15002 window,
15003 cx,
15004 )
15005 });
15006 target_editor.update(cx, |target_editor, cx| {
15007 // When selecting a definition in a different buffer, disable the nav history
15008 // to avoid creating a history entry at the previous cursor location.
15009 pane.update(cx, |pane, _| pane.disable_history());
15010 target_editor.go_to_singleton_buffer_range(range, window, cx);
15011 pane.update(cx, |pane, _| pane.enable_history());
15012 });
15013 });
15014 }
15015 Navigated::Yes
15016 })
15017 })
15018 } else if !definitions.is_empty() {
15019 cx.spawn_in(window, async move |editor, cx| {
15020 let (title, location_tasks, workspace) = editor
15021 .update_in(cx, |editor, window, cx| {
15022 let tab_kind = match kind {
15023 Some(GotoDefinitionKind::Implementation) => "Implementations",
15024 _ => "Definitions",
15025 };
15026 let title = definitions
15027 .iter()
15028 .find_map(|definition| match definition {
15029 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15030 let buffer = origin.buffer.read(cx);
15031 format!(
15032 "{} for {}",
15033 tab_kind,
15034 buffer
15035 .text_for_range(origin.range.clone())
15036 .collect::<String>()
15037 )
15038 }),
15039 HoverLink::InlayHint(_, _) => None,
15040 HoverLink::Url(_) => None,
15041 HoverLink::File(_) => None,
15042 })
15043 .unwrap_or(tab_kind.to_string());
15044 let location_tasks = definitions
15045 .into_iter()
15046 .map(|definition| match definition {
15047 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15048 HoverLink::InlayHint(lsp_location, server_id) => editor
15049 .compute_target_location(lsp_location, server_id, window, cx),
15050 HoverLink::Url(_) => Task::ready(Ok(None)),
15051 HoverLink::File(_) => Task::ready(Ok(None)),
15052 })
15053 .collect::<Vec<_>>();
15054 (title, location_tasks, editor.workspace().clone())
15055 })
15056 .context("location tasks preparation")?;
15057
15058 let locations = future::join_all(location_tasks)
15059 .await
15060 .into_iter()
15061 .filter_map(|location| location.transpose())
15062 .collect::<Result<_>>()
15063 .context("location tasks")?;
15064
15065 let Some(workspace) = workspace else {
15066 return Ok(Navigated::No);
15067 };
15068 let opened = workspace
15069 .update_in(cx, |workspace, window, cx| {
15070 Self::open_locations_in_multibuffer(
15071 workspace,
15072 locations,
15073 title,
15074 split,
15075 MultibufferSelectionMode::First,
15076 window,
15077 cx,
15078 )
15079 })
15080 .ok();
15081
15082 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15083 })
15084 } else {
15085 Task::ready(Ok(Navigated::No))
15086 }
15087 }
15088
15089 fn compute_target_location(
15090 &self,
15091 lsp_location: lsp::Location,
15092 server_id: LanguageServerId,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) -> Task<anyhow::Result<Option<Location>>> {
15096 let Some(project) = self.project.clone() else {
15097 return Task::ready(Ok(None));
15098 };
15099
15100 cx.spawn_in(window, async move |editor, cx| {
15101 let location_task = editor.update(cx, |_, cx| {
15102 project.update(cx, |project, cx| {
15103 let language_server_name = project
15104 .language_server_statuses(cx)
15105 .find(|(id, _)| server_id == *id)
15106 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15107 language_server_name.map(|language_server_name| {
15108 project.open_local_buffer_via_lsp(
15109 lsp_location.uri.clone(),
15110 server_id,
15111 language_server_name,
15112 cx,
15113 )
15114 })
15115 })
15116 })?;
15117 let location = match location_task {
15118 Some(task) => Some({
15119 let target_buffer_handle = task.await.context("open local buffer")?;
15120 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15121 let target_start = target_buffer
15122 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15123 let target_end = target_buffer
15124 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15125 target_buffer.anchor_after(target_start)
15126 ..target_buffer.anchor_before(target_end)
15127 })?;
15128 Location {
15129 buffer: target_buffer_handle,
15130 range,
15131 }
15132 }),
15133 None => None,
15134 };
15135 Ok(location)
15136 })
15137 }
15138
15139 pub fn find_all_references(
15140 &mut self,
15141 _: &FindAllReferences,
15142 window: &mut Window,
15143 cx: &mut Context<Self>,
15144 ) -> Option<Task<Result<Navigated>>> {
15145 let selection = self.selections.newest::<usize>(cx);
15146 let multi_buffer = self.buffer.read(cx);
15147 let head = selection.head();
15148
15149 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15150 let head_anchor = multi_buffer_snapshot.anchor_at(
15151 head,
15152 if head < selection.tail() {
15153 Bias::Right
15154 } else {
15155 Bias::Left
15156 },
15157 );
15158
15159 match self
15160 .find_all_references_task_sources
15161 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15162 {
15163 Ok(_) => {
15164 log::info!(
15165 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15166 );
15167 return None;
15168 }
15169 Err(i) => {
15170 self.find_all_references_task_sources.insert(i, head_anchor);
15171 }
15172 }
15173
15174 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15175 let workspace = self.workspace()?;
15176 let project = workspace.read(cx).project().clone();
15177 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15178 Some(cx.spawn_in(window, async move |editor, cx| {
15179 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15180 if let Ok(i) = editor
15181 .find_all_references_task_sources
15182 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15183 {
15184 editor.find_all_references_task_sources.remove(i);
15185 }
15186 });
15187
15188 let locations = references.await?;
15189 if locations.is_empty() {
15190 return anyhow::Ok(Navigated::No);
15191 }
15192
15193 workspace.update_in(cx, |workspace, window, cx| {
15194 let title = locations
15195 .first()
15196 .as_ref()
15197 .map(|location| {
15198 let buffer = location.buffer.read(cx);
15199 format!(
15200 "References to `{}`",
15201 buffer
15202 .text_for_range(location.range.clone())
15203 .collect::<String>()
15204 )
15205 })
15206 .unwrap();
15207 Self::open_locations_in_multibuffer(
15208 workspace,
15209 locations,
15210 title,
15211 false,
15212 MultibufferSelectionMode::First,
15213 window,
15214 cx,
15215 );
15216 Navigated::Yes
15217 })
15218 }))
15219 }
15220
15221 /// Opens a multibuffer with the given project locations in it
15222 pub fn open_locations_in_multibuffer(
15223 workspace: &mut Workspace,
15224 mut locations: Vec<Location>,
15225 title: String,
15226 split: bool,
15227 multibuffer_selection_mode: MultibufferSelectionMode,
15228 window: &mut Window,
15229 cx: &mut Context<Workspace>,
15230 ) {
15231 // If there are multiple definitions, open them in a multibuffer
15232 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15233 let mut locations = locations.into_iter().peekable();
15234 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15235 let capability = workspace.project().read(cx).capability();
15236
15237 let excerpt_buffer = cx.new(|cx| {
15238 let mut multibuffer = MultiBuffer::new(capability);
15239 while let Some(location) = locations.next() {
15240 let buffer = location.buffer.read(cx);
15241 let mut ranges_for_buffer = Vec::new();
15242 let range = location.range.to_point(buffer);
15243 ranges_for_buffer.push(range.clone());
15244
15245 while let Some(next_location) = locations.peek() {
15246 if next_location.buffer == location.buffer {
15247 ranges_for_buffer.push(next_location.range.to_point(buffer));
15248 locations.next();
15249 } else {
15250 break;
15251 }
15252 }
15253
15254 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15255 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15256 PathKey::for_buffer(&location.buffer, cx),
15257 location.buffer.clone(),
15258 ranges_for_buffer,
15259 DEFAULT_MULTIBUFFER_CONTEXT,
15260 cx,
15261 );
15262 ranges.extend(new_ranges)
15263 }
15264
15265 multibuffer.with_title(title)
15266 });
15267
15268 let editor = cx.new(|cx| {
15269 Editor::for_multibuffer(
15270 excerpt_buffer,
15271 Some(workspace.project().clone()),
15272 window,
15273 cx,
15274 )
15275 });
15276 editor.update(cx, |editor, cx| {
15277 match multibuffer_selection_mode {
15278 MultibufferSelectionMode::First => {
15279 if let Some(first_range) = ranges.first() {
15280 editor.change_selections(None, window, cx, |selections| {
15281 selections.clear_disjoint();
15282 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15283 });
15284 }
15285 editor.highlight_background::<Self>(
15286 &ranges,
15287 |theme| theme.editor_highlighted_line_background,
15288 cx,
15289 );
15290 }
15291 MultibufferSelectionMode::All => {
15292 editor.change_selections(None, window, cx, |selections| {
15293 selections.clear_disjoint();
15294 selections.select_anchor_ranges(ranges);
15295 });
15296 }
15297 }
15298 editor.register_buffers_with_language_servers(cx);
15299 });
15300
15301 let item = Box::new(editor);
15302 let item_id = item.item_id();
15303
15304 if split {
15305 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15306 } else {
15307 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15308 let (preview_item_id, preview_item_idx) =
15309 workspace.active_pane().read_with(cx, |pane, _| {
15310 (pane.preview_item_id(), pane.preview_item_idx())
15311 });
15312
15313 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15314
15315 if let Some(preview_item_id) = preview_item_id {
15316 workspace.active_pane().update(cx, |pane, cx| {
15317 pane.remove_item(preview_item_id, false, false, window, cx);
15318 });
15319 }
15320 } else {
15321 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15322 }
15323 }
15324 workspace.active_pane().update(cx, |pane, cx| {
15325 pane.set_preview_item_id(Some(item_id), cx);
15326 });
15327 }
15328
15329 pub fn rename(
15330 &mut self,
15331 _: &Rename,
15332 window: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) -> Option<Task<Result<()>>> {
15335 use language::ToOffset as _;
15336
15337 let provider = self.semantics_provider.clone()?;
15338 let selection = self.selections.newest_anchor().clone();
15339 let (cursor_buffer, cursor_buffer_position) = self
15340 .buffer
15341 .read(cx)
15342 .text_anchor_for_position(selection.head(), cx)?;
15343 let (tail_buffer, cursor_buffer_position_end) = self
15344 .buffer
15345 .read(cx)
15346 .text_anchor_for_position(selection.tail(), cx)?;
15347 if tail_buffer != cursor_buffer {
15348 return None;
15349 }
15350
15351 let snapshot = cursor_buffer.read(cx).snapshot();
15352 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15353 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15354 let prepare_rename = provider
15355 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15356 .unwrap_or_else(|| Task::ready(Ok(None)));
15357 drop(snapshot);
15358
15359 Some(cx.spawn_in(window, async move |this, cx| {
15360 let rename_range = if let Some(range) = prepare_rename.await? {
15361 Some(range)
15362 } else {
15363 this.update(cx, |this, cx| {
15364 let buffer = this.buffer.read(cx).snapshot(cx);
15365 let mut buffer_highlights = this
15366 .document_highlights_for_position(selection.head(), &buffer)
15367 .filter(|highlight| {
15368 highlight.start.excerpt_id == selection.head().excerpt_id
15369 && highlight.end.excerpt_id == selection.head().excerpt_id
15370 });
15371 buffer_highlights
15372 .next()
15373 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15374 })?
15375 };
15376 if let Some(rename_range) = rename_range {
15377 this.update_in(cx, |this, window, cx| {
15378 let snapshot = cursor_buffer.read(cx).snapshot();
15379 let rename_buffer_range = rename_range.to_offset(&snapshot);
15380 let cursor_offset_in_rename_range =
15381 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15382 let cursor_offset_in_rename_range_end =
15383 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15384
15385 this.take_rename(false, window, cx);
15386 let buffer = this.buffer.read(cx).read(cx);
15387 let cursor_offset = selection.head().to_offset(&buffer);
15388 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15389 let rename_end = rename_start + rename_buffer_range.len();
15390 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15391 let mut old_highlight_id = None;
15392 let old_name: Arc<str> = buffer
15393 .chunks(rename_start..rename_end, true)
15394 .map(|chunk| {
15395 if old_highlight_id.is_none() {
15396 old_highlight_id = chunk.syntax_highlight_id;
15397 }
15398 chunk.text
15399 })
15400 .collect::<String>()
15401 .into();
15402
15403 drop(buffer);
15404
15405 // Position the selection in the rename editor so that it matches the current selection.
15406 this.show_local_selections = false;
15407 let rename_editor = cx.new(|cx| {
15408 let mut editor = Editor::single_line(window, cx);
15409 editor.buffer.update(cx, |buffer, cx| {
15410 buffer.edit([(0..0, old_name.clone())], None, cx)
15411 });
15412 let rename_selection_range = match cursor_offset_in_rename_range
15413 .cmp(&cursor_offset_in_rename_range_end)
15414 {
15415 Ordering::Equal => {
15416 editor.select_all(&SelectAll, window, cx);
15417 return editor;
15418 }
15419 Ordering::Less => {
15420 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15421 }
15422 Ordering::Greater => {
15423 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15424 }
15425 };
15426 if rename_selection_range.end > old_name.len() {
15427 editor.select_all(&SelectAll, window, cx);
15428 } else {
15429 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15430 s.select_ranges([rename_selection_range]);
15431 });
15432 }
15433 editor
15434 });
15435 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15436 if e == &EditorEvent::Focused {
15437 cx.emit(EditorEvent::FocusedIn)
15438 }
15439 })
15440 .detach();
15441
15442 let write_highlights =
15443 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15444 let read_highlights =
15445 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15446 let ranges = write_highlights
15447 .iter()
15448 .flat_map(|(_, ranges)| ranges.iter())
15449 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15450 .cloned()
15451 .collect();
15452
15453 this.highlight_text::<Rename>(
15454 ranges,
15455 HighlightStyle {
15456 fade_out: Some(0.6),
15457 ..Default::default()
15458 },
15459 cx,
15460 );
15461 let rename_focus_handle = rename_editor.focus_handle(cx);
15462 window.focus(&rename_focus_handle);
15463 let block_id = this.insert_blocks(
15464 [BlockProperties {
15465 style: BlockStyle::Flex,
15466 placement: BlockPlacement::Below(range.start),
15467 height: Some(1),
15468 render: Arc::new({
15469 let rename_editor = rename_editor.clone();
15470 move |cx: &mut BlockContext| {
15471 let mut text_style = cx.editor_style.text.clone();
15472 if let Some(highlight_style) = old_highlight_id
15473 .and_then(|h| h.style(&cx.editor_style.syntax))
15474 {
15475 text_style = text_style.highlight(highlight_style);
15476 }
15477 div()
15478 .block_mouse_except_scroll()
15479 .pl(cx.anchor_x)
15480 .child(EditorElement::new(
15481 &rename_editor,
15482 EditorStyle {
15483 background: cx.theme().system().transparent,
15484 local_player: cx.editor_style.local_player,
15485 text: text_style,
15486 scrollbar_width: cx.editor_style.scrollbar_width,
15487 syntax: cx.editor_style.syntax.clone(),
15488 status: cx.editor_style.status.clone(),
15489 inlay_hints_style: HighlightStyle {
15490 font_weight: Some(FontWeight::BOLD),
15491 ..make_inlay_hints_style(cx.app)
15492 },
15493 inline_completion_styles: make_suggestion_styles(
15494 cx.app,
15495 ),
15496 ..EditorStyle::default()
15497 },
15498 ))
15499 .into_any_element()
15500 }
15501 }),
15502 priority: 0,
15503 render_in_minimap: true,
15504 }],
15505 Some(Autoscroll::fit()),
15506 cx,
15507 )[0];
15508 this.pending_rename = Some(RenameState {
15509 range,
15510 old_name,
15511 editor: rename_editor,
15512 block_id,
15513 });
15514 })?;
15515 }
15516
15517 Ok(())
15518 }))
15519 }
15520
15521 pub fn confirm_rename(
15522 &mut self,
15523 _: &ConfirmRename,
15524 window: &mut Window,
15525 cx: &mut Context<Self>,
15526 ) -> Option<Task<Result<()>>> {
15527 let rename = self.take_rename(false, window, cx)?;
15528 let workspace = self.workspace()?.downgrade();
15529 let (buffer, start) = self
15530 .buffer
15531 .read(cx)
15532 .text_anchor_for_position(rename.range.start, cx)?;
15533 let (end_buffer, _) = self
15534 .buffer
15535 .read(cx)
15536 .text_anchor_for_position(rename.range.end, cx)?;
15537 if buffer != end_buffer {
15538 return None;
15539 }
15540
15541 let old_name = rename.old_name;
15542 let new_name = rename.editor.read(cx).text(cx);
15543
15544 let rename = self.semantics_provider.as_ref()?.perform_rename(
15545 &buffer,
15546 start,
15547 new_name.clone(),
15548 cx,
15549 )?;
15550
15551 Some(cx.spawn_in(window, async move |editor, cx| {
15552 let project_transaction = rename.await?;
15553 Self::open_project_transaction(
15554 &editor,
15555 workspace,
15556 project_transaction,
15557 format!("Rename: {} → {}", old_name, new_name),
15558 cx,
15559 )
15560 .await?;
15561
15562 editor.update(cx, |editor, cx| {
15563 editor.refresh_document_highlights(cx);
15564 })?;
15565 Ok(())
15566 }))
15567 }
15568
15569 fn take_rename(
15570 &mut self,
15571 moving_cursor: bool,
15572 window: &mut Window,
15573 cx: &mut Context<Self>,
15574 ) -> Option<RenameState> {
15575 let rename = self.pending_rename.take()?;
15576 if rename.editor.focus_handle(cx).is_focused(window) {
15577 window.focus(&self.focus_handle);
15578 }
15579
15580 self.remove_blocks(
15581 [rename.block_id].into_iter().collect(),
15582 Some(Autoscroll::fit()),
15583 cx,
15584 );
15585 self.clear_highlights::<Rename>(cx);
15586 self.show_local_selections = true;
15587
15588 if moving_cursor {
15589 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15590 editor.selections.newest::<usize>(cx).head()
15591 });
15592
15593 // Update the selection to match the position of the selection inside
15594 // the rename editor.
15595 let snapshot = self.buffer.read(cx).read(cx);
15596 let rename_range = rename.range.to_offset(&snapshot);
15597 let cursor_in_editor = snapshot
15598 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15599 .min(rename_range.end);
15600 drop(snapshot);
15601
15602 self.change_selections(None, window, cx, |s| {
15603 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15604 });
15605 } else {
15606 self.refresh_document_highlights(cx);
15607 }
15608
15609 Some(rename)
15610 }
15611
15612 pub fn pending_rename(&self) -> Option<&RenameState> {
15613 self.pending_rename.as_ref()
15614 }
15615
15616 fn format(
15617 &mut self,
15618 _: &Format,
15619 window: &mut Window,
15620 cx: &mut Context<Self>,
15621 ) -> Option<Task<Result<()>>> {
15622 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15623
15624 let project = match &self.project {
15625 Some(project) => project.clone(),
15626 None => return None,
15627 };
15628
15629 Some(self.perform_format(
15630 project,
15631 FormatTrigger::Manual,
15632 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15633 window,
15634 cx,
15635 ))
15636 }
15637
15638 fn format_selections(
15639 &mut self,
15640 _: &FormatSelections,
15641 window: &mut Window,
15642 cx: &mut Context<Self>,
15643 ) -> Option<Task<Result<()>>> {
15644 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15645
15646 let project = match &self.project {
15647 Some(project) => project.clone(),
15648 None => return None,
15649 };
15650
15651 let ranges = self
15652 .selections
15653 .all_adjusted(cx)
15654 .into_iter()
15655 .map(|selection| selection.range())
15656 .collect_vec();
15657
15658 Some(self.perform_format(
15659 project,
15660 FormatTrigger::Manual,
15661 FormatTarget::Ranges(ranges),
15662 window,
15663 cx,
15664 ))
15665 }
15666
15667 fn perform_format(
15668 &mut self,
15669 project: Entity<Project>,
15670 trigger: FormatTrigger,
15671 target: FormatTarget,
15672 window: &mut Window,
15673 cx: &mut Context<Self>,
15674 ) -> Task<Result<()>> {
15675 let buffer = self.buffer.clone();
15676 let (buffers, target) = match target {
15677 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15678 FormatTarget::Ranges(selection_ranges) => {
15679 let multi_buffer = buffer.read(cx);
15680 let snapshot = multi_buffer.read(cx);
15681 let mut buffers = HashSet::default();
15682 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15683 BTreeMap::new();
15684 for selection_range in selection_ranges {
15685 for (buffer, buffer_range, _) in
15686 snapshot.range_to_buffer_ranges(selection_range)
15687 {
15688 let buffer_id = buffer.remote_id();
15689 let start = buffer.anchor_before(buffer_range.start);
15690 let end = buffer.anchor_after(buffer_range.end);
15691 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15692 buffer_id_to_ranges
15693 .entry(buffer_id)
15694 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15695 .or_insert_with(|| vec![start..end]);
15696 }
15697 }
15698 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15699 }
15700 };
15701
15702 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15703 let selections_prev = transaction_id_prev
15704 .and_then(|transaction_id_prev| {
15705 // default to selections as they were after the last edit, if we have them,
15706 // instead of how they are now.
15707 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15708 // will take you back to where you made the last edit, instead of staying where you scrolled
15709 self.selection_history
15710 .transaction(transaction_id_prev)
15711 .map(|t| t.0.clone())
15712 })
15713 .unwrap_or_else(|| {
15714 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15715 self.selections.disjoint_anchors()
15716 });
15717
15718 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15719 let format = project.update(cx, |project, cx| {
15720 project.format(buffers, target, true, trigger, cx)
15721 });
15722
15723 cx.spawn_in(window, async move |editor, cx| {
15724 let transaction = futures::select_biased! {
15725 transaction = format.log_err().fuse() => transaction,
15726 () = timeout => {
15727 log::warn!("timed out waiting for formatting");
15728 None
15729 }
15730 };
15731
15732 buffer
15733 .update(cx, |buffer, cx| {
15734 if let Some(transaction) = transaction {
15735 if !buffer.is_singleton() {
15736 buffer.push_transaction(&transaction.0, cx);
15737 }
15738 }
15739 cx.notify();
15740 })
15741 .ok();
15742
15743 if let Some(transaction_id_now) =
15744 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15745 {
15746 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15747 if has_new_transaction {
15748 _ = editor.update(cx, |editor, _| {
15749 editor
15750 .selection_history
15751 .insert_transaction(transaction_id_now, selections_prev);
15752 });
15753 }
15754 }
15755
15756 Ok(())
15757 })
15758 }
15759
15760 fn organize_imports(
15761 &mut self,
15762 _: &OrganizeImports,
15763 window: &mut Window,
15764 cx: &mut Context<Self>,
15765 ) -> Option<Task<Result<()>>> {
15766 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15767 let project = match &self.project {
15768 Some(project) => project.clone(),
15769 None => return None,
15770 };
15771 Some(self.perform_code_action_kind(
15772 project,
15773 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15774 window,
15775 cx,
15776 ))
15777 }
15778
15779 fn perform_code_action_kind(
15780 &mut self,
15781 project: Entity<Project>,
15782 kind: CodeActionKind,
15783 window: &mut Window,
15784 cx: &mut Context<Self>,
15785 ) -> Task<Result<()>> {
15786 let buffer = self.buffer.clone();
15787 let buffers = buffer.read(cx).all_buffers();
15788 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15789 let apply_action = project.update(cx, |project, cx| {
15790 project.apply_code_action_kind(buffers, kind, true, cx)
15791 });
15792 cx.spawn_in(window, async move |_, cx| {
15793 let transaction = futures::select_biased! {
15794 () = timeout => {
15795 log::warn!("timed out waiting for executing code action");
15796 None
15797 }
15798 transaction = apply_action.log_err().fuse() => transaction,
15799 };
15800 buffer
15801 .update(cx, |buffer, cx| {
15802 // check if we need this
15803 if let Some(transaction) = transaction {
15804 if !buffer.is_singleton() {
15805 buffer.push_transaction(&transaction.0, cx);
15806 }
15807 }
15808 cx.notify();
15809 })
15810 .ok();
15811 Ok(())
15812 })
15813 }
15814
15815 fn restart_language_server(
15816 &mut self,
15817 _: &RestartLanguageServer,
15818 _: &mut Window,
15819 cx: &mut Context<Self>,
15820 ) {
15821 if let Some(project) = self.project.clone() {
15822 self.buffer.update(cx, |multi_buffer, cx| {
15823 project.update(cx, |project, cx| {
15824 project.restart_language_servers_for_buffers(
15825 multi_buffer.all_buffers().into_iter().collect(),
15826 cx,
15827 );
15828 });
15829 })
15830 }
15831 }
15832
15833 fn stop_language_server(
15834 &mut self,
15835 _: &StopLanguageServer,
15836 _: &mut Window,
15837 cx: &mut Context<Self>,
15838 ) {
15839 if let Some(project) = self.project.clone() {
15840 self.buffer.update(cx, |multi_buffer, cx| {
15841 project.update(cx, |project, cx| {
15842 project.stop_language_servers_for_buffers(
15843 multi_buffer.all_buffers().into_iter().collect(),
15844 cx,
15845 );
15846 cx.emit(project::Event::RefreshInlayHints);
15847 });
15848 });
15849 }
15850 }
15851
15852 fn cancel_language_server_work(
15853 workspace: &mut Workspace,
15854 _: &actions::CancelLanguageServerWork,
15855 _: &mut Window,
15856 cx: &mut Context<Workspace>,
15857 ) {
15858 let project = workspace.project();
15859 let buffers = workspace
15860 .active_item(cx)
15861 .and_then(|item| item.act_as::<Editor>(cx))
15862 .map_or(HashSet::default(), |editor| {
15863 editor.read(cx).buffer.read(cx).all_buffers()
15864 });
15865 project.update(cx, |project, cx| {
15866 project.cancel_language_server_work_for_buffers(buffers, cx);
15867 });
15868 }
15869
15870 fn show_character_palette(
15871 &mut self,
15872 _: &ShowCharacterPalette,
15873 window: &mut Window,
15874 _: &mut Context<Self>,
15875 ) {
15876 window.show_character_palette();
15877 }
15878
15879 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15880 if self.mode.is_minimap() {
15881 return;
15882 }
15883
15884 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15885 let buffer = self.buffer.read(cx).snapshot(cx);
15886 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15887 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15888 let is_valid = buffer
15889 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15890 .any(|entry| {
15891 entry.diagnostic.is_primary
15892 && !entry.range.is_empty()
15893 && entry.range.start == primary_range_start
15894 && entry.diagnostic.message == active_diagnostics.active_message
15895 });
15896
15897 if !is_valid {
15898 self.dismiss_diagnostics(cx);
15899 }
15900 }
15901 }
15902
15903 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15904 match &self.active_diagnostics {
15905 ActiveDiagnostic::Group(group) => Some(group),
15906 _ => None,
15907 }
15908 }
15909
15910 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15911 self.dismiss_diagnostics(cx);
15912 self.active_diagnostics = ActiveDiagnostic::All;
15913 }
15914
15915 fn activate_diagnostics(
15916 &mut self,
15917 buffer_id: BufferId,
15918 diagnostic: DiagnosticEntry<usize>,
15919 window: &mut Window,
15920 cx: &mut Context<Self>,
15921 ) {
15922 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15923 return;
15924 }
15925 self.dismiss_diagnostics(cx);
15926 let snapshot = self.snapshot(window, cx);
15927 let buffer = self.buffer.read(cx).snapshot(cx);
15928 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15929 return;
15930 };
15931
15932 let diagnostic_group = buffer
15933 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15934 .collect::<Vec<_>>();
15935
15936 let blocks =
15937 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15938
15939 let blocks = self.display_map.update(cx, |display_map, cx| {
15940 display_map.insert_blocks(blocks, cx).into_iter().collect()
15941 });
15942 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15943 active_range: buffer.anchor_before(diagnostic.range.start)
15944 ..buffer.anchor_after(diagnostic.range.end),
15945 active_message: diagnostic.diagnostic.message.clone(),
15946 group_id: diagnostic.diagnostic.group_id,
15947 blocks,
15948 });
15949 cx.notify();
15950 }
15951
15952 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15953 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15954 return;
15955 };
15956
15957 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15958 if let ActiveDiagnostic::Group(group) = prev {
15959 self.display_map.update(cx, |display_map, cx| {
15960 display_map.remove_blocks(group.blocks, cx);
15961 });
15962 cx.notify();
15963 }
15964 }
15965
15966 /// Disable inline diagnostics rendering for this editor.
15967 pub fn disable_inline_diagnostics(&mut self) {
15968 self.inline_diagnostics_enabled = false;
15969 self.inline_diagnostics_update = Task::ready(());
15970 self.inline_diagnostics.clear();
15971 }
15972
15973 pub fn diagnostics_enabled(&self) -> bool {
15974 self.mode.is_full()
15975 }
15976
15977 pub fn inline_diagnostics_enabled(&self) -> bool {
15978 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15979 }
15980
15981 pub fn show_inline_diagnostics(&self) -> bool {
15982 self.show_inline_diagnostics
15983 }
15984
15985 pub fn toggle_inline_diagnostics(
15986 &mut self,
15987 _: &ToggleInlineDiagnostics,
15988 window: &mut Window,
15989 cx: &mut Context<Editor>,
15990 ) {
15991 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15992 self.refresh_inline_diagnostics(false, window, cx);
15993 }
15994
15995 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15996 self.diagnostics_max_severity = severity;
15997 self.display_map.update(cx, |display_map, _| {
15998 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15999 });
16000 }
16001
16002 pub fn toggle_diagnostics(
16003 &mut self,
16004 _: &ToggleDiagnostics,
16005 window: &mut Window,
16006 cx: &mut Context<Editor>,
16007 ) {
16008 if !self.diagnostics_enabled() {
16009 return;
16010 }
16011
16012 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16013 EditorSettings::get_global(cx)
16014 .diagnostics_max_severity
16015 .filter(|severity| severity != &DiagnosticSeverity::Off)
16016 .unwrap_or(DiagnosticSeverity::Hint)
16017 } else {
16018 DiagnosticSeverity::Off
16019 };
16020 self.set_max_diagnostics_severity(new_severity, cx);
16021 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16022 self.active_diagnostics = ActiveDiagnostic::None;
16023 self.inline_diagnostics_update = Task::ready(());
16024 self.inline_diagnostics.clear();
16025 } else {
16026 self.refresh_inline_diagnostics(false, window, cx);
16027 }
16028
16029 cx.notify();
16030 }
16031
16032 pub fn toggle_minimap(
16033 &mut self,
16034 _: &ToggleMinimap,
16035 window: &mut Window,
16036 cx: &mut Context<Editor>,
16037 ) {
16038 if self.supports_minimap(cx) {
16039 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16040 }
16041 }
16042
16043 fn refresh_inline_diagnostics(
16044 &mut self,
16045 debounce: bool,
16046 window: &mut Window,
16047 cx: &mut Context<Self>,
16048 ) {
16049 let max_severity = ProjectSettings::get_global(cx)
16050 .diagnostics
16051 .inline
16052 .max_severity
16053 .unwrap_or(self.diagnostics_max_severity);
16054
16055 if !self.inline_diagnostics_enabled()
16056 || !self.show_inline_diagnostics
16057 || max_severity == DiagnosticSeverity::Off
16058 {
16059 self.inline_diagnostics_update = Task::ready(());
16060 self.inline_diagnostics.clear();
16061 return;
16062 }
16063
16064 let debounce_ms = ProjectSettings::get_global(cx)
16065 .diagnostics
16066 .inline
16067 .update_debounce_ms;
16068 let debounce = if debounce && debounce_ms > 0 {
16069 Some(Duration::from_millis(debounce_ms))
16070 } else {
16071 None
16072 };
16073 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16074 if let Some(debounce) = debounce {
16075 cx.background_executor().timer(debounce).await;
16076 }
16077 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16078 editor
16079 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16080 .ok()
16081 }) else {
16082 return;
16083 };
16084
16085 let new_inline_diagnostics = cx
16086 .background_spawn(async move {
16087 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16088 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16089 let message = diagnostic_entry
16090 .diagnostic
16091 .message
16092 .split_once('\n')
16093 .map(|(line, _)| line)
16094 .map(SharedString::new)
16095 .unwrap_or_else(|| {
16096 SharedString::from(diagnostic_entry.diagnostic.message)
16097 });
16098 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16099 let (Ok(i) | Err(i)) = inline_diagnostics
16100 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16101 inline_diagnostics.insert(
16102 i,
16103 (
16104 start_anchor,
16105 InlineDiagnostic {
16106 message,
16107 group_id: diagnostic_entry.diagnostic.group_id,
16108 start: diagnostic_entry.range.start.to_point(&snapshot),
16109 is_primary: diagnostic_entry.diagnostic.is_primary,
16110 severity: diagnostic_entry.diagnostic.severity,
16111 },
16112 ),
16113 );
16114 }
16115 inline_diagnostics
16116 })
16117 .await;
16118
16119 editor
16120 .update(cx, |editor, cx| {
16121 editor.inline_diagnostics = new_inline_diagnostics;
16122 cx.notify();
16123 })
16124 .ok();
16125 });
16126 }
16127
16128 fn pull_diagnostics(
16129 &mut self,
16130 buffer_id: Option<BufferId>,
16131 window: &Window,
16132 cx: &mut Context<Self>,
16133 ) -> Option<()> {
16134 if !self.mode().is_full() {
16135 return None;
16136 }
16137 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16138 .diagnostics
16139 .lsp_pull_diagnostics;
16140 if !pull_diagnostics_settings.enabled {
16141 return None;
16142 }
16143 let project = self.project.as_ref()?.downgrade();
16144 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16145 let mut buffers = self.buffer.read(cx).all_buffers();
16146 if let Some(buffer_id) = buffer_id {
16147 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16148 }
16149
16150 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16151 cx.background_executor().timer(debounce).await;
16152
16153 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16154 buffers
16155 .into_iter()
16156 .flat_map(|buffer| {
16157 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16158 })
16159 .collect::<FuturesUnordered<_>>()
16160 }) else {
16161 return;
16162 };
16163
16164 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16165 match pull_task {
16166 Ok(()) => {
16167 if editor
16168 .update_in(cx, |editor, window, cx| {
16169 editor.update_diagnostics_state(window, cx);
16170 })
16171 .is_err()
16172 {
16173 return;
16174 }
16175 }
16176 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16177 }
16178 }
16179 });
16180
16181 Some(())
16182 }
16183
16184 pub fn set_selections_from_remote(
16185 &mut self,
16186 selections: Vec<Selection<Anchor>>,
16187 pending_selection: Option<Selection<Anchor>>,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 let old_cursor_position = self.selections.newest_anchor().head();
16192 self.selections.change_with(cx, |s| {
16193 s.select_anchors(selections);
16194 if let Some(pending_selection) = pending_selection {
16195 s.set_pending(pending_selection, SelectMode::Character);
16196 } else {
16197 s.clear_pending();
16198 }
16199 });
16200 self.selections_did_change(
16201 false,
16202 &old_cursor_position,
16203 SelectionEffects::default(),
16204 window,
16205 cx,
16206 );
16207 }
16208
16209 pub fn transact(
16210 &mut self,
16211 window: &mut Window,
16212 cx: &mut Context<Self>,
16213 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16214 ) -> Option<TransactionId> {
16215 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16216 this.start_transaction_at(Instant::now(), window, cx);
16217 update(this, window, cx);
16218 this.end_transaction_at(Instant::now(), cx)
16219 })
16220 }
16221
16222 pub fn start_transaction_at(
16223 &mut self,
16224 now: Instant,
16225 window: &mut Window,
16226 cx: &mut Context<Self>,
16227 ) {
16228 self.end_selection(window, cx);
16229 if let Some(tx_id) = self
16230 .buffer
16231 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16232 {
16233 self.selection_history
16234 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16235 cx.emit(EditorEvent::TransactionBegun {
16236 transaction_id: tx_id,
16237 })
16238 }
16239 }
16240
16241 pub fn end_transaction_at(
16242 &mut self,
16243 now: Instant,
16244 cx: &mut Context<Self>,
16245 ) -> Option<TransactionId> {
16246 if let Some(transaction_id) = self
16247 .buffer
16248 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16249 {
16250 if let Some((_, end_selections)) =
16251 self.selection_history.transaction_mut(transaction_id)
16252 {
16253 *end_selections = Some(self.selections.disjoint_anchors());
16254 } else {
16255 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16256 }
16257
16258 cx.emit(EditorEvent::Edited { transaction_id });
16259 Some(transaction_id)
16260 } else {
16261 None
16262 }
16263 }
16264
16265 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16266 if self.selection_mark_mode {
16267 self.change_selections(None, window, cx, |s| {
16268 s.move_with(|_, sel| {
16269 sel.collapse_to(sel.head(), SelectionGoal::None);
16270 });
16271 })
16272 }
16273 self.selection_mark_mode = true;
16274 cx.notify();
16275 }
16276
16277 pub fn swap_selection_ends(
16278 &mut self,
16279 _: &actions::SwapSelectionEnds,
16280 window: &mut Window,
16281 cx: &mut Context<Self>,
16282 ) {
16283 self.change_selections(None, window, cx, |s| {
16284 s.move_with(|_, sel| {
16285 if sel.start != sel.end {
16286 sel.reversed = !sel.reversed
16287 }
16288 });
16289 });
16290 self.request_autoscroll(Autoscroll::newest(), cx);
16291 cx.notify();
16292 }
16293
16294 pub fn toggle_fold(
16295 &mut self,
16296 _: &actions::ToggleFold,
16297 window: &mut Window,
16298 cx: &mut Context<Self>,
16299 ) {
16300 if self.is_singleton(cx) {
16301 let selection = self.selections.newest::<Point>(cx);
16302
16303 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16304 let range = if selection.is_empty() {
16305 let point = selection.head().to_display_point(&display_map);
16306 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16307 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16308 .to_point(&display_map);
16309 start..end
16310 } else {
16311 selection.range()
16312 };
16313 if display_map.folds_in_range(range).next().is_some() {
16314 self.unfold_lines(&Default::default(), window, cx)
16315 } else {
16316 self.fold(&Default::default(), window, cx)
16317 }
16318 } else {
16319 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16320 let buffer_ids: HashSet<_> = self
16321 .selections
16322 .disjoint_anchor_ranges()
16323 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16324 .collect();
16325
16326 let should_unfold = buffer_ids
16327 .iter()
16328 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16329
16330 for buffer_id in buffer_ids {
16331 if should_unfold {
16332 self.unfold_buffer(buffer_id, cx);
16333 } else {
16334 self.fold_buffer(buffer_id, cx);
16335 }
16336 }
16337 }
16338 }
16339
16340 pub fn toggle_fold_recursive(
16341 &mut self,
16342 _: &actions::ToggleFoldRecursive,
16343 window: &mut Window,
16344 cx: &mut Context<Self>,
16345 ) {
16346 let selection = self.selections.newest::<Point>(cx);
16347
16348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16349 let range = if selection.is_empty() {
16350 let point = selection.head().to_display_point(&display_map);
16351 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16352 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16353 .to_point(&display_map);
16354 start..end
16355 } else {
16356 selection.range()
16357 };
16358 if display_map.folds_in_range(range).next().is_some() {
16359 self.unfold_recursive(&Default::default(), window, cx)
16360 } else {
16361 self.fold_recursive(&Default::default(), window, cx)
16362 }
16363 }
16364
16365 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16366 if self.is_singleton(cx) {
16367 let mut to_fold = Vec::new();
16368 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16369 let selections = self.selections.all_adjusted(cx);
16370
16371 for selection in selections {
16372 let range = selection.range().sorted();
16373 let buffer_start_row = range.start.row;
16374
16375 if range.start.row != range.end.row {
16376 let mut found = false;
16377 let mut row = range.start.row;
16378 while row <= range.end.row {
16379 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16380 {
16381 found = true;
16382 row = crease.range().end.row + 1;
16383 to_fold.push(crease);
16384 } else {
16385 row += 1
16386 }
16387 }
16388 if found {
16389 continue;
16390 }
16391 }
16392
16393 for row in (0..=range.start.row).rev() {
16394 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16395 if crease.range().end.row >= buffer_start_row {
16396 to_fold.push(crease);
16397 if row <= range.start.row {
16398 break;
16399 }
16400 }
16401 }
16402 }
16403 }
16404
16405 self.fold_creases(to_fold, true, window, cx);
16406 } else {
16407 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16408 let buffer_ids = self
16409 .selections
16410 .disjoint_anchor_ranges()
16411 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16412 .collect::<HashSet<_>>();
16413 for buffer_id in buffer_ids {
16414 self.fold_buffer(buffer_id, cx);
16415 }
16416 }
16417 }
16418
16419 fn fold_at_level(
16420 &mut self,
16421 fold_at: &FoldAtLevel,
16422 window: &mut Window,
16423 cx: &mut Context<Self>,
16424 ) {
16425 if !self.buffer.read(cx).is_singleton() {
16426 return;
16427 }
16428
16429 let fold_at_level = fold_at.0;
16430 let snapshot = self.buffer.read(cx).snapshot(cx);
16431 let mut to_fold = Vec::new();
16432 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16433
16434 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16435 while start_row < end_row {
16436 match self
16437 .snapshot(window, cx)
16438 .crease_for_buffer_row(MultiBufferRow(start_row))
16439 {
16440 Some(crease) => {
16441 let nested_start_row = crease.range().start.row + 1;
16442 let nested_end_row = crease.range().end.row;
16443
16444 if current_level < fold_at_level {
16445 stack.push((nested_start_row, nested_end_row, current_level + 1));
16446 } else if current_level == fold_at_level {
16447 to_fold.push(crease);
16448 }
16449
16450 start_row = nested_end_row + 1;
16451 }
16452 None => start_row += 1,
16453 }
16454 }
16455 }
16456
16457 self.fold_creases(to_fold, true, window, cx);
16458 }
16459
16460 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16461 if self.buffer.read(cx).is_singleton() {
16462 let mut fold_ranges = Vec::new();
16463 let snapshot = self.buffer.read(cx).snapshot(cx);
16464
16465 for row in 0..snapshot.max_row().0 {
16466 if let Some(foldable_range) = self
16467 .snapshot(window, cx)
16468 .crease_for_buffer_row(MultiBufferRow(row))
16469 {
16470 fold_ranges.push(foldable_range);
16471 }
16472 }
16473
16474 self.fold_creases(fold_ranges, true, window, cx);
16475 } else {
16476 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16477 editor
16478 .update_in(cx, |editor, _, cx| {
16479 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16480 editor.fold_buffer(buffer_id, cx);
16481 }
16482 })
16483 .ok();
16484 });
16485 }
16486 }
16487
16488 pub fn fold_function_bodies(
16489 &mut self,
16490 _: &actions::FoldFunctionBodies,
16491 window: &mut Window,
16492 cx: &mut Context<Self>,
16493 ) {
16494 let snapshot = self.buffer.read(cx).snapshot(cx);
16495
16496 let ranges = snapshot
16497 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16498 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16499 .collect::<Vec<_>>();
16500
16501 let creases = ranges
16502 .into_iter()
16503 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16504 .collect();
16505
16506 self.fold_creases(creases, true, window, cx);
16507 }
16508
16509 pub fn fold_recursive(
16510 &mut self,
16511 _: &actions::FoldRecursive,
16512 window: &mut Window,
16513 cx: &mut Context<Self>,
16514 ) {
16515 let mut to_fold = Vec::new();
16516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16517 let selections = self.selections.all_adjusted(cx);
16518
16519 for selection in selections {
16520 let range = selection.range().sorted();
16521 let buffer_start_row = range.start.row;
16522
16523 if range.start.row != range.end.row {
16524 let mut found = false;
16525 for row in range.start.row..=range.end.row {
16526 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16527 found = true;
16528 to_fold.push(crease);
16529 }
16530 }
16531 if found {
16532 continue;
16533 }
16534 }
16535
16536 for row in (0..=range.start.row).rev() {
16537 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16538 if crease.range().end.row >= buffer_start_row {
16539 to_fold.push(crease);
16540 } else {
16541 break;
16542 }
16543 }
16544 }
16545 }
16546
16547 self.fold_creases(to_fold, true, window, cx);
16548 }
16549
16550 pub fn fold_at(
16551 &mut self,
16552 buffer_row: MultiBufferRow,
16553 window: &mut Window,
16554 cx: &mut Context<Self>,
16555 ) {
16556 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16557
16558 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16559 let autoscroll = self
16560 .selections
16561 .all::<Point>(cx)
16562 .iter()
16563 .any(|selection| crease.range().overlaps(&selection.range()));
16564
16565 self.fold_creases(vec![crease], autoscroll, window, cx);
16566 }
16567 }
16568
16569 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16570 if self.is_singleton(cx) {
16571 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16572 let buffer = &display_map.buffer_snapshot;
16573 let selections = self.selections.all::<Point>(cx);
16574 let ranges = selections
16575 .iter()
16576 .map(|s| {
16577 let range = s.display_range(&display_map).sorted();
16578 let mut start = range.start.to_point(&display_map);
16579 let mut end = range.end.to_point(&display_map);
16580 start.column = 0;
16581 end.column = buffer.line_len(MultiBufferRow(end.row));
16582 start..end
16583 })
16584 .collect::<Vec<_>>();
16585
16586 self.unfold_ranges(&ranges, true, true, cx);
16587 } else {
16588 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16589 let buffer_ids = self
16590 .selections
16591 .disjoint_anchor_ranges()
16592 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16593 .collect::<HashSet<_>>();
16594 for buffer_id in buffer_ids {
16595 self.unfold_buffer(buffer_id, cx);
16596 }
16597 }
16598 }
16599
16600 pub fn unfold_recursive(
16601 &mut self,
16602 _: &UnfoldRecursive,
16603 _window: &mut Window,
16604 cx: &mut Context<Self>,
16605 ) {
16606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16607 let selections = self.selections.all::<Point>(cx);
16608 let ranges = selections
16609 .iter()
16610 .map(|s| {
16611 let mut range = s.display_range(&display_map).sorted();
16612 *range.start.column_mut() = 0;
16613 *range.end.column_mut() = display_map.line_len(range.end.row());
16614 let start = range.start.to_point(&display_map);
16615 let end = range.end.to_point(&display_map);
16616 start..end
16617 })
16618 .collect::<Vec<_>>();
16619
16620 self.unfold_ranges(&ranges, true, true, cx);
16621 }
16622
16623 pub fn unfold_at(
16624 &mut self,
16625 buffer_row: MultiBufferRow,
16626 _window: &mut Window,
16627 cx: &mut Context<Self>,
16628 ) {
16629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16630
16631 let intersection_range = Point::new(buffer_row.0, 0)
16632 ..Point::new(
16633 buffer_row.0,
16634 display_map.buffer_snapshot.line_len(buffer_row),
16635 );
16636
16637 let autoscroll = self
16638 .selections
16639 .all::<Point>(cx)
16640 .iter()
16641 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16642
16643 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16644 }
16645
16646 pub fn unfold_all(
16647 &mut self,
16648 _: &actions::UnfoldAll,
16649 _window: &mut Window,
16650 cx: &mut Context<Self>,
16651 ) {
16652 if self.buffer.read(cx).is_singleton() {
16653 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16654 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16655 } else {
16656 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16657 editor
16658 .update(cx, |editor, cx| {
16659 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16660 editor.unfold_buffer(buffer_id, cx);
16661 }
16662 })
16663 .ok();
16664 });
16665 }
16666 }
16667
16668 pub fn fold_selected_ranges(
16669 &mut self,
16670 _: &FoldSelectedRanges,
16671 window: &mut Window,
16672 cx: &mut Context<Self>,
16673 ) {
16674 let selections = self.selections.all_adjusted(cx);
16675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16676 let ranges = selections
16677 .into_iter()
16678 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16679 .collect::<Vec<_>>();
16680 self.fold_creases(ranges, true, window, cx);
16681 }
16682
16683 pub fn fold_ranges<T: ToOffset + Clone>(
16684 &mut self,
16685 ranges: Vec<Range<T>>,
16686 auto_scroll: bool,
16687 window: &mut Window,
16688 cx: &mut Context<Self>,
16689 ) {
16690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16691 let ranges = ranges
16692 .into_iter()
16693 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16694 .collect::<Vec<_>>();
16695 self.fold_creases(ranges, auto_scroll, window, cx);
16696 }
16697
16698 pub fn fold_creases<T: ToOffset + Clone>(
16699 &mut self,
16700 creases: Vec<Crease<T>>,
16701 auto_scroll: bool,
16702 _window: &mut Window,
16703 cx: &mut Context<Self>,
16704 ) {
16705 if creases.is_empty() {
16706 return;
16707 }
16708
16709 let mut buffers_affected = HashSet::default();
16710 let multi_buffer = self.buffer().read(cx);
16711 for crease in &creases {
16712 if let Some((_, buffer, _)) =
16713 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16714 {
16715 buffers_affected.insert(buffer.read(cx).remote_id());
16716 };
16717 }
16718
16719 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16720
16721 if auto_scroll {
16722 self.request_autoscroll(Autoscroll::fit(), cx);
16723 }
16724
16725 cx.notify();
16726
16727 self.scrollbar_marker_state.dirty = true;
16728 self.folds_did_change(cx);
16729 }
16730
16731 /// Removes any folds whose ranges intersect any of the given ranges.
16732 pub fn unfold_ranges<T: ToOffset + Clone>(
16733 &mut self,
16734 ranges: &[Range<T>],
16735 inclusive: bool,
16736 auto_scroll: bool,
16737 cx: &mut Context<Self>,
16738 ) {
16739 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16740 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16741 });
16742 self.folds_did_change(cx);
16743 }
16744
16745 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16746 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16747 return;
16748 }
16749 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16750 self.display_map.update(cx, |display_map, cx| {
16751 display_map.fold_buffers([buffer_id], cx)
16752 });
16753 cx.emit(EditorEvent::BufferFoldToggled {
16754 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16755 folded: true,
16756 });
16757 cx.notify();
16758 }
16759
16760 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16761 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16762 return;
16763 }
16764 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16765 self.display_map.update(cx, |display_map, cx| {
16766 display_map.unfold_buffers([buffer_id], cx);
16767 });
16768 cx.emit(EditorEvent::BufferFoldToggled {
16769 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16770 folded: false,
16771 });
16772 cx.notify();
16773 }
16774
16775 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16776 self.display_map.read(cx).is_buffer_folded(buffer)
16777 }
16778
16779 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16780 self.display_map.read(cx).folded_buffers()
16781 }
16782
16783 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16784 self.display_map.update(cx, |display_map, cx| {
16785 display_map.disable_header_for_buffer(buffer_id, cx);
16786 });
16787 cx.notify();
16788 }
16789
16790 /// Removes any folds with the given ranges.
16791 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16792 &mut self,
16793 ranges: &[Range<T>],
16794 type_id: TypeId,
16795 auto_scroll: bool,
16796 cx: &mut Context<Self>,
16797 ) {
16798 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16799 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16800 });
16801 self.folds_did_change(cx);
16802 }
16803
16804 fn remove_folds_with<T: ToOffset + Clone>(
16805 &mut self,
16806 ranges: &[Range<T>],
16807 auto_scroll: bool,
16808 cx: &mut Context<Self>,
16809 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16810 ) {
16811 if ranges.is_empty() {
16812 return;
16813 }
16814
16815 let mut buffers_affected = HashSet::default();
16816 let multi_buffer = self.buffer().read(cx);
16817 for range in ranges {
16818 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16819 buffers_affected.insert(buffer.read(cx).remote_id());
16820 };
16821 }
16822
16823 self.display_map.update(cx, update);
16824
16825 if auto_scroll {
16826 self.request_autoscroll(Autoscroll::fit(), cx);
16827 }
16828
16829 cx.notify();
16830 self.scrollbar_marker_state.dirty = true;
16831 self.active_indent_guides_state.dirty = true;
16832 }
16833
16834 pub fn update_fold_widths(
16835 &mut self,
16836 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16837 cx: &mut Context<Self>,
16838 ) -> bool {
16839 self.display_map
16840 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16841 }
16842
16843 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16844 self.display_map.read(cx).fold_placeholder.clone()
16845 }
16846
16847 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16848 self.buffer.update(cx, |buffer, cx| {
16849 buffer.set_all_diff_hunks_expanded(cx);
16850 });
16851 }
16852
16853 pub fn expand_all_diff_hunks(
16854 &mut self,
16855 _: &ExpandAllDiffHunks,
16856 _window: &mut Window,
16857 cx: &mut Context<Self>,
16858 ) {
16859 self.buffer.update(cx, |buffer, cx| {
16860 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16861 });
16862 }
16863
16864 pub fn toggle_selected_diff_hunks(
16865 &mut self,
16866 _: &ToggleSelectedDiffHunks,
16867 _window: &mut Window,
16868 cx: &mut Context<Self>,
16869 ) {
16870 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16871 self.toggle_diff_hunks_in_ranges(ranges, cx);
16872 }
16873
16874 pub fn diff_hunks_in_ranges<'a>(
16875 &'a self,
16876 ranges: &'a [Range<Anchor>],
16877 buffer: &'a MultiBufferSnapshot,
16878 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16879 ranges.iter().flat_map(move |range| {
16880 let end_excerpt_id = range.end.excerpt_id;
16881 let range = range.to_point(buffer);
16882 let mut peek_end = range.end;
16883 if range.end.row < buffer.max_row().0 {
16884 peek_end = Point::new(range.end.row + 1, 0);
16885 }
16886 buffer
16887 .diff_hunks_in_range(range.start..peek_end)
16888 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16889 })
16890 }
16891
16892 pub fn has_stageable_diff_hunks_in_ranges(
16893 &self,
16894 ranges: &[Range<Anchor>],
16895 snapshot: &MultiBufferSnapshot,
16896 ) -> bool {
16897 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16898 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16899 }
16900
16901 pub fn toggle_staged_selected_diff_hunks(
16902 &mut self,
16903 _: &::git::ToggleStaged,
16904 _: &mut Window,
16905 cx: &mut Context<Self>,
16906 ) {
16907 let snapshot = self.buffer.read(cx).snapshot(cx);
16908 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16909 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16910 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16911 }
16912
16913 pub fn set_render_diff_hunk_controls(
16914 &mut self,
16915 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16916 cx: &mut Context<Self>,
16917 ) {
16918 self.render_diff_hunk_controls = render_diff_hunk_controls;
16919 cx.notify();
16920 }
16921
16922 pub fn stage_and_next(
16923 &mut self,
16924 _: &::git::StageAndNext,
16925 window: &mut Window,
16926 cx: &mut Context<Self>,
16927 ) {
16928 self.do_stage_or_unstage_and_next(true, window, cx);
16929 }
16930
16931 pub fn unstage_and_next(
16932 &mut self,
16933 _: &::git::UnstageAndNext,
16934 window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) {
16937 self.do_stage_or_unstage_and_next(false, window, cx);
16938 }
16939
16940 pub fn stage_or_unstage_diff_hunks(
16941 &mut self,
16942 stage: bool,
16943 ranges: Vec<Range<Anchor>>,
16944 cx: &mut Context<Self>,
16945 ) {
16946 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16947 cx.spawn(async move |this, cx| {
16948 task.await?;
16949 this.update(cx, |this, cx| {
16950 let snapshot = this.buffer.read(cx).snapshot(cx);
16951 let chunk_by = this
16952 .diff_hunks_in_ranges(&ranges, &snapshot)
16953 .chunk_by(|hunk| hunk.buffer_id);
16954 for (buffer_id, hunks) in &chunk_by {
16955 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16956 }
16957 })
16958 })
16959 .detach_and_log_err(cx);
16960 }
16961
16962 fn save_buffers_for_ranges_if_needed(
16963 &mut self,
16964 ranges: &[Range<Anchor>],
16965 cx: &mut Context<Editor>,
16966 ) -> Task<Result<()>> {
16967 let multibuffer = self.buffer.read(cx);
16968 let snapshot = multibuffer.read(cx);
16969 let buffer_ids: HashSet<_> = ranges
16970 .iter()
16971 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16972 .collect();
16973 drop(snapshot);
16974
16975 let mut buffers = HashSet::default();
16976 for buffer_id in buffer_ids {
16977 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16978 let buffer = buffer_entity.read(cx);
16979 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16980 {
16981 buffers.insert(buffer_entity);
16982 }
16983 }
16984 }
16985
16986 if let Some(project) = &self.project {
16987 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16988 } else {
16989 Task::ready(Ok(()))
16990 }
16991 }
16992
16993 fn do_stage_or_unstage_and_next(
16994 &mut self,
16995 stage: bool,
16996 window: &mut Window,
16997 cx: &mut Context<Self>,
16998 ) {
16999 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17000
17001 if ranges.iter().any(|range| range.start != range.end) {
17002 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17003 return;
17004 }
17005
17006 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17007 let snapshot = self.snapshot(window, cx);
17008 let position = self.selections.newest::<Point>(cx).head();
17009 let mut row = snapshot
17010 .buffer_snapshot
17011 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17012 .find(|hunk| hunk.row_range.start.0 > position.row)
17013 .map(|hunk| hunk.row_range.start);
17014
17015 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17016 // Outside of the project diff editor, wrap around to the beginning.
17017 if !all_diff_hunks_expanded {
17018 row = row.or_else(|| {
17019 snapshot
17020 .buffer_snapshot
17021 .diff_hunks_in_range(Point::zero()..position)
17022 .find(|hunk| hunk.row_range.end.0 < position.row)
17023 .map(|hunk| hunk.row_range.start)
17024 });
17025 }
17026
17027 if let Some(row) = row {
17028 let destination = Point::new(row.0, 0);
17029 let autoscroll = Autoscroll::center();
17030
17031 self.unfold_ranges(&[destination..destination], false, false, cx);
17032 self.change_selections(Some(autoscroll), window, cx, |s| {
17033 s.select_ranges([destination..destination]);
17034 });
17035 }
17036 }
17037
17038 fn do_stage_or_unstage(
17039 &self,
17040 stage: bool,
17041 buffer_id: BufferId,
17042 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17043 cx: &mut App,
17044 ) -> Option<()> {
17045 let project = self.project.as_ref()?;
17046 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17047 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17048 let buffer_snapshot = buffer.read(cx).snapshot();
17049 let file_exists = buffer_snapshot
17050 .file()
17051 .is_some_and(|file| file.disk_state().exists());
17052 diff.update(cx, |diff, cx| {
17053 diff.stage_or_unstage_hunks(
17054 stage,
17055 &hunks
17056 .map(|hunk| buffer_diff::DiffHunk {
17057 buffer_range: hunk.buffer_range,
17058 diff_base_byte_range: hunk.diff_base_byte_range,
17059 secondary_status: hunk.secondary_status,
17060 range: Point::zero()..Point::zero(), // unused
17061 })
17062 .collect::<Vec<_>>(),
17063 &buffer_snapshot,
17064 file_exists,
17065 cx,
17066 )
17067 });
17068 None
17069 }
17070
17071 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17072 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17073 self.buffer
17074 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17075 }
17076
17077 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17078 self.buffer.update(cx, |buffer, cx| {
17079 let ranges = vec![Anchor::min()..Anchor::max()];
17080 if !buffer.all_diff_hunks_expanded()
17081 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17082 {
17083 buffer.collapse_diff_hunks(ranges, cx);
17084 true
17085 } else {
17086 false
17087 }
17088 })
17089 }
17090
17091 fn toggle_diff_hunks_in_ranges(
17092 &mut self,
17093 ranges: Vec<Range<Anchor>>,
17094 cx: &mut Context<Editor>,
17095 ) {
17096 self.buffer.update(cx, |buffer, cx| {
17097 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17098 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17099 })
17100 }
17101
17102 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17103 self.buffer.update(cx, |buffer, cx| {
17104 let snapshot = buffer.snapshot(cx);
17105 let excerpt_id = range.end.excerpt_id;
17106 let point_range = range.to_point(&snapshot);
17107 let expand = !buffer.single_hunk_is_expanded(range, cx);
17108 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17109 })
17110 }
17111
17112 pub(crate) fn apply_all_diff_hunks(
17113 &mut self,
17114 _: &ApplyAllDiffHunks,
17115 window: &mut Window,
17116 cx: &mut Context<Self>,
17117 ) {
17118 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17119
17120 let buffers = self.buffer.read(cx).all_buffers();
17121 for branch_buffer in buffers {
17122 branch_buffer.update(cx, |branch_buffer, cx| {
17123 branch_buffer.merge_into_base(Vec::new(), cx);
17124 });
17125 }
17126
17127 if let Some(project) = self.project.clone() {
17128 self.save(
17129 SaveOptions {
17130 format: true,
17131 autosave: false,
17132 },
17133 project,
17134 window,
17135 cx,
17136 )
17137 .detach_and_log_err(cx);
17138 }
17139 }
17140
17141 pub(crate) fn apply_selected_diff_hunks(
17142 &mut self,
17143 _: &ApplyDiffHunk,
17144 window: &mut Window,
17145 cx: &mut Context<Self>,
17146 ) {
17147 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17148 let snapshot = self.snapshot(window, cx);
17149 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17150 let mut ranges_by_buffer = HashMap::default();
17151 self.transact(window, cx, |editor, _window, cx| {
17152 for hunk in hunks {
17153 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17154 ranges_by_buffer
17155 .entry(buffer.clone())
17156 .or_insert_with(Vec::new)
17157 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17158 }
17159 }
17160
17161 for (buffer, ranges) in ranges_by_buffer {
17162 buffer.update(cx, |buffer, cx| {
17163 buffer.merge_into_base(ranges, cx);
17164 });
17165 }
17166 });
17167
17168 if let Some(project) = self.project.clone() {
17169 self.save(
17170 SaveOptions {
17171 format: true,
17172 autosave: false,
17173 },
17174 project,
17175 window,
17176 cx,
17177 )
17178 .detach_and_log_err(cx);
17179 }
17180 }
17181
17182 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17183 if hovered != self.gutter_hovered {
17184 self.gutter_hovered = hovered;
17185 cx.notify();
17186 }
17187 }
17188
17189 pub fn insert_blocks(
17190 &mut self,
17191 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17192 autoscroll: Option<Autoscroll>,
17193 cx: &mut Context<Self>,
17194 ) -> Vec<CustomBlockId> {
17195 let blocks = self
17196 .display_map
17197 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17198 if let Some(autoscroll) = autoscroll {
17199 self.request_autoscroll(autoscroll, cx);
17200 }
17201 cx.notify();
17202 blocks
17203 }
17204
17205 pub fn resize_blocks(
17206 &mut self,
17207 heights: HashMap<CustomBlockId, u32>,
17208 autoscroll: Option<Autoscroll>,
17209 cx: &mut Context<Self>,
17210 ) {
17211 self.display_map
17212 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17213 if let Some(autoscroll) = autoscroll {
17214 self.request_autoscroll(autoscroll, cx);
17215 }
17216 cx.notify();
17217 }
17218
17219 pub fn replace_blocks(
17220 &mut self,
17221 renderers: HashMap<CustomBlockId, RenderBlock>,
17222 autoscroll: Option<Autoscroll>,
17223 cx: &mut Context<Self>,
17224 ) {
17225 self.display_map
17226 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17227 if let Some(autoscroll) = autoscroll {
17228 self.request_autoscroll(autoscroll, cx);
17229 }
17230 cx.notify();
17231 }
17232
17233 pub fn remove_blocks(
17234 &mut self,
17235 block_ids: HashSet<CustomBlockId>,
17236 autoscroll: Option<Autoscroll>,
17237 cx: &mut Context<Self>,
17238 ) {
17239 self.display_map.update(cx, |display_map, cx| {
17240 display_map.remove_blocks(block_ids, cx)
17241 });
17242 if let Some(autoscroll) = autoscroll {
17243 self.request_autoscroll(autoscroll, cx);
17244 }
17245 cx.notify();
17246 }
17247
17248 pub fn row_for_block(
17249 &self,
17250 block_id: CustomBlockId,
17251 cx: &mut Context<Self>,
17252 ) -> Option<DisplayRow> {
17253 self.display_map
17254 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17255 }
17256
17257 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17258 self.focused_block = Some(focused_block);
17259 }
17260
17261 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17262 self.focused_block.take()
17263 }
17264
17265 pub fn insert_creases(
17266 &mut self,
17267 creases: impl IntoIterator<Item = Crease<Anchor>>,
17268 cx: &mut Context<Self>,
17269 ) -> Vec<CreaseId> {
17270 self.display_map
17271 .update(cx, |map, cx| map.insert_creases(creases, cx))
17272 }
17273
17274 pub fn remove_creases(
17275 &mut self,
17276 ids: impl IntoIterator<Item = CreaseId>,
17277 cx: &mut Context<Self>,
17278 ) -> Vec<(CreaseId, Range<Anchor>)> {
17279 self.display_map
17280 .update(cx, |map, cx| map.remove_creases(ids, cx))
17281 }
17282
17283 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17284 self.display_map
17285 .update(cx, |map, cx| map.snapshot(cx))
17286 .longest_row()
17287 }
17288
17289 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17290 self.display_map
17291 .update(cx, |map, cx| map.snapshot(cx))
17292 .max_point()
17293 }
17294
17295 pub fn text(&self, cx: &App) -> String {
17296 self.buffer.read(cx).read(cx).text()
17297 }
17298
17299 pub fn is_empty(&self, cx: &App) -> bool {
17300 self.buffer.read(cx).read(cx).is_empty()
17301 }
17302
17303 pub fn text_option(&self, cx: &App) -> Option<String> {
17304 let text = self.text(cx);
17305 let text = text.trim();
17306
17307 if text.is_empty() {
17308 return None;
17309 }
17310
17311 Some(text.to_string())
17312 }
17313
17314 pub fn set_text(
17315 &mut self,
17316 text: impl Into<Arc<str>>,
17317 window: &mut Window,
17318 cx: &mut Context<Self>,
17319 ) {
17320 self.transact(window, cx, |this, _, cx| {
17321 this.buffer
17322 .read(cx)
17323 .as_singleton()
17324 .expect("you can only call set_text on editors for singleton buffers")
17325 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17326 });
17327 }
17328
17329 pub fn display_text(&self, cx: &mut App) -> String {
17330 self.display_map
17331 .update(cx, |map, cx| map.snapshot(cx))
17332 .text()
17333 }
17334
17335 fn create_minimap(
17336 &self,
17337 minimap_settings: MinimapSettings,
17338 window: &mut Window,
17339 cx: &mut Context<Self>,
17340 ) -> Option<Entity<Self>> {
17341 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17342 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17343 }
17344
17345 fn initialize_new_minimap(
17346 &self,
17347 minimap_settings: MinimapSettings,
17348 window: &mut Window,
17349 cx: &mut Context<Self>,
17350 ) -> Entity<Self> {
17351 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17352
17353 let mut minimap = Editor::new_internal(
17354 EditorMode::Minimap {
17355 parent: cx.weak_entity(),
17356 },
17357 self.buffer.clone(),
17358 self.project.clone(),
17359 Some(self.display_map.clone()),
17360 window,
17361 cx,
17362 );
17363 minimap.scroll_manager.clone_state(&self.scroll_manager);
17364 minimap.set_text_style_refinement(TextStyleRefinement {
17365 font_size: Some(MINIMAP_FONT_SIZE),
17366 font_weight: Some(MINIMAP_FONT_WEIGHT),
17367 ..Default::default()
17368 });
17369 minimap.update_minimap_configuration(minimap_settings, cx);
17370 cx.new(|_| minimap)
17371 }
17372
17373 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17374 let current_line_highlight = minimap_settings
17375 .current_line_highlight
17376 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17377 self.set_current_line_highlight(Some(current_line_highlight));
17378 }
17379
17380 pub fn minimap(&self) -> Option<&Entity<Self>> {
17381 self.minimap
17382 .as_ref()
17383 .filter(|_| self.minimap_visibility.visible())
17384 }
17385
17386 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17387 let mut wrap_guides = smallvec![];
17388
17389 if self.show_wrap_guides == Some(false) {
17390 return wrap_guides;
17391 }
17392
17393 let settings = self.buffer.read(cx).language_settings(cx);
17394 if settings.show_wrap_guides {
17395 match self.soft_wrap_mode(cx) {
17396 SoftWrap::Column(soft_wrap) => {
17397 wrap_guides.push((soft_wrap as usize, true));
17398 }
17399 SoftWrap::Bounded(soft_wrap) => {
17400 wrap_guides.push((soft_wrap as usize, true));
17401 }
17402 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17403 }
17404 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17405 }
17406
17407 wrap_guides
17408 }
17409
17410 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17411 let settings = self.buffer.read(cx).language_settings(cx);
17412 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17413 match mode {
17414 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17415 SoftWrap::None
17416 }
17417 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17418 language_settings::SoftWrap::PreferredLineLength => {
17419 SoftWrap::Column(settings.preferred_line_length)
17420 }
17421 language_settings::SoftWrap::Bounded => {
17422 SoftWrap::Bounded(settings.preferred_line_length)
17423 }
17424 }
17425 }
17426
17427 pub fn set_soft_wrap_mode(
17428 &mut self,
17429 mode: language_settings::SoftWrap,
17430
17431 cx: &mut Context<Self>,
17432 ) {
17433 self.soft_wrap_mode_override = Some(mode);
17434 cx.notify();
17435 }
17436
17437 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17438 self.hard_wrap = hard_wrap;
17439 cx.notify();
17440 }
17441
17442 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17443 self.text_style_refinement = Some(style);
17444 }
17445
17446 /// called by the Element so we know what style we were most recently rendered with.
17447 pub(crate) fn set_style(
17448 &mut self,
17449 style: EditorStyle,
17450 window: &mut Window,
17451 cx: &mut Context<Self>,
17452 ) {
17453 // We intentionally do not inform the display map about the minimap style
17454 // so that wrapping is not recalculated and stays consistent for the editor
17455 // and its linked minimap.
17456 if !self.mode.is_minimap() {
17457 let rem_size = window.rem_size();
17458 self.display_map.update(cx, |map, cx| {
17459 map.set_font(
17460 style.text.font(),
17461 style.text.font_size.to_pixels(rem_size),
17462 cx,
17463 )
17464 });
17465 }
17466 self.style = Some(style);
17467 }
17468
17469 pub fn style(&self) -> Option<&EditorStyle> {
17470 self.style.as_ref()
17471 }
17472
17473 // Called by the element. This method is not designed to be called outside of the editor
17474 // element's layout code because it does not notify when rewrapping is computed synchronously.
17475 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17476 self.display_map
17477 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17478 }
17479
17480 pub fn set_soft_wrap(&mut self) {
17481 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17482 }
17483
17484 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17485 if self.soft_wrap_mode_override.is_some() {
17486 self.soft_wrap_mode_override.take();
17487 } else {
17488 let soft_wrap = match self.soft_wrap_mode(cx) {
17489 SoftWrap::GitDiff => return,
17490 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17491 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17492 language_settings::SoftWrap::None
17493 }
17494 };
17495 self.soft_wrap_mode_override = Some(soft_wrap);
17496 }
17497 cx.notify();
17498 }
17499
17500 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17501 let Some(workspace) = self.workspace() else {
17502 return;
17503 };
17504 let fs = workspace.read(cx).app_state().fs.clone();
17505 let current_show = TabBarSettings::get_global(cx).show;
17506 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17507 setting.show = Some(!current_show);
17508 });
17509 }
17510
17511 pub fn toggle_indent_guides(
17512 &mut self,
17513 _: &ToggleIndentGuides,
17514 _: &mut Window,
17515 cx: &mut Context<Self>,
17516 ) {
17517 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17518 self.buffer
17519 .read(cx)
17520 .language_settings(cx)
17521 .indent_guides
17522 .enabled
17523 });
17524 self.show_indent_guides = Some(!currently_enabled);
17525 cx.notify();
17526 }
17527
17528 fn should_show_indent_guides(&self) -> Option<bool> {
17529 self.show_indent_guides
17530 }
17531
17532 pub fn toggle_line_numbers(
17533 &mut self,
17534 _: &ToggleLineNumbers,
17535 _: &mut Window,
17536 cx: &mut Context<Self>,
17537 ) {
17538 let mut editor_settings = EditorSettings::get_global(cx).clone();
17539 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17540 EditorSettings::override_global(editor_settings, cx);
17541 }
17542
17543 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17544 if let Some(show_line_numbers) = self.show_line_numbers {
17545 return show_line_numbers;
17546 }
17547 EditorSettings::get_global(cx).gutter.line_numbers
17548 }
17549
17550 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17551 self.use_relative_line_numbers
17552 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17553 }
17554
17555 pub fn toggle_relative_line_numbers(
17556 &mut self,
17557 _: &ToggleRelativeLineNumbers,
17558 _: &mut Window,
17559 cx: &mut Context<Self>,
17560 ) {
17561 let is_relative = self.should_use_relative_line_numbers(cx);
17562 self.set_relative_line_number(Some(!is_relative), cx)
17563 }
17564
17565 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17566 self.use_relative_line_numbers = is_relative;
17567 cx.notify();
17568 }
17569
17570 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17571 self.show_gutter = show_gutter;
17572 cx.notify();
17573 }
17574
17575 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17576 self.show_scrollbars = ScrollbarAxes {
17577 horizontal: show,
17578 vertical: show,
17579 };
17580 cx.notify();
17581 }
17582
17583 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17584 self.show_scrollbars.vertical = show;
17585 cx.notify();
17586 }
17587
17588 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17589 self.show_scrollbars.horizontal = show;
17590 cx.notify();
17591 }
17592
17593 pub fn set_minimap_visibility(
17594 &mut self,
17595 minimap_visibility: MinimapVisibility,
17596 window: &mut Window,
17597 cx: &mut Context<Self>,
17598 ) {
17599 if self.minimap_visibility != minimap_visibility {
17600 if minimap_visibility.visible() && self.minimap.is_none() {
17601 let minimap_settings = EditorSettings::get_global(cx).minimap;
17602 self.minimap =
17603 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17604 }
17605 self.minimap_visibility = minimap_visibility;
17606 cx.notify();
17607 }
17608 }
17609
17610 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17611 self.set_show_scrollbars(false, cx);
17612 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17613 }
17614
17615 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17616 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17617 }
17618
17619 /// Normally the text in full mode and auto height editors is padded on the
17620 /// left side by roughly half a character width for improved hit testing.
17621 ///
17622 /// Use this method to disable this for cases where this is not wanted (e.g.
17623 /// if you want to align the editor text with some other text above or below)
17624 /// or if you want to add this padding to single-line editors.
17625 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17626 self.offset_content = offset_content;
17627 cx.notify();
17628 }
17629
17630 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17631 self.show_line_numbers = Some(show_line_numbers);
17632 cx.notify();
17633 }
17634
17635 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17636 self.disable_expand_excerpt_buttons = true;
17637 cx.notify();
17638 }
17639
17640 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17641 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17642 cx.notify();
17643 }
17644
17645 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17646 self.show_code_actions = Some(show_code_actions);
17647 cx.notify();
17648 }
17649
17650 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17651 self.show_runnables = Some(show_runnables);
17652 cx.notify();
17653 }
17654
17655 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17656 self.show_breakpoints = Some(show_breakpoints);
17657 cx.notify();
17658 }
17659
17660 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17661 if self.display_map.read(cx).masked != masked {
17662 self.display_map.update(cx, |map, _| map.masked = masked);
17663 }
17664 cx.notify()
17665 }
17666
17667 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17668 self.show_wrap_guides = Some(show_wrap_guides);
17669 cx.notify();
17670 }
17671
17672 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17673 self.show_indent_guides = Some(show_indent_guides);
17674 cx.notify();
17675 }
17676
17677 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17678 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17679 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17680 if let Some(dir) = file.abs_path(cx).parent() {
17681 return Some(dir.to_owned());
17682 }
17683 }
17684
17685 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17686 return Some(project_path.path.to_path_buf());
17687 }
17688 }
17689
17690 None
17691 }
17692
17693 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17694 self.active_excerpt(cx)?
17695 .1
17696 .read(cx)
17697 .file()
17698 .and_then(|f| f.as_local())
17699 }
17700
17701 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17702 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17703 let buffer = buffer.read(cx);
17704 if let Some(project_path) = buffer.project_path(cx) {
17705 let project = self.project.as_ref()?.read(cx);
17706 project.absolute_path(&project_path, cx)
17707 } else {
17708 buffer
17709 .file()
17710 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17711 }
17712 })
17713 }
17714
17715 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17716 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17717 let project_path = buffer.read(cx).project_path(cx)?;
17718 let project = self.project.as_ref()?.read(cx);
17719 let entry = project.entry_for_path(&project_path, cx)?;
17720 let path = entry.path.to_path_buf();
17721 Some(path)
17722 })
17723 }
17724
17725 pub fn reveal_in_finder(
17726 &mut self,
17727 _: &RevealInFileManager,
17728 _window: &mut Window,
17729 cx: &mut Context<Self>,
17730 ) {
17731 if let Some(target) = self.target_file(cx) {
17732 cx.reveal_path(&target.abs_path(cx));
17733 }
17734 }
17735
17736 pub fn copy_path(
17737 &mut self,
17738 _: &zed_actions::workspace::CopyPath,
17739 _window: &mut Window,
17740 cx: &mut Context<Self>,
17741 ) {
17742 if let Some(path) = self.target_file_abs_path(cx) {
17743 if let Some(path) = path.to_str() {
17744 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17745 }
17746 }
17747 }
17748
17749 pub fn copy_relative_path(
17750 &mut self,
17751 _: &zed_actions::workspace::CopyRelativePath,
17752 _window: &mut Window,
17753 cx: &mut Context<Self>,
17754 ) {
17755 if let Some(path) = self.target_file_path(cx) {
17756 if let Some(path) = path.to_str() {
17757 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17758 }
17759 }
17760 }
17761
17762 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17763 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17764 buffer.read(cx).project_path(cx)
17765 } else {
17766 None
17767 }
17768 }
17769
17770 // Returns true if the editor handled a go-to-line request
17771 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17772 maybe!({
17773 let breakpoint_store = self.breakpoint_store.as_ref()?;
17774
17775 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17776 else {
17777 self.clear_row_highlights::<ActiveDebugLine>();
17778 return None;
17779 };
17780
17781 let position = active_stack_frame.position;
17782 let buffer_id = position.buffer_id?;
17783 let snapshot = self
17784 .project
17785 .as_ref()?
17786 .read(cx)
17787 .buffer_for_id(buffer_id, cx)?
17788 .read(cx)
17789 .snapshot();
17790
17791 let mut handled = false;
17792 for (id, ExcerptRange { context, .. }) in
17793 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17794 {
17795 if context.start.cmp(&position, &snapshot).is_ge()
17796 || context.end.cmp(&position, &snapshot).is_lt()
17797 {
17798 continue;
17799 }
17800 let snapshot = self.buffer.read(cx).snapshot(cx);
17801 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17802
17803 handled = true;
17804 self.clear_row_highlights::<ActiveDebugLine>();
17805
17806 self.go_to_line::<ActiveDebugLine>(
17807 multibuffer_anchor,
17808 Some(cx.theme().colors().editor_debugger_active_line_background),
17809 window,
17810 cx,
17811 );
17812
17813 cx.notify();
17814 }
17815
17816 handled.then_some(())
17817 })
17818 .is_some()
17819 }
17820
17821 pub fn copy_file_name_without_extension(
17822 &mut self,
17823 _: &CopyFileNameWithoutExtension,
17824 _: &mut Window,
17825 cx: &mut Context<Self>,
17826 ) {
17827 if let Some(file) = self.target_file(cx) {
17828 if let Some(file_stem) = file.path().file_stem() {
17829 if let Some(name) = file_stem.to_str() {
17830 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17831 }
17832 }
17833 }
17834 }
17835
17836 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17837 if let Some(file) = self.target_file(cx) {
17838 if let Some(file_name) = file.path().file_name() {
17839 if let Some(name) = file_name.to_str() {
17840 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17841 }
17842 }
17843 }
17844 }
17845
17846 pub fn toggle_git_blame(
17847 &mut self,
17848 _: &::git::Blame,
17849 window: &mut Window,
17850 cx: &mut Context<Self>,
17851 ) {
17852 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17853
17854 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17855 self.start_git_blame(true, window, cx);
17856 }
17857
17858 cx.notify();
17859 }
17860
17861 pub fn toggle_git_blame_inline(
17862 &mut self,
17863 _: &ToggleGitBlameInline,
17864 window: &mut Window,
17865 cx: &mut Context<Self>,
17866 ) {
17867 self.toggle_git_blame_inline_internal(true, window, cx);
17868 cx.notify();
17869 }
17870
17871 pub fn open_git_blame_commit(
17872 &mut self,
17873 _: &OpenGitBlameCommit,
17874 window: &mut Window,
17875 cx: &mut Context<Self>,
17876 ) {
17877 self.open_git_blame_commit_internal(window, cx);
17878 }
17879
17880 fn open_git_blame_commit_internal(
17881 &mut self,
17882 window: &mut Window,
17883 cx: &mut Context<Self>,
17884 ) -> Option<()> {
17885 let blame = self.blame.as_ref()?;
17886 let snapshot = self.snapshot(window, cx);
17887 let cursor = self.selections.newest::<Point>(cx).head();
17888 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17889 let blame_entry = blame
17890 .update(cx, |blame, cx| {
17891 blame
17892 .blame_for_rows(
17893 &[RowInfo {
17894 buffer_id: Some(buffer.remote_id()),
17895 buffer_row: Some(point.row),
17896 ..Default::default()
17897 }],
17898 cx,
17899 )
17900 .next()
17901 })
17902 .flatten()?;
17903 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17904 let repo = blame.read(cx).repository(cx)?;
17905 let workspace = self.workspace()?.downgrade();
17906 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17907 None
17908 }
17909
17910 pub fn git_blame_inline_enabled(&self) -> bool {
17911 self.git_blame_inline_enabled
17912 }
17913
17914 pub fn toggle_selection_menu(
17915 &mut self,
17916 _: &ToggleSelectionMenu,
17917 _: &mut Window,
17918 cx: &mut Context<Self>,
17919 ) {
17920 self.show_selection_menu = self
17921 .show_selection_menu
17922 .map(|show_selections_menu| !show_selections_menu)
17923 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17924
17925 cx.notify();
17926 }
17927
17928 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17929 self.show_selection_menu
17930 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17931 }
17932
17933 fn start_git_blame(
17934 &mut self,
17935 user_triggered: bool,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) {
17939 if let Some(project) = self.project.as_ref() {
17940 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17941 return;
17942 };
17943
17944 if buffer.read(cx).file().is_none() {
17945 return;
17946 }
17947
17948 let focused = self.focus_handle(cx).contains_focused(window, cx);
17949
17950 let project = project.clone();
17951 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17952 self.blame_subscription =
17953 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17954 self.blame = Some(blame);
17955 }
17956 }
17957
17958 fn toggle_git_blame_inline_internal(
17959 &mut self,
17960 user_triggered: bool,
17961 window: &mut Window,
17962 cx: &mut Context<Self>,
17963 ) {
17964 if self.git_blame_inline_enabled {
17965 self.git_blame_inline_enabled = false;
17966 self.show_git_blame_inline = false;
17967 self.show_git_blame_inline_delay_task.take();
17968 } else {
17969 self.git_blame_inline_enabled = true;
17970 self.start_git_blame_inline(user_triggered, window, cx);
17971 }
17972
17973 cx.notify();
17974 }
17975
17976 fn start_git_blame_inline(
17977 &mut self,
17978 user_triggered: bool,
17979 window: &mut Window,
17980 cx: &mut Context<Self>,
17981 ) {
17982 self.start_git_blame(user_triggered, window, cx);
17983
17984 if ProjectSettings::get_global(cx)
17985 .git
17986 .inline_blame_delay()
17987 .is_some()
17988 {
17989 self.start_inline_blame_timer(window, cx);
17990 } else {
17991 self.show_git_blame_inline = true
17992 }
17993 }
17994
17995 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17996 self.blame.as_ref()
17997 }
17998
17999 pub fn show_git_blame_gutter(&self) -> bool {
18000 self.show_git_blame_gutter
18001 }
18002
18003 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18004 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18005 }
18006
18007 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18008 self.show_git_blame_inline
18009 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18010 && !self.newest_selection_head_on_empty_line(cx)
18011 && self.has_blame_entries(cx)
18012 }
18013
18014 fn has_blame_entries(&self, cx: &App) -> bool {
18015 self.blame()
18016 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18017 }
18018
18019 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18020 let cursor_anchor = self.selections.newest_anchor().head();
18021
18022 let snapshot = self.buffer.read(cx).snapshot(cx);
18023 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18024
18025 snapshot.line_len(buffer_row) == 0
18026 }
18027
18028 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18029 let buffer_and_selection = maybe!({
18030 let selection = self.selections.newest::<Point>(cx);
18031 let selection_range = selection.range();
18032
18033 let multi_buffer = self.buffer().read(cx);
18034 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18035 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18036
18037 let (buffer, range, _) = if selection.reversed {
18038 buffer_ranges.first()
18039 } else {
18040 buffer_ranges.last()
18041 }?;
18042
18043 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18044 ..text::ToPoint::to_point(&range.end, &buffer).row;
18045 Some((
18046 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18047 selection,
18048 ))
18049 });
18050
18051 let Some((buffer, selection)) = buffer_and_selection else {
18052 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18053 };
18054
18055 let Some(project) = self.project.as_ref() else {
18056 return Task::ready(Err(anyhow!("editor does not have project")));
18057 };
18058
18059 project.update(cx, |project, cx| {
18060 project.get_permalink_to_line(&buffer, selection, cx)
18061 })
18062 }
18063
18064 pub fn copy_permalink_to_line(
18065 &mut self,
18066 _: &CopyPermalinkToLine,
18067 window: &mut Window,
18068 cx: &mut Context<Self>,
18069 ) {
18070 let permalink_task = self.get_permalink_to_line(cx);
18071 let workspace = self.workspace();
18072
18073 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18074 Ok(permalink) => {
18075 cx.update(|_, cx| {
18076 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18077 })
18078 .ok();
18079 }
18080 Err(err) => {
18081 let message = format!("Failed to copy permalink: {err}");
18082
18083 anyhow::Result::<()>::Err(err).log_err();
18084
18085 if let Some(workspace) = workspace {
18086 workspace
18087 .update_in(cx, |workspace, _, cx| {
18088 struct CopyPermalinkToLine;
18089
18090 workspace.show_toast(
18091 Toast::new(
18092 NotificationId::unique::<CopyPermalinkToLine>(),
18093 message,
18094 ),
18095 cx,
18096 )
18097 })
18098 .ok();
18099 }
18100 }
18101 })
18102 .detach();
18103 }
18104
18105 pub fn copy_file_location(
18106 &mut self,
18107 _: &CopyFileLocation,
18108 _: &mut Window,
18109 cx: &mut Context<Self>,
18110 ) {
18111 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18112 if let Some(file) = self.target_file(cx) {
18113 if let Some(path) = file.path().to_str() {
18114 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18115 }
18116 }
18117 }
18118
18119 pub fn open_permalink_to_line(
18120 &mut self,
18121 _: &OpenPermalinkToLine,
18122 window: &mut Window,
18123 cx: &mut Context<Self>,
18124 ) {
18125 let permalink_task = self.get_permalink_to_line(cx);
18126 let workspace = self.workspace();
18127
18128 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18129 Ok(permalink) => {
18130 cx.update(|_, cx| {
18131 cx.open_url(permalink.as_ref());
18132 })
18133 .ok();
18134 }
18135 Err(err) => {
18136 let message = format!("Failed to open permalink: {err}");
18137
18138 anyhow::Result::<()>::Err(err).log_err();
18139
18140 if let Some(workspace) = workspace {
18141 workspace
18142 .update(cx, |workspace, cx| {
18143 struct OpenPermalinkToLine;
18144
18145 workspace.show_toast(
18146 Toast::new(
18147 NotificationId::unique::<OpenPermalinkToLine>(),
18148 message,
18149 ),
18150 cx,
18151 )
18152 })
18153 .ok();
18154 }
18155 }
18156 })
18157 .detach();
18158 }
18159
18160 pub fn insert_uuid_v4(
18161 &mut self,
18162 _: &InsertUuidV4,
18163 window: &mut Window,
18164 cx: &mut Context<Self>,
18165 ) {
18166 self.insert_uuid(UuidVersion::V4, window, cx);
18167 }
18168
18169 pub fn insert_uuid_v7(
18170 &mut self,
18171 _: &InsertUuidV7,
18172 window: &mut Window,
18173 cx: &mut Context<Self>,
18174 ) {
18175 self.insert_uuid(UuidVersion::V7, window, cx);
18176 }
18177
18178 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18179 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18180 self.transact(window, cx, |this, window, cx| {
18181 let edits = this
18182 .selections
18183 .all::<Point>(cx)
18184 .into_iter()
18185 .map(|selection| {
18186 let uuid = match version {
18187 UuidVersion::V4 => uuid::Uuid::new_v4(),
18188 UuidVersion::V7 => uuid::Uuid::now_v7(),
18189 };
18190
18191 (selection.range(), uuid.to_string())
18192 });
18193 this.edit(edits, cx);
18194 this.refresh_inline_completion(true, false, window, cx);
18195 });
18196 }
18197
18198 pub fn open_selections_in_multibuffer(
18199 &mut self,
18200 _: &OpenSelectionsInMultibuffer,
18201 window: &mut Window,
18202 cx: &mut Context<Self>,
18203 ) {
18204 let multibuffer = self.buffer.read(cx);
18205
18206 let Some(buffer) = multibuffer.as_singleton() else {
18207 return;
18208 };
18209
18210 let Some(workspace) = self.workspace() else {
18211 return;
18212 };
18213
18214 let locations = self
18215 .selections
18216 .disjoint_anchors()
18217 .iter()
18218 .map(|range| Location {
18219 buffer: buffer.clone(),
18220 range: range.start.text_anchor..range.end.text_anchor,
18221 })
18222 .collect::<Vec<_>>();
18223
18224 let title = multibuffer.title(cx).to_string();
18225
18226 cx.spawn_in(window, async move |_, cx| {
18227 workspace.update_in(cx, |workspace, window, cx| {
18228 Self::open_locations_in_multibuffer(
18229 workspace,
18230 locations,
18231 format!("Selections for '{title}'"),
18232 false,
18233 MultibufferSelectionMode::All,
18234 window,
18235 cx,
18236 );
18237 })
18238 })
18239 .detach();
18240 }
18241
18242 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18243 /// last highlight added will be used.
18244 ///
18245 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18246 pub fn highlight_rows<T: 'static>(
18247 &mut self,
18248 range: Range<Anchor>,
18249 color: Hsla,
18250 options: RowHighlightOptions,
18251 cx: &mut Context<Self>,
18252 ) {
18253 let snapshot = self.buffer().read(cx).snapshot(cx);
18254 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18255 let ix = row_highlights.binary_search_by(|highlight| {
18256 Ordering::Equal
18257 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18258 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18259 });
18260
18261 if let Err(mut ix) = ix {
18262 let index = post_inc(&mut self.highlight_order);
18263
18264 // If this range intersects with the preceding highlight, then merge it with
18265 // the preceding highlight. Otherwise insert a new highlight.
18266 let mut merged = false;
18267 if ix > 0 {
18268 let prev_highlight = &mut row_highlights[ix - 1];
18269 if prev_highlight
18270 .range
18271 .end
18272 .cmp(&range.start, &snapshot)
18273 .is_ge()
18274 {
18275 ix -= 1;
18276 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18277 prev_highlight.range.end = range.end;
18278 }
18279 merged = true;
18280 prev_highlight.index = index;
18281 prev_highlight.color = color;
18282 prev_highlight.options = options;
18283 }
18284 }
18285
18286 if !merged {
18287 row_highlights.insert(
18288 ix,
18289 RowHighlight {
18290 range: range.clone(),
18291 index,
18292 color,
18293 options,
18294 type_id: TypeId::of::<T>(),
18295 },
18296 );
18297 }
18298
18299 // If any of the following highlights intersect with this one, merge them.
18300 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18301 let highlight = &row_highlights[ix];
18302 if next_highlight
18303 .range
18304 .start
18305 .cmp(&highlight.range.end, &snapshot)
18306 .is_le()
18307 {
18308 if next_highlight
18309 .range
18310 .end
18311 .cmp(&highlight.range.end, &snapshot)
18312 .is_gt()
18313 {
18314 row_highlights[ix].range.end = next_highlight.range.end;
18315 }
18316 row_highlights.remove(ix + 1);
18317 } else {
18318 break;
18319 }
18320 }
18321 }
18322 }
18323
18324 /// Remove any highlighted row ranges of the given type that intersect the
18325 /// given ranges.
18326 pub fn remove_highlighted_rows<T: 'static>(
18327 &mut self,
18328 ranges_to_remove: Vec<Range<Anchor>>,
18329 cx: &mut Context<Self>,
18330 ) {
18331 let snapshot = self.buffer().read(cx).snapshot(cx);
18332 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18333 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18334 row_highlights.retain(|highlight| {
18335 while let Some(range_to_remove) = ranges_to_remove.peek() {
18336 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18337 Ordering::Less | Ordering::Equal => {
18338 ranges_to_remove.next();
18339 }
18340 Ordering::Greater => {
18341 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18342 Ordering::Less | Ordering::Equal => {
18343 return false;
18344 }
18345 Ordering::Greater => break,
18346 }
18347 }
18348 }
18349 }
18350
18351 true
18352 })
18353 }
18354
18355 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18356 pub fn clear_row_highlights<T: 'static>(&mut self) {
18357 self.highlighted_rows.remove(&TypeId::of::<T>());
18358 }
18359
18360 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18361 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18362 self.highlighted_rows
18363 .get(&TypeId::of::<T>())
18364 .map_or(&[] as &[_], |vec| vec.as_slice())
18365 .iter()
18366 .map(|highlight| (highlight.range.clone(), highlight.color))
18367 }
18368
18369 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18370 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18371 /// Allows to ignore certain kinds of highlights.
18372 pub fn highlighted_display_rows(
18373 &self,
18374 window: &mut Window,
18375 cx: &mut App,
18376 ) -> BTreeMap<DisplayRow, LineHighlight> {
18377 let snapshot = self.snapshot(window, cx);
18378 let mut used_highlight_orders = HashMap::default();
18379 self.highlighted_rows
18380 .iter()
18381 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18382 .fold(
18383 BTreeMap::<DisplayRow, LineHighlight>::new(),
18384 |mut unique_rows, highlight| {
18385 let start = highlight.range.start.to_display_point(&snapshot);
18386 let end = highlight.range.end.to_display_point(&snapshot);
18387 let start_row = start.row().0;
18388 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18389 && end.column() == 0
18390 {
18391 end.row().0.saturating_sub(1)
18392 } else {
18393 end.row().0
18394 };
18395 for row in start_row..=end_row {
18396 let used_index =
18397 used_highlight_orders.entry(row).or_insert(highlight.index);
18398 if highlight.index >= *used_index {
18399 *used_index = highlight.index;
18400 unique_rows.insert(
18401 DisplayRow(row),
18402 LineHighlight {
18403 include_gutter: highlight.options.include_gutter,
18404 border: None,
18405 background: highlight.color.into(),
18406 type_id: Some(highlight.type_id),
18407 },
18408 );
18409 }
18410 }
18411 unique_rows
18412 },
18413 )
18414 }
18415
18416 pub fn highlighted_display_row_for_autoscroll(
18417 &self,
18418 snapshot: &DisplaySnapshot,
18419 ) -> Option<DisplayRow> {
18420 self.highlighted_rows
18421 .values()
18422 .flat_map(|highlighted_rows| highlighted_rows.iter())
18423 .filter_map(|highlight| {
18424 if highlight.options.autoscroll {
18425 Some(highlight.range.start.to_display_point(snapshot).row())
18426 } else {
18427 None
18428 }
18429 })
18430 .min()
18431 }
18432
18433 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18434 self.highlight_background::<SearchWithinRange>(
18435 ranges,
18436 |colors| colors.editor_document_highlight_read_background,
18437 cx,
18438 )
18439 }
18440
18441 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18442 self.breadcrumb_header = Some(new_header);
18443 }
18444
18445 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18446 self.clear_background_highlights::<SearchWithinRange>(cx);
18447 }
18448
18449 pub fn highlight_background<T: 'static>(
18450 &mut self,
18451 ranges: &[Range<Anchor>],
18452 color_fetcher: fn(&ThemeColors) -> Hsla,
18453 cx: &mut Context<Self>,
18454 ) {
18455 self.background_highlights
18456 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18457 self.scrollbar_marker_state.dirty = true;
18458 cx.notify();
18459 }
18460
18461 pub fn clear_background_highlights<T: 'static>(
18462 &mut self,
18463 cx: &mut Context<Self>,
18464 ) -> Option<BackgroundHighlight> {
18465 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18466 if !text_highlights.1.is_empty() {
18467 self.scrollbar_marker_state.dirty = true;
18468 cx.notify();
18469 }
18470 Some(text_highlights)
18471 }
18472
18473 pub fn highlight_gutter<T: 'static>(
18474 &mut self,
18475 ranges: impl Into<Vec<Range<Anchor>>>,
18476 color_fetcher: fn(&App) -> Hsla,
18477 cx: &mut Context<Self>,
18478 ) {
18479 self.gutter_highlights
18480 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18481 cx.notify();
18482 }
18483
18484 pub fn clear_gutter_highlights<T: 'static>(
18485 &mut self,
18486 cx: &mut Context<Self>,
18487 ) -> Option<GutterHighlight> {
18488 cx.notify();
18489 self.gutter_highlights.remove(&TypeId::of::<T>())
18490 }
18491
18492 pub fn insert_gutter_highlight<T: 'static>(
18493 &mut self,
18494 range: Range<Anchor>,
18495 color_fetcher: fn(&App) -> Hsla,
18496 cx: &mut Context<Self>,
18497 ) {
18498 let snapshot = self.buffer().read(cx).snapshot(cx);
18499 let mut highlights = self
18500 .gutter_highlights
18501 .remove(&TypeId::of::<T>())
18502 .map(|(_, highlights)| highlights)
18503 .unwrap_or_default();
18504 let ix = highlights.binary_search_by(|highlight| {
18505 Ordering::Equal
18506 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18507 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18508 });
18509 if let Err(ix) = ix {
18510 highlights.insert(ix, range);
18511 }
18512 self.gutter_highlights
18513 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18514 }
18515
18516 pub fn remove_gutter_highlights<T: 'static>(
18517 &mut self,
18518 ranges_to_remove: Vec<Range<Anchor>>,
18519 cx: &mut Context<Self>,
18520 ) {
18521 let snapshot = self.buffer().read(cx).snapshot(cx);
18522 let Some((color_fetcher, mut gutter_highlights)) =
18523 self.gutter_highlights.remove(&TypeId::of::<T>())
18524 else {
18525 return;
18526 };
18527 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18528 gutter_highlights.retain(|highlight| {
18529 while let Some(range_to_remove) = ranges_to_remove.peek() {
18530 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18531 Ordering::Less | Ordering::Equal => {
18532 ranges_to_remove.next();
18533 }
18534 Ordering::Greater => {
18535 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18536 Ordering::Less | Ordering::Equal => {
18537 return false;
18538 }
18539 Ordering::Greater => break,
18540 }
18541 }
18542 }
18543 }
18544
18545 true
18546 });
18547 self.gutter_highlights
18548 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18549 }
18550
18551 #[cfg(feature = "test-support")]
18552 pub fn all_text_background_highlights(
18553 &self,
18554 window: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18557 let snapshot = self.snapshot(window, cx);
18558 let buffer = &snapshot.buffer_snapshot;
18559 let start = buffer.anchor_before(0);
18560 let end = buffer.anchor_after(buffer.len());
18561 let theme = cx.theme().colors();
18562 self.background_highlights_in_range(start..end, &snapshot, theme)
18563 }
18564
18565 #[cfg(feature = "test-support")]
18566 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18567 let snapshot = self.buffer().read(cx).snapshot(cx);
18568
18569 let highlights = self
18570 .background_highlights
18571 .get(&TypeId::of::<items::BufferSearchHighlights>());
18572
18573 if let Some((_color, ranges)) = highlights {
18574 ranges
18575 .iter()
18576 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18577 .collect_vec()
18578 } else {
18579 vec![]
18580 }
18581 }
18582
18583 fn document_highlights_for_position<'a>(
18584 &'a self,
18585 position: Anchor,
18586 buffer: &'a MultiBufferSnapshot,
18587 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18588 let read_highlights = self
18589 .background_highlights
18590 .get(&TypeId::of::<DocumentHighlightRead>())
18591 .map(|h| &h.1);
18592 let write_highlights = self
18593 .background_highlights
18594 .get(&TypeId::of::<DocumentHighlightWrite>())
18595 .map(|h| &h.1);
18596 let left_position = position.bias_left(buffer);
18597 let right_position = position.bias_right(buffer);
18598 read_highlights
18599 .into_iter()
18600 .chain(write_highlights)
18601 .flat_map(move |ranges| {
18602 let start_ix = match ranges.binary_search_by(|probe| {
18603 let cmp = probe.end.cmp(&left_position, buffer);
18604 if cmp.is_ge() {
18605 Ordering::Greater
18606 } else {
18607 Ordering::Less
18608 }
18609 }) {
18610 Ok(i) | Err(i) => i,
18611 };
18612
18613 ranges[start_ix..]
18614 .iter()
18615 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18616 })
18617 }
18618
18619 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18620 self.background_highlights
18621 .get(&TypeId::of::<T>())
18622 .map_or(false, |(_, highlights)| !highlights.is_empty())
18623 }
18624
18625 pub fn background_highlights_in_range(
18626 &self,
18627 search_range: Range<Anchor>,
18628 display_snapshot: &DisplaySnapshot,
18629 theme: &ThemeColors,
18630 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18631 let mut results = Vec::new();
18632 for (color_fetcher, ranges) in self.background_highlights.values() {
18633 let color = color_fetcher(theme);
18634 let start_ix = match ranges.binary_search_by(|probe| {
18635 let cmp = probe
18636 .end
18637 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18638 if cmp.is_gt() {
18639 Ordering::Greater
18640 } else {
18641 Ordering::Less
18642 }
18643 }) {
18644 Ok(i) | Err(i) => i,
18645 };
18646 for range in &ranges[start_ix..] {
18647 if range
18648 .start
18649 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18650 .is_ge()
18651 {
18652 break;
18653 }
18654
18655 let start = range.start.to_display_point(display_snapshot);
18656 let end = range.end.to_display_point(display_snapshot);
18657 results.push((start..end, color))
18658 }
18659 }
18660 results
18661 }
18662
18663 pub fn background_highlight_row_ranges<T: 'static>(
18664 &self,
18665 search_range: Range<Anchor>,
18666 display_snapshot: &DisplaySnapshot,
18667 count: usize,
18668 ) -> Vec<RangeInclusive<DisplayPoint>> {
18669 let mut results = Vec::new();
18670 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18671 return vec![];
18672 };
18673
18674 let start_ix = match ranges.binary_search_by(|probe| {
18675 let cmp = probe
18676 .end
18677 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18678 if cmp.is_gt() {
18679 Ordering::Greater
18680 } else {
18681 Ordering::Less
18682 }
18683 }) {
18684 Ok(i) | Err(i) => i,
18685 };
18686 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18687 if let (Some(start_display), Some(end_display)) = (start, end) {
18688 results.push(
18689 start_display.to_display_point(display_snapshot)
18690 ..=end_display.to_display_point(display_snapshot),
18691 );
18692 }
18693 };
18694 let mut start_row: Option<Point> = None;
18695 let mut end_row: Option<Point> = None;
18696 if ranges.len() > count {
18697 return Vec::new();
18698 }
18699 for range in &ranges[start_ix..] {
18700 if range
18701 .start
18702 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18703 .is_ge()
18704 {
18705 break;
18706 }
18707 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18708 if let Some(current_row) = &end_row {
18709 if end.row == current_row.row {
18710 continue;
18711 }
18712 }
18713 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18714 if start_row.is_none() {
18715 assert_eq!(end_row, None);
18716 start_row = Some(start);
18717 end_row = Some(end);
18718 continue;
18719 }
18720 if let Some(current_end) = end_row.as_mut() {
18721 if start.row > current_end.row + 1 {
18722 push_region(start_row, end_row);
18723 start_row = Some(start);
18724 end_row = Some(end);
18725 } else {
18726 // Merge two hunks.
18727 *current_end = end;
18728 }
18729 } else {
18730 unreachable!();
18731 }
18732 }
18733 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18734 push_region(start_row, end_row);
18735 results
18736 }
18737
18738 pub fn gutter_highlights_in_range(
18739 &self,
18740 search_range: Range<Anchor>,
18741 display_snapshot: &DisplaySnapshot,
18742 cx: &App,
18743 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18744 let mut results = Vec::new();
18745 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18746 let color = color_fetcher(cx);
18747 let start_ix = match ranges.binary_search_by(|probe| {
18748 let cmp = probe
18749 .end
18750 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18751 if cmp.is_gt() {
18752 Ordering::Greater
18753 } else {
18754 Ordering::Less
18755 }
18756 }) {
18757 Ok(i) | Err(i) => i,
18758 };
18759 for range in &ranges[start_ix..] {
18760 if range
18761 .start
18762 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18763 .is_ge()
18764 {
18765 break;
18766 }
18767
18768 let start = range.start.to_display_point(display_snapshot);
18769 let end = range.end.to_display_point(display_snapshot);
18770 results.push((start..end, color))
18771 }
18772 }
18773 results
18774 }
18775
18776 /// Get the text ranges corresponding to the redaction query
18777 pub fn redacted_ranges(
18778 &self,
18779 search_range: Range<Anchor>,
18780 display_snapshot: &DisplaySnapshot,
18781 cx: &App,
18782 ) -> Vec<Range<DisplayPoint>> {
18783 display_snapshot
18784 .buffer_snapshot
18785 .redacted_ranges(search_range, |file| {
18786 if let Some(file) = file {
18787 file.is_private()
18788 && EditorSettings::get(
18789 Some(SettingsLocation {
18790 worktree_id: file.worktree_id(cx),
18791 path: file.path().as_ref(),
18792 }),
18793 cx,
18794 )
18795 .redact_private_values
18796 } else {
18797 false
18798 }
18799 })
18800 .map(|range| {
18801 range.start.to_display_point(display_snapshot)
18802 ..range.end.to_display_point(display_snapshot)
18803 })
18804 .collect()
18805 }
18806
18807 pub fn highlight_text<T: 'static>(
18808 &mut self,
18809 ranges: Vec<Range<Anchor>>,
18810 style: HighlightStyle,
18811 cx: &mut Context<Self>,
18812 ) {
18813 self.display_map.update(cx, |map, _| {
18814 map.highlight_text(TypeId::of::<T>(), ranges, style)
18815 });
18816 cx.notify();
18817 }
18818
18819 pub(crate) fn highlight_inlays<T: 'static>(
18820 &mut self,
18821 highlights: Vec<InlayHighlight>,
18822 style: HighlightStyle,
18823 cx: &mut Context<Self>,
18824 ) {
18825 self.display_map.update(cx, |map, _| {
18826 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18827 });
18828 cx.notify();
18829 }
18830
18831 pub fn text_highlights<'a, T: 'static>(
18832 &'a self,
18833 cx: &'a App,
18834 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18835 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18836 }
18837
18838 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18839 let cleared = self
18840 .display_map
18841 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18842 if cleared {
18843 cx.notify();
18844 }
18845 }
18846
18847 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18848 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18849 && self.focus_handle.is_focused(window)
18850 }
18851
18852 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18853 self.show_cursor_when_unfocused = is_enabled;
18854 cx.notify();
18855 }
18856
18857 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18858 cx.notify();
18859 }
18860
18861 fn on_debug_session_event(
18862 &mut self,
18863 _session: Entity<Session>,
18864 event: &SessionEvent,
18865 cx: &mut Context<Self>,
18866 ) {
18867 match event {
18868 SessionEvent::InvalidateInlineValue => {
18869 self.refresh_inline_values(cx);
18870 }
18871 _ => {}
18872 }
18873 }
18874
18875 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18876 let Some(project) = self.project.clone() else {
18877 return;
18878 };
18879
18880 if !self.inline_value_cache.enabled {
18881 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18882 self.splice_inlays(&inlays, Vec::new(), cx);
18883 return;
18884 }
18885
18886 let current_execution_position = self
18887 .highlighted_rows
18888 .get(&TypeId::of::<ActiveDebugLine>())
18889 .and_then(|lines| lines.last().map(|line| line.range.start));
18890
18891 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18892 let inline_values = editor
18893 .update(cx, |editor, cx| {
18894 let Some(current_execution_position) = current_execution_position else {
18895 return Some(Task::ready(Ok(Vec::new())));
18896 };
18897
18898 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18899 let snapshot = buffer.snapshot(cx);
18900
18901 let excerpt = snapshot.excerpt_containing(
18902 current_execution_position..current_execution_position,
18903 )?;
18904
18905 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18906 })?;
18907
18908 let range =
18909 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18910
18911 project.inline_values(buffer, range, cx)
18912 })
18913 .ok()
18914 .flatten()?
18915 .await
18916 .context("refreshing debugger inlays")
18917 .log_err()?;
18918
18919 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18920
18921 for (buffer_id, inline_value) in inline_values
18922 .into_iter()
18923 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18924 {
18925 buffer_inline_values
18926 .entry(buffer_id)
18927 .or_default()
18928 .push(inline_value);
18929 }
18930
18931 editor
18932 .update(cx, |editor, cx| {
18933 let snapshot = editor.buffer.read(cx).snapshot(cx);
18934 let mut new_inlays = Vec::default();
18935
18936 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18937 let buffer_id = buffer_snapshot.remote_id();
18938 buffer_inline_values
18939 .get(&buffer_id)
18940 .into_iter()
18941 .flatten()
18942 .for_each(|hint| {
18943 let inlay = Inlay::debugger_hint(
18944 post_inc(&mut editor.next_inlay_id),
18945 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18946 hint.text(),
18947 );
18948
18949 new_inlays.push(inlay);
18950 });
18951 }
18952
18953 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18954 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18955
18956 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18957 })
18958 .ok()?;
18959 Some(())
18960 });
18961 }
18962
18963 fn on_buffer_event(
18964 &mut self,
18965 multibuffer: &Entity<MultiBuffer>,
18966 event: &multi_buffer::Event,
18967 window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 match event {
18971 multi_buffer::Event::Edited {
18972 singleton_buffer_edited,
18973 edited_buffer,
18974 } => {
18975 self.scrollbar_marker_state.dirty = true;
18976 self.active_indent_guides_state.dirty = true;
18977 self.refresh_active_diagnostics(cx);
18978 self.refresh_code_actions(window, cx);
18979 self.refresh_selected_text_highlights(true, window, cx);
18980 refresh_matching_bracket_highlights(self, window, cx);
18981 if self.has_active_inline_completion() {
18982 self.update_visible_inline_completion(window, cx);
18983 }
18984 if let Some(project) = self.project.as_ref() {
18985 if let Some(edited_buffer) = edited_buffer {
18986 project.update(cx, |project, cx| {
18987 self.registered_buffers
18988 .entry(edited_buffer.read(cx).remote_id())
18989 .or_insert_with(|| {
18990 project
18991 .register_buffer_with_language_servers(&edited_buffer, cx)
18992 });
18993 });
18994 if edited_buffer.read(cx).file().is_some() {
18995 self.pull_diagnostics(
18996 Some(edited_buffer.read(cx).remote_id()),
18997 window,
18998 cx,
18999 );
19000 }
19001 }
19002 }
19003 cx.emit(EditorEvent::BufferEdited);
19004 cx.emit(SearchEvent::MatchesInvalidated);
19005 if *singleton_buffer_edited {
19006 if let Some(buffer) = edited_buffer {
19007 if buffer.read(cx).file().is_none() {
19008 cx.emit(EditorEvent::TitleChanged);
19009 }
19010 }
19011 if let Some(project) = &self.project {
19012 #[allow(clippy::mutable_key_type)]
19013 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19014 multibuffer
19015 .all_buffers()
19016 .into_iter()
19017 .filter_map(|buffer| {
19018 buffer.update(cx, |buffer, cx| {
19019 let language = buffer.language()?;
19020 let should_discard = project.update(cx, |project, cx| {
19021 project.is_local()
19022 && !project.has_language_servers_for(buffer, cx)
19023 });
19024 should_discard.not().then_some(language.clone())
19025 })
19026 })
19027 .collect::<HashSet<_>>()
19028 });
19029 if !languages_affected.is_empty() {
19030 self.refresh_inlay_hints(
19031 InlayHintRefreshReason::BufferEdited(languages_affected),
19032 cx,
19033 );
19034 }
19035 }
19036 }
19037
19038 let Some(project) = &self.project else { return };
19039 let (telemetry, is_via_ssh) = {
19040 let project = project.read(cx);
19041 let telemetry = project.client().telemetry().clone();
19042 let is_via_ssh = project.is_via_ssh();
19043 (telemetry, is_via_ssh)
19044 };
19045 refresh_linked_ranges(self, window, cx);
19046 telemetry.log_edit_event("editor", is_via_ssh);
19047 }
19048 multi_buffer::Event::ExcerptsAdded {
19049 buffer,
19050 predecessor,
19051 excerpts,
19052 } => {
19053 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19054 let buffer_id = buffer.read(cx).remote_id();
19055 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19056 if let Some(project) = &self.project {
19057 update_uncommitted_diff_for_buffer(
19058 cx.entity(),
19059 project,
19060 [buffer.clone()],
19061 self.buffer.clone(),
19062 cx,
19063 )
19064 .detach();
19065 }
19066 }
19067 cx.emit(EditorEvent::ExcerptsAdded {
19068 buffer: buffer.clone(),
19069 predecessor: *predecessor,
19070 excerpts: excerpts.clone(),
19071 });
19072 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19073 }
19074 multi_buffer::Event::ExcerptsRemoved {
19075 ids,
19076 removed_buffer_ids,
19077 } => {
19078 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19079 let buffer = self.buffer.read(cx);
19080 self.registered_buffers
19081 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19082 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19083 cx.emit(EditorEvent::ExcerptsRemoved {
19084 ids: ids.clone(),
19085 removed_buffer_ids: removed_buffer_ids.clone(),
19086 })
19087 }
19088 multi_buffer::Event::ExcerptsEdited {
19089 excerpt_ids,
19090 buffer_ids,
19091 } => {
19092 self.display_map.update(cx, |map, cx| {
19093 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19094 });
19095 cx.emit(EditorEvent::ExcerptsEdited {
19096 ids: excerpt_ids.clone(),
19097 })
19098 }
19099 multi_buffer::Event::ExcerptsExpanded { ids } => {
19100 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19101 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19102 }
19103 multi_buffer::Event::Reparsed(buffer_id) => {
19104 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19105 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19106
19107 cx.emit(EditorEvent::Reparsed(*buffer_id));
19108 }
19109 multi_buffer::Event::DiffHunksToggled => {
19110 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19111 }
19112 multi_buffer::Event::LanguageChanged(buffer_id) => {
19113 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19114 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19115 cx.emit(EditorEvent::Reparsed(*buffer_id));
19116 cx.notify();
19117 }
19118 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19119 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19120 multi_buffer::Event::FileHandleChanged
19121 | multi_buffer::Event::Reloaded
19122 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19123 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19124 multi_buffer::Event::DiagnosticsUpdated => {
19125 self.update_diagnostics_state(window, cx);
19126 }
19127 _ => {}
19128 };
19129 }
19130
19131 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19132 self.refresh_active_diagnostics(cx);
19133 self.refresh_inline_diagnostics(true, window, cx);
19134 self.scrollbar_marker_state.dirty = true;
19135 cx.notify();
19136 }
19137
19138 pub fn start_temporary_diff_override(&mut self) {
19139 self.load_diff_task.take();
19140 self.temporary_diff_override = true;
19141 }
19142
19143 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19144 self.temporary_diff_override = false;
19145 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19146 self.buffer.update(cx, |buffer, cx| {
19147 buffer.set_all_diff_hunks_collapsed(cx);
19148 });
19149
19150 if let Some(project) = self.project.clone() {
19151 self.load_diff_task = Some(
19152 update_uncommitted_diff_for_buffer(
19153 cx.entity(),
19154 &project,
19155 self.buffer.read(cx).all_buffers(),
19156 self.buffer.clone(),
19157 cx,
19158 )
19159 .shared(),
19160 );
19161 }
19162 }
19163
19164 fn on_display_map_changed(
19165 &mut self,
19166 _: Entity<DisplayMap>,
19167 _: &mut Window,
19168 cx: &mut Context<Self>,
19169 ) {
19170 cx.notify();
19171 }
19172
19173 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19174 let new_severity = if self.diagnostics_enabled() {
19175 EditorSettings::get_global(cx)
19176 .diagnostics_max_severity
19177 .unwrap_or(DiagnosticSeverity::Hint)
19178 } else {
19179 DiagnosticSeverity::Off
19180 };
19181 self.set_max_diagnostics_severity(new_severity, cx);
19182 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19183 self.update_edit_prediction_settings(cx);
19184 self.refresh_inline_completion(true, false, window, cx);
19185 self.refresh_inlay_hints(
19186 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19187 self.selections.newest_anchor().head(),
19188 &self.buffer.read(cx).snapshot(cx),
19189 cx,
19190 )),
19191 cx,
19192 );
19193
19194 let old_cursor_shape = self.cursor_shape;
19195
19196 {
19197 let editor_settings = EditorSettings::get_global(cx);
19198 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19199 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19200 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19201 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19202 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19203 }
19204
19205 if old_cursor_shape != self.cursor_shape {
19206 cx.emit(EditorEvent::CursorShapeChanged);
19207 }
19208
19209 let project_settings = ProjectSettings::get_global(cx);
19210 self.serialize_dirty_buffers =
19211 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19212
19213 if self.mode.is_full() {
19214 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19215 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19216 if self.show_inline_diagnostics != show_inline_diagnostics {
19217 self.show_inline_diagnostics = show_inline_diagnostics;
19218 self.refresh_inline_diagnostics(false, window, cx);
19219 }
19220
19221 if self.git_blame_inline_enabled != inline_blame_enabled {
19222 self.toggle_git_blame_inline_internal(false, window, cx);
19223 }
19224
19225 let minimap_settings = EditorSettings::get_global(cx).minimap;
19226 if self.minimap_visibility != MinimapVisibility::Disabled {
19227 if self.minimap_visibility.settings_visibility()
19228 != minimap_settings.minimap_enabled()
19229 {
19230 self.set_minimap_visibility(
19231 MinimapVisibility::for_mode(self.mode(), cx),
19232 window,
19233 cx,
19234 );
19235 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19236 minimap_entity.update(cx, |minimap_editor, cx| {
19237 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19238 })
19239 }
19240 }
19241 }
19242
19243 cx.notify();
19244 }
19245
19246 pub fn set_searchable(&mut self, searchable: bool) {
19247 self.searchable = searchable;
19248 }
19249
19250 pub fn searchable(&self) -> bool {
19251 self.searchable
19252 }
19253
19254 fn open_proposed_changes_editor(
19255 &mut self,
19256 _: &OpenProposedChangesEditor,
19257 window: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) {
19260 let Some(workspace) = self.workspace() else {
19261 cx.propagate();
19262 return;
19263 };
19264
19265 let selections = self.selections.all::<usize>(cx);
19266 let multi_buffer = self.buffer.read(cx);
19267 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19268 let mut new_selections_by_buffer = HashMap::default();
19269 for selection in selections {
19270 for (buffer, range, _) in
19271 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19272 {
19273 let mut range = range.to_point(buffer);
19274 range.start.column = 0;
19275 range.end.column = buffer.line_len(range.end.row);
19276 new_selections_by_buffer
19277 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19278 .or_insert(Vec::new())
19279 .push(range)
19280 }
19281 }
19282
19283 let proposed_changes_buffers = new_selections_by_buffer
19284 .into_iter()
19285 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19286 .collect::<Vec<_>>();
19287 let proposed_changes_editor = cx.new(|cx| {
19288 ProposedChangesEditor::new(
19289 "Proposed changes",
19290 proposed_changes_buffers,
19291 self.project.clone(),
19292 window,
19293 cx,
19294 )
19295 });
19296
19297 window.defer(cx, move |window, cx| {
19298 workspace.update(cx, |workspace, cx| {
19299 workspace.active_pane().update(cx, |pane, cx| {
19300 pane.add_item(
19301 Box::new(proposed_changes_editor),
19302 true,
19303 true,
19304 None,
19305 window,
19306 cx,
19307 );
19308 });
19309 });
19310 });
19311 }
19312
19313 pub fn open_excerpts_in_split(
19314 &mut self,
19315 _: &OpenExcerptsSplit,
19316 window: &mut Window,
19317 cx: &mut Context<Self>,
19318 ) {
19319 self.open_excerpts_common(None, true, window, cx)
19320 }
19321
19322 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19323 self.open_excerpts_common(None, false, window, cx)
19324 }
19325
19326 fn open_excerpts_common(
19327 &mut self,
19328 jump_data: Option<JumpData>,
19329 split: bool,
19330 window: &mut Window,
19331 cx: &mut Context<Self>,
19332 ) {
19333 let Some(workspace) = self.workspace() else {
19334 cx.propagate();
19335 return;
19336 };
19337
19338 if self.buffer.read(cx).is_singleton() {
19339 cx.propagate();
19340 return;
19341 }
19342
19343 let mut new_selections_by_buffer = HashMap::default();
19344 match &jump_data {
19345 Some(JumpData::MultiBufferPoint {
19346 excerpt_id,
19347 position,
19348 anchor,
19349 line_offset_from_top,
19350 }) => {
19351 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19352 if let Some(buffer) = multi_buffer_snapshot
19353 .buffer_id_for_excerpt(*excerpt_id)
19354 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19355 {
19356 let buffer_snapshot = buffer.read(cx).snapshot();
19357 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19358 language::ToPoint::to_point(anchor, &buffer_snapshot)
19359 } else {
19360 buffer_snapshot.clip_point(*position, Bias::Left)
19361 };
19362 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19363 new_selections_by_buffer.insert(
19364 buffer,
19365 (
19366 vec![jump_to_offset..jump_to_offset],
19367 Some(*line_offset_from_top),
19368 ),
19369 );
19370 }
19371 }
19372 Some(JumpData::MultiBufferRow {
19373 row,
19374 line_offset_from_top,
19375 }) => {
19376 let point = MultiBufferPoint::new(row.0, 0);
19377 if let Some((buffer, buffer_point, _)) =
19378 self.buffer.read(cx).point_to_buffer_point(point, cx)
19379 {
19380 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19381 new_selections_by_buffer
19382 .entry(buffer)
19383 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19384 .0
19385 .push(buffer_offset..buffer_offset)
19386 }
19387 }
19388 None => {
19389 let selections = self.selections.all::<usize>(cx);
19390 let multi_buffer = self.buffer.read(cx);
19391 for selection in selections {
19392 for (snapshot, range, _, anchor) in multi_buffer
19393 .snapshot(cx)
19394 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19395 {
19396 if let Some(anchor) = anchor {
19397 // selection is in a deleted hunk
19398 let Some(buffer_id) = anchor.buffer_id else {
19399 continue;
19400 };
19401 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19402 continue;
19403 };
19404 let offset = text::ToOffset::to_offset(
19405 &anchor.text_anchor,
19406 &buffer_handle.read(cx).snapshot(),
19407 );
19408 let range = offset..offset;
19409 new_selections_by_buffer
19410 .entry(buffer_handle)
19411 .or_insert((Vec::new(), None))
19412 .0
19413 .push(range)
19414 } else {
19415 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19416 else {
19417 continue;
19418 };
19419 new_selections_by_buffer
19420 .entry(buffer_handle)
19421 .or_insert((Vec::new(), None))
19422 .0
19423 .push(range)
19424 }
19425 }
19426 }
19427 }
19428 }
19429
19430 new_selections_by_buffer
19431 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19432
19433 if new_selections_by_buffer.is_empty() {
19434 return;
19435 }
19436
19437 // We defer the pane interaction because we ourselves are a workspace item
19438 // and activating a new item causes the pane to call a method on us reentrantly,
19439 // which panics if we're on the stack.
19440 window.defer(cx, move |window, cx| {
19441 workspace.update(cx, |workspace, cx| {
19442 let pane = if split {
19443 workspace.adjacent_pane(window, cx)
19444 } else {
19445 workspace.active_pane().clone()
19446 };
19447
19448 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19449 let editor = buffer
19450 .read(cx)
19451 .file()
19452 .is_none()
19453 .then(|| {
19454 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19455 // so `workspace.open_project_item` will never find them, always opening a new editor.
19456 // Instead, we try to activate the existing editor in the pane first.
19457 let (editor, pane_item_index) =
19458 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19459 let editor = item.downcast::<Editor>()?;
19460 let singleton_buffer =
19461 editor.read(cx).buffer().read(cx).as_singleton()?;
19462 if singleton_buffer == buffer {
19463 Some((editor, i))
19464 } else {
19465 None
19466 }
19467 })?;
19468 pane.update(cx, |pane, cx| {
19469 pane.activate_item(pane_item_index, true, true, window, cx)
19470 });
19471 Some(editor)
19472 })
19473 .flatten()
19474 .unwrap_or_else(|| {
19475 workspace.open_project_item::<Self>(
19476 pane.clone(),
19477 buffer,
19478 true,
19479 true,
19480 window,
19481 cx,
19482 )
19483 });
19484
19485 editor.update(cx, |editor, cx| {
19486 let autoscroll = match scroll_offset {
19487 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19488 None => Autoscroll::newest(),
19489 };
19490 let nav_history = editor.nav_history.take();
19491 editor.change_selections(Some(autoscroll), window, cx, |s| {
19492 s.select_ranges(ranges);
19493 });
19494 editor.nav_history = nav_history;
19495 });
19496 }
19497 })
19498 });
19499 }
19500
19501 // For now, don't allow opening excerpts in buffers that aren't backed by
19502 // regular project files.
19503 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19504 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19505 }
19506
19507 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19508 let snapshot = self.buffer.read(cx).read(cx);
19509 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19510 Some(
19511 ranges
19512 .iter()
19513 .map(move |range| {
19514 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19515 })
19516 .collect(),
19517 )
19518 }
19519
19520 fn selection_replacement_ranges(
19521 &self,
19522 range: Range<OffsetUtf16>,
19523 cx: &mut App,
19524 ) -> Vec<Range<OffsetUtf16>> {
19525 let selections = self.selections.all::<OffsetUtf16>(cx);
19526 let newest_selection = selections
19527 .iter()
19528 .max_by_key(|selection| selection.id)
19529 .unwrap();
19530 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19531 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19532 let snapshot = self.buffer.read(cx).read(cx);
19533 selections
19534 .into_iter()
19535 .map(|mut selection| {
19536 selection.start.0 =
19537 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19538 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19539 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19540 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19541 })
19542 .collect()
19543 }
19544
19545 fn report_editor_event(
19546 &self,
19547 event_type: &'static str,
19548 file_extension: Option<String>,
19549 cx: &App,
19550 ) {
19551 if cfg!(any(test, feature = "test-support")) {
19552 return;
19553 }
19554
19555 let Some(project) = &self.project else { return };
19556
19557 // If None, we are in a file without an extension
19558 let file = self
19559 .buffer
19560 .read(cx)
19561 .as_singleton()
19562 .and_then(|b| b.read(cx).file());
19563 let file_extension = file_extension.or(file
19564 .as_ref()
19565 .and_then(|file| Path::new(file.file_name(cx)).extension())
19566 .and_then(|e| e.to_str())
19567 .map(|a| a.to_string()));
19568
19569 let vim_mode = vim_enabled(cx);
19570
19571 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19572 let copilot_enabled = edit_predictions_provider
19573 == language::language_settings::EditPredictionProvider::Copilot;
19574 let copilot_enabled_for_language = self
19575 .buffer
19576 .read(cx)
19577 .language_settings(cx)
19578 .show_edit_predictions;
19579
19580 let project = project.read(cx);
19581 telemetry::event!(
19582 event_type,
19583 file_extension,
19584 vim_mode,
19585 copilot_enabled,
19586 copilot_enabled_for_language,
19587 edit_predictions_provider,
19588 is_via_ssh = project.is_via_ssh(),
19589 );
19590 }
19591
19592 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19593 /// with each line being an array of {text, highlight} objects.
19594 fn copy_highlight_json(
19595 &mut self,
19596 _: &CopyHighlightJson,
19597 window: &mut Window,
19598 cx: &mut Context<Self>,
19599 ) {
19600 #[derive(Serialize)]
19601 struct Chunk<'a> {
19602 text: String,
19603 highlight: Option<&'a str>,
19604 }
19605
19606 let snapshot = self.buffer.read(cx).snapshot(cx);
19607 let range = self
19608 .selected_text_range(false, window, cx)
19609 .and_then(|selection| {
19610 if selection.range.is_empty() {
19611 None
19612 } else {
19613 Some(selection.range)
19614 }
19615 })
19616 .unwrap_or_else(|| 0..snapshot.len());
19617
19618 let chunks = snapshot.chunks(range, true);
19619 let mut lines = Vec::new();
19620 let mut line: VecDeque<Chunk> = VecDeque::new();
19621
19622 let Some(style) = self.style.as_ref() else {
19623 return;
19624 };
19625
19626 for chunk in chunks {
19627 let highlight = chunk
19628 .syntax_highlight_id
19629 .and_then(|id| id.name(&style.syntax));
19630 let mut chunk_lines = chunk.text.split('\n').peekable();
19631 while let Some(text) = chunk_lines.next() {
19632 let mut merged_with_last_token = false;
19633 if let Some(last_token) = line.back_mut() {
19634 if last_token.highlight == highlight {
19635 last_token.text.push_str(text);
19636 merged_with_last_token = true;
19637 }
19638 }
19639
19640 if !merged_with_last_token {
19641 line.push_back(Chunk {
19642 text: text.into(),
19643 highlight,
19644 });
19645 }
19646
19647 if chunk_lines.peek().is_some() {
19648 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19649 line.pop_front();
19650 }
19651 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19652 line.pop_back();
19653 }
19654
19655 lines.push(mem::take(&mut line));
19656 }
19657 }
19658 }
19659
19660 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19661 return;
19662 };
19663 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19664 }
19665
19666 pub fn open_context_menu(
19667 &mut self,
19668 _: &OpenContextMenu,
19669 window: &mut Window,
19670 cx: &mut Context<Self>,
19671 ) {
19672 self.request_autoscroll(Autoscroll::newest(), cx);
19673 let position = self.selections.newest_display(cx).start;
19674 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19675 }
19676
19677 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19678 &self.inlay_hint_cache
19679 }
19680
19681 pub fn replay_insert_event(
19682 &mut self,
19683 text: &str,
19684 relative_utf16_range: Option<Range<isize>>,
19685 window: &mut Window,
19686 cx: &mut Context<Self>,
19687 ) {
19688 if !self.input_enabled {
19689 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19690 return;
19691 }
19692 if let Some(relative_utf16_range) = relative_utf16_range {
19693 let selections = self.selections.all::<OffsetUtf16>(cx);
19694 self.change_selections(None, window, cx, |s| {
19695 let new_ranges = selections.into_iter().map(|range| {
19696 let start = OffsetUtf16(
19697 range
19698 .head()
19699 .0
19700 .saturating_add_signed(relative_utf16_range.start),
19701 );
19702 let end = OffsetUtf16(
19703 range
19704 .head()
19705 .0
19706 .saturating_add_signed(relative_utf16_range.end),
19707 );
19708 start..end
19709 });
19710 s.select_ranges(new_ranges);
19711 });
19712 }
19713
19714 self.handle_input(text, window, cx);
19715 }
19716
19717 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19718 let Some(provider) = self.semantics_provider.as_ref() else {
19719 return false;
19720 };
19721
19722 let mut supports = false;
19723 self.buffer().update(cx, |this, cx| {
19724 this.for_each_buffer(|buffer| {
19725 supports |= provider.supports_inlay_hints(buffer, cx);
19726 });
19727 });
19728
19729 supports
19730 }
19731
19732 pub fn is_focused(&self, window: &Window) -> bool {
19733 self.focus_handle.is_focused(window)
19734 }
19735
19736 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19737 cx.emit(EditorEvent::Focused);
19738
19739 if let Some(descendant) = self
19740 .last_focused_descendant
19741 .take()
19742 .and_then(|descendant| descendant.upgrade())
19743 {
19744 window.focus(&descendant);
19745 } else {
19746 if let Some(blame) = self.blame.as_ref() {
19747 blame.update(cx, GitBlame::focus)
19748 }
19749
19750 self.blink_manager.update(cx, BlinkManager::enable);
19751 self.show_cursor_names(window, cx);
19752 self.buffer.update(cx, |buffer, cx| {
19753 buffer.finalize_last_transaction(cx);
19754 if self.leader_id.is_none() {
19755 buffer.set_active_selections(
19756 &self.selections.disjoint_anchors(),
19757 self.selections.line_mode,
19758 self.cursor_shape,
19759 cx,
19760 );
19761 }
19762 });
19763 }
19764 }
19765
19766 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19767 cx.emit(EditorEvent::FocusedIn)
19768 }
19769
19770 fn handle_focus_out(
19771 &mut self,
19772 event: FocusOutEvent,
19773 _window: &mut Window,
19774 cx: &mut Context<Self>,
19775 ) {
19776 if event.blurred != self.focus_handle {
19777 self.last_focused_descendant = Some(event.blurred);
19778 }
19779 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19780 }
19781
19782 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19783 self.blink_manager.update(cx, BlinkManager::disable);
19784 self.buffer
19785 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19786
19787 if let Some(blame) = self.blame.as_ref() {
19788 blame.update(cx, GitBlame::blur)
19789 }
19790 if !self.hover_state.focused(window, cx) {
19791 hide_hover(self, cx);
19792 }
19793 if !self
19794 .context_menu
19795 .borrow()
19796 .as_ref()
19797 .is_some_and(|context_menu| context_menu.focused(window, cx))
19798 {
19799 self.hide_context_menu(window, cx);
19800 }
19801 self.discard_inline_completion(false, cx);
19802 cx.emit(EditorEvent::Blurred);
19803 cx.notify();
19804 }
19805
19806 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19807 let mut pending: String = window
19808 .pending_input_keystrokes()
19809 .into_iter()
19810 .flatten()
19811 .filter_map(|keystroke| {
19812 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19813 keystroke.key_char.clone()
19814 } else {
19815 None
19816 }
19817 })
19818 .collect();
19819
19820 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19821 pending = "".to_string();
19822 }
19823
19824 let existing_pending = self
19825 .text_highlights::<PendingInput>(cx)
19826 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19827 if existing_pending.is_none() && pending.is_empty() {
19828 return;
19829 }
19830 let transaction =
19831 self.transact(window, cx, |this, window, cx| {
19832 let selections = this.selections.all::<usize>(cx);
19833 let edits = selections
19834 .iter()
19835 .map(|selection| (selection.end..selection.end, pending.clone()));
19836 this.edit(edits, cx);
19837 this.change_selections(None, window, cx, |s| {
19838 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19839 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19840 }));
19841 });
19842 if let Some(existing_ranges) = existing_pending {
19843 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19844 this.edit(edits, cx);
19845 }
19846 });
19847
19848 let snapshot = self.snapshot(window, cx);
19849 let ranges = self
19850 .selections
19851 .all::<usize>(cx)
19852 .into_iter()
19853 .map(|selection| {
19854 snapshot.buffer_snapshot.anchor_after(selection.end)
19855 ..snapshot
19856 .buffer_snapshot
19857 .anchor_before(selection.end + pending.len())
19858 })
19859 .collect();
19860
19861 if pending.is_empty() {
19862 self.clear_highlights::<PendingInput>(cx);
19863 } else {
19864 self.highlight_text::<PendingInput>(
19865 ranges,
19866 HighlightStyle {
19867 underline: Some(UnderlineStyle {
19868 thickness: px(1.),
19869 color: None,
19870 wavy: false,
19871 }),
19872 ..Default::default()
19873 },
19874 cx,
19875 );
19876 }
19877
19878 self.ime_transaction = self.ime_transaction.or(transaction);
19879 if let Some(transaction) = self.ime_transaction {
19880 self.buffer.update(cx, |buffer, cx| {
19881 buffer.group_until_transaction(transaction, cx);
19882 });
19883 }
19884
19885 if self.text_highlights::<PendingInput>(cx).is_none() {
19886 self.ime_transaction.take();
19887 }
19888 }
19889
19890 pub fn register_action_renderer(
19891 &mut self,
19892 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19893 ) -> Subscription {
19894 let id = self.next_editor_action_id.post_inc();
19895 self.editor_actions
19896 .borrow_mut()
19897 .insert(id, Box::new(listener));
19898
19899 let editor_actions = self.editor_actions.clone();
19900 Subscription::new(move || {
19901 editor_actions.borrow_mut().remove(&id);
19902 })
19903 }
19904
19905 pub fn register_action<A: Action>(
19906 &mut self,
19907 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19908 ) -> Subscription {
19909 let id = self.next_editor_action_id.post_inc();
19910 let listener = Arc::new(listener);
19911 self.editor_actions.borrow_mut().insert(
19912 id,
19913 Box::new(move |_, window, _| {
19914 let listener = listener.clone();
19915 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19916 let action = action.downcast_ref().unwrap();
19917 if phase == DispatchPhase::Bubble {
19918 listener(action, window, cx)
19919 }
19920 })
19921 }),
19922 );
19923
19924 let editor_actions = self.editor_actions.clone();
19925 Subscription::new(move || {
19926 editor_actions.borrow_mut().remove(&id);
19927 })
19928 }
19929
19930 pub fn file_header_size(&self) -> u32 {
19931 FILE_HEADER_HEIGHT
19932 }
19933
19934 pub fn restore(
19935 &mut self,
19936 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19937 window: &mut Window,
19938 cx: &mut Context<Self>,
19939 ) {
19940 let workspace = self.workspace();
19941 let project = self.project.as_ref();
19942 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19943 let mut tasks = Vec::new();
19944 for (buffer_id, changes) in revert_changes {
19945 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19946 buffer.update(cx, |buffer, cx| {
19947 buffer.edit(
19948 changes
19949 .into_iter()
19950 .map(|(range, text)| (range, text.to_string())),
19951 None,
19952 cx,
19953 );
19954 });
19955
19956 if let Some(project) =
19957 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19958 {
19959 project.update(cx, |project, cx| {
19960 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19961 })
19962 }
19963 }
19964 }
19965 tasks
19966 });
19967 cx.spawn_in(window, async move |_, cx| {
19968 for (buffer, task) in save_tasks {
19969 let result = task.await;
19970 if result.is_err() {
19971 let Some(path) = buffer
19972 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19973 .ok()
19974 else {
19975 continue;
19976 };
19977 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19978 let Some(task) = cx
19979 .update_window_entity(&workspace, |workspace, window, cx| {
19980 workspace
19981 .open_path_preview(path, None, false, false, false, window, cx)
19982 })
19983 .ok()
19984 else {
19985 continue;
19986 };
19987 task.await.log_err();
19988 }
19989 }
19990 }
19991 })
19992 .detach();
19993 self.change_selections(None, window, cx, |selections| selections.refresh());
19994 }
19995
19996 pub fn to_pixel_point(
19997 &self,
19998 source: multi_buffer::Anchor,
19999 editor_snapshot: &EditorSnapshot,
20000 window: &mut Window,
20001 ) -> Option<gpui::Point<Pixels>> {
20002 let source_point = source.to_display_point(editor_snapshot);
20003 self.display_to_pixel_point(source_point, editor_snapshot, window)
20004 }
20005
20006 pub fn display_to_pixel_point(
20007 &self,
20008 source: DisplayPoint,
20009 editor_snapshot: &EditorSnapshot,
20010 window: &mut Window,
20011 ) -> Option<gpui::Point<Pixels>> {
20012 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20013 let text_layout_details = self.text_layout_details(window);
20014 let scroll_top = text_layout_details
20015 .scroll_anchor
20016 .scroll_position(editor_snapshot)
20017 .y;
20018
20019 if source.row().as_f32() < scroll_top.floor() {
20020 return None;
20021 }
20022 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20023 let source_y = line_height * (source.row().as_f32() - scroll_top);
20024 Some(gpui::Point::new(source_x, source_y))
20025 }
20026
20027 pub fn has_visible_completions_menu(&self) -> bool {
20028 !self.edit_prediction_preview_is_active()
20029 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20030 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20031 })
20032 }
20033
20034 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20035 if self.mode.is_minimap() {
20036 return;
20037 }
20038 self.addons
20039 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20040 }
20041
20042 pub fn unregister_addon<T: Addon>(&mut self) {
20043 self.addons.remove(&std::any::TypeId::of::<T>());
20044 }
20045
20046 pub fn addon<T: Addon>(&self) -> Option<&T> {
20047 let type_id = std::any::TypeId::of::<T>();
20048 self.addons
20049 .get(&type_id)
20050 .and_then(|item| item.to_any().downcast_ref::<T>())
20051 }
20052
20053 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20054 let type_id = std::any::TypeId::of::<T>();
20055 self.addons
20056 .get_mut(&type_id)
20057 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20058 }
20059
20060 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20061 let text_layout_details = self.text_layout_details(window);
20062 let style = &text_layout_details.editor_style;
20063 let font_id = window.text_system().resolve_font(&style.text.font());
20064 let font_size = style.text.font_size.to_pixels(window.rem_size());
20065 let line_height = style.text.line_height_in_pixels(window.rem_size());
20066 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20067
20068 gpui::Size::new(em_width, line_height)
20069 }
20070
20071 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20072 self.load_diff_task.clone()
20073 }
20074
20075 fn read_metadata_from_db(
20076 &mut self,
20077 item_id: u64,
20078 workspace_id: WorkspaceId,
20079 window: &mut Window,
20080 cx: &mut Context<Editor>,
20081 ) {
20082 if self.is_singleton(cx)
20083 && !self.mode.is_minimap()
20084 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20085 {
20086 let buffer_snapshot = OnceCell::new();
20087
20088 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20089 if !folds.is_empty() {
20090 let snapshot =
20091 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20092 self.fold_ranges(
20093 folds
20094 .into_iter()
20095 .map(|(start, end)| {
20096 snapshot.clip_offset(start, Bias::Left)
20097 ..snapshot.clip_offset(end, Bias::Right)
20098 })
20099 .collect(),
20100 false,
20101 window,
20102 cx,
20103 );
20104 }
20105 }
20106
20107 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20108 if !selections.is_empty() {
20109 let snapshot =
20110 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20111 // skip adding the initial selection to selection history
20112 self.selection_history.mode = SelectionHistoryMode::Skipping;
20113 self.change_selections(None, window, cx, |s| {
20114 s.select_ranges(selections.into_iter().map(|(start, end)| {
20115 snapshot.clip_offset(start, Bias::Left)
20116 ..snapshot.clip_offset(end, Bias::Right)
20117 }));
20118 });
20119 self.selection_history.mode = SelectionHistoryMode::Normal;
20120 }
20121 };
20122 }
20123
20124 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20125 }
20126}
20127
20128fn vim_enabled(cx: &App) -> bool {
20129 cx.global::<SettingsStore>()
20130 .raw_user_settings()
20131 .get("vim_mode")
20132 == Some(&serde_json::Value::Bool(true))
20133}
20134
20135fn process_completion_for_edit(
20136 completion: &Completion,
20137 intent: CompletionIntent,
20138 buffer: &Entity<Buffer>,
20139 cursor_position: &text::Anchor,
20140 cx: &mut Context<Editor>,
20141) -> CompletionEdit {
20142 let buffer = buffer.read(cx);
20143 let buffer_snapshot = buffer.snapshot();
20144 let (snippet, new_text) = if completion.is_snippet() {
20145 // Workaround for typescript language server issues so that methods don't expand within
20146 // strings and functions with type expressions. The previous point is used because the query
20147 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20148 let mut snippet_source = completion.new_text.clone();
20149 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20150 previous_point.column = previous_point.column.saturating_sub(1);
20151 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20152 if scope.prefers_label_for_snippet_in_completion() {
20153 if let Some(label) = completion.label() {
20154 if matches!(
20155 completion.kind(),
20156 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20157 ) {
20158 snippet_source = label;
20159 }
20160 }
20161 }
20162 }
20163 match Snippet::parse(&snippet_source).log_err() {
20164 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20165 None => (None, completion.new_text.clone()),
20166 }
20167 } else {
20168 (None, completion.new_text.clone())
20169 };
20170
20171 let mut range_to_replace = {
20172 let replace_range = &completion.replace_range;
20173 if let CompletionSource::Lsp {
20174 insert_range: Some(insert_range),
20175 ..
20176 } = &completion.source
20177 {
20178 debug_assert_eq!(
20179 insert_range.start, replace_range.start,
20180 "insert_range and replace_range should start at the same position"
20181 );
20182 debug_assert!(
20183 insert_range
20184 .start
20185 .cmp(&cursor_position, &buffer_snapshot)
20186 .is_le(),
20187 "insert_range should start before or at cursor position"
20188 );
20189 debug_assert!(
20190 replace_range
20191 .start
20192 .cmp(&cursor_position, &buffer_snapshot)
20193 .is_le(),
20194 "replace_range should start before or at cursor position"
20195 );
20196 debug_assert!(
20197 insert_range
20198 .end
20199 .cmp(&cursor_position, &buffer_snapshot)
20200 .is_le(),
20201 "insert_range should end before or at cursor position"
20202 );
20203
20204 let should_replace = match intent {
20205 CompletionIntent::CompleteWithInsert => false,
20206 CompletionIntent::CompleteWithReplace => true,
20207 CompletionIntent::Complete | CompletionIntent::Compose => {
20208 let insert_mode =
20209 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20210 .completions
20211 .lsp_insert_mode;
20212 match insert_mode {
20213 LspInsertMode::Insert => false,
20214 LspInsertMode::Replace => true,
20215 LspInsertMode::ReplaceSubsequence => {
20216 let mut text_to_replace = buffer.chars_for_range(
20217 buffer.anchor_before(replace_range.start)
20218 ..buffer.anchor_after(replace_range.end),
20219 );
20220 let mut current_needle = text_to_replace.next();
20221 for haystack_ch in completion.label.text.chars() {
20222 if let Some(needle_ch) = current_needle {
20223 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20224 current_needle = text_to_replace.next();
20225 }
20226 }
20227 }
20228 current_needle.is_none()
20229 }
20230 LspInsertMode::ReplaceSuffix => {
20231 if replace_range
20232 .end
20233 .cmp(&cursor_position, &buffer_snapshot)
20234 .is_gt()
20235 {
20236 let range_after_cursor = *cursor_position..replace_range.end;
20237 let text_after_cursor = buffer
20238 .text_for_range(
20239 buffer.anchor_before(range_after_cursor.start)
20240 ..buffer.anchor_after(range_after_cursor.end),
20241 )
20242 .collect::<String>()
20243 .to_ascii_lowercase();
20244 completion
20245 .label
20246 .text
20247 .to_ascii_lowercase()
20248 .ends_with(&text_after_cursor)
20249 } else {
20250 true
20251 }
20252 }
20253 }
20254 }
20255 };
20256
20257 if should_replace {
20258 replace_range.clone()
20259 } else {
20260 insert_range.clone()
20261 }
20262 } else {
20263 replace_range.clone()
20264 }
20265 };
20266
20267 if range_to_replace
20268 .end
20269 .cmp(&cursor_position, &buffer_snapshot)
20270 .is_lt()
20271 {
20272 range_to_replace.end = *cursor_position;
20273 }
20274
20275 CompletionEdit {
20276 new_text,
20277 replace_range: range_to_replace.to_offset(&buffer),
20278 snippet,
20279 }
20280}
20281
20282struct CompletionEdit {
20283 new_text: String,
20284 replace_range: Range<usize>,
20285 snippet: Option<Snippet>,
20286}
20287
20288fn insert_extra_newline_brackets(
20289 buffer: &MultiBufferSnapshot,
20290 range: Range<usize>,
20291 language: &language::LanguageScope,
20292) -> bool {
20293 let leading_whitespace_len = buffer
20294 .reversed_chars_at(range.start)
20295 .take_while(|c| c.is_whitespace() && *c != '\n')
20296 .map(|c| c.len_utf8())
20297 .sum::<usize>();
20298 let trailing_whitespace_len = buffer
20299 .chars_at(range.end)
20300 .take_while(|c| c.is_whitespace() && *c != '\n')
20301 .map(|c| c.len_utf8())
20302 .sum::<usize>();
20303 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20304
20305 language.brackets().any(|(pair, enabled)| {
20306 let pair_start = pair.start.trim_end();
20307 let pair_end = pair.end.trim_start();
20308
20309 enabled
20310 && pair.newline
20311 && buffer.contains_str_at(range.end, pair_end)
20312 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20313 })
20314}
20315
20316fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20317 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20318 [(buffer, range, _)] => (*buffer, range.clone()),
20319 _ => return false,
20320 };
20321 let pair = {
20322 let mut result: Option<BracketMatch> = None;
20323
20324 for pair in buffer
20325 .all_bracket_ranges(range.clone())
20326 .filter(move |pair| {
20327 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20328 })
20329 {
20330 let len = pair.close_range.end - pair.open_range.start;
20331
20332 if let Some(existing) = &result {
20333 let existing_len = existing.close_range.end - existing.open_range.start;
20334 if len > existing_len {
20335 continue;
20336 }
20337 }
20338
20339 result = Some(pair);
20340 }
20341
20342 result
20343 };
20344 let Some(pair) = pair else {
20345 return false;
20346 };
20347 pair.newline_only
20348 && buffer
20349 .chars_for_range(pair.open_range.end..range.start)
20350 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20351 .all(|c| c.is_whitespace() && c != '\n')
20352}
20353
20354fn update_uncommitted_diff_for_buffer(
20355 editor: Entity<Editor>,
20356 project: &Entity<Project>,
20357 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20358 buffer: Entity<MultiBuffer>,
20359 cx: &mut App,
20360) -> Task<()> {
20361 let mut tasks = Vec::new();
20362 project.update(cx, |project, cx| {
20363 for buffer in buffers {
20364 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20365 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20366 }
20367 }
20368 });
20369 cx.spawn(async move |cx| {
20370 let diffs = future::join_all(tasks).await;
20371 if editor
20372 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20373 .unwrap_or(false)
20374 {
20375 return;
20376 }
20377
20378 buffer
20379 .update(cx, |buffer, cx| {
20380 for diff in diffs.into_iter().flatten() {
20381 buffer.add_diff(diff, cx);
20382 }
20383 })
20384 .ok();
20385 })
20386}
20387
20388fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20389 let tab_size = tab_size.get() as usize;
20390 let mut width = offset;
20391
20392 for ch in text.chars() {
20393 width += if ch == '\t' {
20394 tab_size - (width % tab_size)
20395 } else {
20396 1
20397 };
20398 }
20399
20400 width - offset
20401}
20402
20403#[cfg(test)]
20404mod tests {
20405 use super::*;
20406
20407 #[test]
20408 fn test_string_size_with_expanded_tabs() {
20409 let nz = |val| NonZeroU32::new(val).unwrap();
20410 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20411 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20412 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20413 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20414 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20415 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20416 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20417 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20418 }
20419}
20420
20421/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20422struct WordBreakingTokenizer<'a> {
20423 input: &'a str,
20424}
20425
20426impl<'a> WordBreakingTokenizer<'a> {
20427 fn new(input: &'a str) -> Self {
20428 Self { input }
20429 }
20430}
20431
20432fn is_char_ideographic(ch: char) -> bool {
20433 use unicode_script::Script::*;
20434 use unicode_script::UnicodeScript;
20435 matches!(ch.script(), Han | Tangut | Yi)
20436}
20437
20438fn is_grapheme_ideographic(text: &str) -> bool {
20439 text.chars().any(is_char_ideographic)
20440}
20441
20442fn is_grapheme_whitespace(text: &str) -> bool {
20443 text.chars().any(|x| x.is_whitespace())
20444}
20445
20446fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20447 text.chars().next().map_or(false, |ch| {
20448 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20449 })
20450}
20451
20452#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20453enum WordBreakToken<'a> {
20454 Word { token: &'a str, grapheme_len: usize },
20455 InlineWhitespace { token: &'a str, grapheme_len: usize },
20456 Newline,
20457}
20458
20459impl<'a> Iterator for WordBreakingTokenizer<'a> {
20460 /// Yields a span, the count of graphemes in the token, and whether it was
20461 /// whitespace. Note that it also breaks at word boundaries.
20462 type Item = WordBreakToken<'a>;
20463
20464 fn next(&mut self) -> Option<Self::Item> {
20465 use unicode_segmentation::UnicodeSegmentation;
20466 if self.input.is_empty() {
20467 return None;
20468 }
20469
20470 let mut iter = self.input.graphemes(true).peekable();
20471 let mut offset = 0;
20472 let mut grapheme_len = 0;
20473 if let Some(first_grapheme) = iter.next() {
20474 let is_newline = first_grapheme == "\n";
20475 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20476 offset += first_grapheme.len();
20477 grapheme_len += 1;
20478 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20479 if let Some(grapheme) = iter.peek().copied() {
20480 if should_stay_with_preceding_ideograph(grapheme) {
20481 offset += grapheme.len();
20482 grapheme_len += 1;
20483 }
20484 }
20485 } else {
20486 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20487 let mut next_word_bound = words.peek().copied();
20488 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20489 next_word_bound = words.next();
20490 }
20491 while let Some(grapheme) = iter.peek().copied() {
20492 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20493 break;
20494 };
20495 if is_grapheme_whitespace(grapheme) != is_whitespace
20496 || (grapheme == "\n") != is_newline
20497 {
20498 break;
20499 };
20500 offset += grapheme.len();
20501 grapheme_len += 1;
20502 iter.next();
20503 }
20504 }
20505 let token = &self.input[..offset];
20506 self.input = &self.input[offset..];
20507 if token == "\n" {
20508 Some(WordBreakToken::Newline)
20509 } else if is_whitespace {
20510 Some(WordBreakToken::InlineWhitespace {
20511 token,
20512 grapheme_len,
20513 })
20514 } else {
20515 Some(WordBreakToken::Word {
20516 token,
20517 grapheme_len,
20518 })
20519 }
20520 } else {
20521 None
20522 }
20523 }
20524}
20525
20526#[test]
20527fn test_word_breaking_tokenizer() {
20528 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20529 ("", &[]),
20530 (" ", &[whitespace(" ", 2)]),
20531 ("Ʒ", &[word("Ʒ", 1)]),
20532 ("Ǽ", &[word("Ǽ", 1)]),
20533 ("⋑", &[word("⋑", 1)]),
20534 ("⋑⋑", &[word("⋑⋑", 2)]),
20535 (
20536 "原理,进而",
20537 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20538 ),
20539 (
20540 "hello world",
20541 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20542 ),
20543 (
20544 "hello, world",
20545 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20546 ),
20547 (
20548 " hello world",
20549 &[
20550 whitespace(" ", 2),
20551 word("hello", 5),
20552 whitespace(" ", 1),
20553 word("world", 5),
20554 ],
20555 ),
20556 (
20557 "这是什么 \n 钢笔",
20558 &[
20559 word("这", 1),
20560 word("是", 1),
20561 word("什", 1),
20562 word("么", 1),
20563 whitespace(" ", 1),
20564 newline(),
20565 whitespace(" ", 1),
20566 word("钢", 1),
20567 word("笔", 1),
20568 ],
20569 ),
20570 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20571 ];
20572
20573 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20574 WordBreakToken::Word {
20575 token,
20576 grapheme_len,
20577 }
20578 }
20579
20580 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20581 WordBreakToken::InlineWhitespace {
20582 token,
20583 grapheme_len,
20584 }
20585 }
20586
20587 fn newline() -> WordBreakToken<'static> {
20588 WordBreakToken::Newline
20589 }
20590
20591 for (input, result) in tests {
20592 assert_eq!(
20593 WordBreakingTokenizer::new(input)
20594 .collect::<Vec<_>>()
20595 .as_slice(),
20596 *result,
20597 );
20598 }
20599}
20600
20601fn wrap_with_prefix(
20602 line_prefix: String,
20603 unwrapped_text: String,
20604 wrap_column: usize,
20605 tab_size: NonZeroU32,
20606 preserve_existing_whitespace: bool,
20607) -> String {
20608 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20609 let mut wrapped_text = String::new();
20610 let mut current_line = line_prefix.clone();
20611
20612 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20613 let mut current_line_len = line_prefix_len;
20614 let mut in_whitespace = false;
20615 for token in tokenizer {
20616 let have_preceding_whitespace = in_whitespace;
20617 match token {
20618 WordBreakToken::Word {
20619 token,
20620 grapheme_len,
20621 } => {
20622 in_whitespace = false;
20623 if current_line_len + grapheme_len > wrap_column
20624 && current_line_len != line_prefix_len
20625 {
20626 wrapped_text.push_str(current_line.trim_end());
20627 wrapped_text.push('\n');
20628 current_line.truncate(line_prefix.len());
20629 current_line_len = line_prefix_len;
20630 }
20631 current_line.push_str(token);
20632 current_line_len += grapheme_len;
20633 }
20634 WordBreakToken::InlineWhitespace {
20635 mut token,
20636 mut grapheme_len,
20637 } => {
20638 in_whitespace = true;
20639 if have_preceding_whitespace && !preserve_existing_whitespace {
20640 continue;
20641 }
20642 if !preserve_existing_whitespace {
20643 token = " ";
20644 grapheme_len = 1;
20645 }
20646 if current_line_len + grapheme_len > wrap_column {
20647 wrapped_text.push_str(current_line.trim_end());
20648 wrapped_text.push('\n');
20649 current_line.truncate(line_prefix.len());
20650 current_line_len = line_prefix_len;
20651 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20652 current_line.push_str(token);
20653 current_line_len += grapheme_len;
20654 }
20655 }
20656 WordBreakToken::Newline => {
20657 in_whitespace = true;
20658 if preserve_existing_whitespace {
20659 wrapped_text.push_str(current_line.trim_end());
20660 wrapped_text.push('\n');
20661 current_line.truncate(line_prefix.len());
20662 current_line_len = line_prefix_len;
20663 } else if have_preceding_whitespace {
20664 continue;
20665 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20666 {
20667 wrapped_text.push_str(current_line.trim_end());
20668 wrapped_text.push('\n');
20669 current_line.truncate(line_prefix.len());
20670 current_line_len = line_prefix_len;
20671 } else if current_line_len != line_prefix_len {
20672 current_line.push(' ');
20673 current_line_len += 1;
20674 }
20675 }
20676 }
20677 }
20678
20679 if !current_line.is_empty() {
20680 wrapped_text.push_str(¤t_line);
20681 }
20682 wrapped_text
20683}
20684
20685#[test]
20686fn test_wrap_with_prefix() {
20687 assert_eq!(
20688 wrap_with_prefix(
20689 "# ".to_string(),
20690 "abcdefg".to_string(),
20691 4,
20692 NonZeroU32::new(4).unwrap(),
20693 false,
20694 ),
20695 "# abcdefg"
20696 );
20697 assert_eq!(
20698 wrap_with_prefix(
20699 "".to_string(),
20700 "\thello world".to_string(),
20701 8,
20702 NonZeroU32::new(4).unwrap(),
20703 false,
20704 ),
20705 "hello\nworld"
20706 );
20707 assert_eq!(
20708 wrap_with_prefix(
20709 "// ".to_string(),
20710 "xx \nyy zz aa bb cc".to_string(),
20711 12,
20712 NonZeroU32::new(4).unwrap(),
20713 false,
20714 ),
20715 "// xx yy zz\n// aa bb cc"
20716 );
20717 assert_eq!(
20718 wrap_with_prefix(
20719 String::new(),
20720 "这是什么 \n 钢笔".to_string(),
20721 3,
20722 NonZeroU32::new(4).unwrap(),
20723 false,
20724 ),
20725 "这是什\n么 钢\n笔"
20726 );
20727}
20728
20729pub trait CollaborationHub {
20730 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20731 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20732 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20733}
20734
20735impl CollaborationHub for Entity<Project> {
20736 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20737 self.read(cx).collaborators()
20738 }
20739
20740 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20741 self.read(cx).user_store().read(cx).participant_indices()
20742 }
20743
20744 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20745 let this = self.read(cx);
20746 let user_ids = this.collaborators().values().map(|c| c.user_id);
20747 this.user_store().read(cx).participant_names(user_ids, cx)
20748 }
20749}
20750
20751pub trait SemanticsProvider {
20752 fn hover(
20753 &self,
20754 buffer: &Entity<Buffer>,
20755 position: text::Anchor,
20756 cx: &mut App,
20757 ) -> Option<Task<Vec<project::Hover>>>;
20758
20759 fn inline_values(
20760 &self,
20761 buffer_handle: Entity<Buffer>,
20762 range: Range<text::Anchor>,
20763 cx: &mut App,
20764 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20765
20766 fn inlay_hints(
20767 &self,
20768 buffer_handle: Entity<Buffer>,
20769 range: Range<text::Anchor>,
20770 cx: &mut App,
20771 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20772
20773 fn resolve_inlay_hint(
20774 &self,
20775 hint: InlayHint,
20776 buffer_handle: Entity<Buffer>,
20777 server_id: LanguageServerId,
20778 cx: &mut App,
20779 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20780
20781 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20782
20783 fn document_highlights(
20784 &self,
20785 buffer: &Entity<Buffer>,
20786 position: text::Anchor,
20787 cx: &mut App,
20788 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20789
20790 fn definitions(
20791 &self,
20792 buffer: &Entity<Buffer>,
20793 position: text::Anchor,
20794 kind: GotoDefinitionKind,
20795 cx: &mut App,
20796 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20797
20798 fn range_for_rename(
20799 &self,
20800 buffer: &Entity<Buffer>,
20801 position: text::Anchor,
20802 cx: &mut App,
20803 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20804
20805 fn perform_rename(
20806 &self,
20807 buffer: &Entity<Buffer>,
20808 position: text::Anchor,
20809 new_name: String,
20810 cx: &mut App,
20811 ) -> Option<Task<Result<ProjectTransaction>>>;
20812
20813 fn pull_diagnostics_for_buffer(
20814 &self,
20815 buffer: Entity<Buffer>,
20816 cx: &mut App,
20817 ) -> Task<anyhow::Result<()>>;
20818}
20819
20820pub trait CompletionProvider {
20821 fn completions(
20822 &self,
20823 excerpt_id: ExcerptId,
20824 buffer: &Entity<Buffer>,
20825 buffer_position: text::Anchor,
20826 trigger: CompletionContext,
20827 window: &mut Window,
20828 cx: &mut Context<Editor>,
20829 ) -> Task<Result<Vec<CompletionResponse>>>;
20830
20831 fn resolve_completions(
20832 &self,
20833 _buffer: Entity<Buffer>,
20834 _completion_indices: Vec<usize>,
20835 _completions: Rc<RefCell<Box<[Completion]>>>,
20836 _cx: &mut Context<Editor>,
20837 ) -> Task<Result<bool>> {
20838 Task::ready(Ok(false))
20839 }
20840
20841 fn apply_additional_edits_for_completion(
20842 &self,
20843 _buffer: Entity<Buffer>,
20844 _completions: Rc<RefCell<Box<[Completion]>>>,
20845 _completion_index: usize,
20846 _push_to_history: bool,
20847 _cx: &mut Context<Editor>,
20848 ) -> Task<Result<Option<language::Transaction>>> {
20849 Task::ready(Ok(None))
20850 }
20851
20852 fn is_completion_trigger(
20853 &self,
20854 buffer: &Entity<Buffer>,
20855 position: language::Anchor,
20856 text: &str,
20857 trigger_in_words: bool,
20858 menu_is_open: bool,
20859 cx: &mut Context<Editor>,
20860 ) -> bool;
20861
20862 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20863
20864 fn sort_completions(&self) -> bool {
20865 true
20866 }
20867
20868 fn filter_completions(&self) -> bool {
20869 true
20870 }
20871}
20872
20873pub trait CodeActionProvider {
20874 fn id(&self) -> Arc<str>;
20875
20876 fn code_actions(
20877 &self,
20878 buffer: &Entity<Buffer>,
20879 range: Range<text::Anchor>,
20880 window: &mut Window,
20881 cx: &mut App,
20882 ) -> Task<Result<Vec<CodeAction>>>;
20883
20884 fn apply_code_action(
20885 &self,
20886 buffer_handle: Entity<Buffer>,
20887 action: CodeAction,
20888 excerpt_id: ExcerptId,
20889 push_to_history: bool,
20890 window: &mut Window,
20891 cx: &mut App,
20892 ) -> Task<Result<ProjectTransaction>>;
20893}
20894
20895impl CodeActionProvider for Entity<Project> {
20896 fn id(&self) -> Arc<str> {
20897 "project".into()
20898 }
20899
20900 fn code_actions(
20901 &self,
20902 buffer: &Entity<Buffer>,
20903 range: Range<text::Anchor>,
20904 _window: &mut Window,
20905 cx: &mut App,
20906 ) -> Task<Result<Vec<CodeAction>>> {
20907 self.update(cx, |project, cx| {
20908 let code_lens = project.code_lens(buffer, range.clone(), cx);
20909 let code_actions = project.code_actions(buffer, range, None, cx);
20910 cx.background_spawn(async move {
20911 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20912 Ok(code_lens
20913 .context("code lens fetch")?
20914 .into_iter()
20915 .chain(code_actions.context("code action fetch")?)
20916 .collect())
20917 })
20918 })
20919 }
20920
20921 fn apply_code_action(
20922 &self,
20923 buffer_handle: Entity<Buffer>,
20924 action: CodeAction,
20925 _excerpt_id: ExcerptId,
20926 push_to_history: bool,
20927 _window: &mut Window,
20928 cx: &mut App,
20929 ) -> Task<Result<ProjectTransaction>> {
20930 self.update(cx, |project, cx| {
20931 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20932 })
20933 }
20934}
20935
20936fn snippet_completions(
20937 project: &Project,
20938 buffer: &Entity<Buffer>,
20939 buffer_position: text::Anchor,
20940 cx: &mut App,
20941) -> Task<Result<CompletionResponse>> {
20942 let languages = buffer.read(cx).languages_at(buffer_position);
20943 let snippet_store = project.snippets().read(cx);
20944
20945 let scopes: Vec<_> = languages
20946 .iter()
20947 .filter_map(|language| {
20948 let language_name = language.lsp_id();
20949 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20950
20951 if snippets.is_empty() {
20952 None
20953 } else {
20954 Some((language.default_scope(), snippets))
20955 }
20956 })
20957 .collect();
20958
20959 if scopes.is_empty() {
20960 return Task::ready(Ok(CompletionResponse {
20961 completions: vec![],
20962 is_incomplete: false,
20963 }));
20964 }
20965
20966 let snapshot = buffer.read(cx).text_snapshot();
20967 let chars: String = snapshot
20968 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20969 .collect();
20970 let executor = cx.background_executor().clone();
20971
20972 cx.background_spawn(async move {
20973 let mut is_incomplete = false;
20974 let mut completions: Vec<Completion> = Vec::new();
20975 for (scope, snippets) in scopes.into_iter() {
20976 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20977 let mut last_word = chars
20978 .chars()
20979 .take_while(|c| classifier.is_word(*c))
20980 .collect::<String>();
20981 last_word = last_word.chars().rev().collect();
20982
20983 if last_word.is_empty() {
20984 return Ok(CompletionResponse {
20985 completions: vec![],
20986 is_incomplete: true,
20987 });
20988 }
20989
20990 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20991 let to_lsp = |point: &text::Anchor| {
20992 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20993 point_to_lsp(end)
20994 };
20995 let lsp_end = to_lsp(&buffer_position);
20996
20997 let candidates = snippets
20998 .iter()
20999 .enumerate()
21000 .flat_map(|(ix, snippet)| {
21001 snippet
21002 .prefix
21003 .iter()
21004 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21005 })
21006 .collect::<Vec<StringMatchCandidate>>();
21007
21008 const MAX_RESULTS: usize = 100;
21009 let mut matches = fuzzy::match_strings(
21010 &candidates,
21011 &last_word,
21012 last_word.chars().any(|c| c.is_uppercase()),
21013 MAX_RESULTS,
21014 &Default::default(),
21015 executor.clone(),
21016 )
21017 .await;
21018
21019 if matches.len() >= MAX_RESULTS {
21020 is_incomplete = true;
21021 }
21022
21023 // Remove all candidates where the query's start does not match the start of any word in the candidate
21024 if let Some(query_start) = last_word.chars().next() {
21025 matches.retain(|string_match| {
21026 split_words(&string_match.string).any(|word| {
21027 // Check that the first codepoint of the word as lowercase matches the first
21028 // codepoint of the query as lowercase
21029 word.chars()
21030 .flat_map(|codepoint| codepoint.to_lowercase())
21031 .zip(query_start.to_lowercase())
21032 .all(|(word_cp, query_cp)| word_cp == query_cp)
21033 })
21034 });
21035 }
21036
21037 let matched_strings = matches
21038 .into_iter()
21039 .map(|m| m.string)
21040 .collect::<HashSet<_>>();
21041
21042 completions.extend(snippets.iter().filter_map(|snippet| {
21043 let matching_prefix = snippet
21044 .prefix
21045 .iter()
21046 .find(|prefix| matched_strings.contains(*prefix))?;
21047 let start = as_offset - last_word.len();
21048 let start = snapshot.anchor_before(start);
21049 let range = start..buffer_position;
21050 let lsp_start = to_lsp(&start);
21051 let lsp_range = lsp::Range {
21052 start: lsp_start,
21053 end: lsp_end,
21054 };
21055 Some(Completion {
21056 replace_range: range,
21057 new_text: snippet.body.clone(),
21058 source: CompletionSource::Lsp {
21059 insert_range: None,
21060 server_id: LanguageServerId(usize::MAX),
21061 resolved: true,
21062 lsp_completion: Box::new(lsp::CompletionItem {
21063 label: snippet.prefix.first().unwrap().clone(),
21064 kind: Some(CompletionItemKind::SNIPPET),
21065 label_details: snippet.description.as_ref().map(|description| {
21066 lsp::CompletionItemLabelDetails {
21067 detail: Some(description.clone()),
21068 description: None,
21069 }
21070 }),
21071 insert_text_format: Some(InsertTextFormat::SNIPPET),
21072 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21073 lsp::InsertReplaceEdit {
21074 new_text: snippet.body.clone(),
21075 insert: lsp_range,
21076 replace: lsp_range,
21077 },
21078 )),
21079 filter_text: Some(snippet.body.clone()),
21080 sort_text: Some(char::MAX.to_string()),
21081 ..lsp::CompletionItem::default()
21082 }),
21083 lsp_defaults: None,
21084 },
21085 label: CodeLabel {
21086 text: matching_prefix.clone(),
21087 runs: Vec::new(),
21088 filter_range: 0..matching_prefix.len(),
21089 },
21090 icon_path: None,
21091 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21092 single_line: snippet.name.clone().into(),
21093 plain_text: snippet
21094 .description
21095 .clone()
21096 .map(|description| description.into()),
21097 }),
21098 insert_text_mode: None,
21099 confirm: None,
21100 })
21101 }))
21102 }
21103
21104 Ok(CompletionResponse {
21105 completions,
21106 is_incomplete,
21107 })
21108 })
21109}
21110
21111impl CompletionProvider for Entity<Project> {
21112 fn completions(
21113 &self,
21114 _excerpt_id: ExcerptId,
21115 buffer: &Entity<Buffer>,
21116 buffer_position: text::Anchor,
21117 options: CompletionContext,
21118 _window: &mut Window,
21119 cx: &mut Context<Editor>,
21120 ) -> Task<Result<Vec<CompletionResponse>>> {
21121 self.update(cx, |project, cx| {
21122 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21123 let project_completions = project.completions(buffer, buffer_position, options, cx);
21124 cx.background_spawn(async move {
21125 let mut responses = project_completions.await?;
21126 let snippets = snippets.await?;
21127 if !snippets.completions.is_empty() {
21128 responses.push(snippets);
21129 }
21130 Ok(responses)
21131 })
21132 })
21133 }
21134
21135 fn resolve_completions(
21136 &self,
21137 buffer: Entity<Buffer>,
21138 completion_indices: Vec<usize>,
21139 completions: Rc<RefCell<Box<[Completion]>>>,
21140 cx: &mut Context<Editor>,
21141 ) -> Task<Result<bool>> {
21142 self.update(cx, |project, cx| {
21143 project.lsp_store().update(cx, |lsp_store, cx| {
21144 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21145 })
21146 })
21147 }
21148
21149 fn apply_additional_edits_for_completion(
21150 &self,
21151 buffer: Entity<Buffer>,
21152 completions: Rc<RefCell<Box<[Completion]>>>,
21153 completion_index: usize,
21154 push_to_history: bool,
21155 cx: &mut Context<Editor>,
21156 ) -> Task<Result<Option<language::Transaction>>> {
21157 self.update(cx, |project, cx| {
21158 project.lsp_store().update(cx, |lsp_store, cx| {
21159 lsp_store.apply_additional_edits_for_completion(
21160 buffer,
21161 completions,
21162 completion_index,
21163 push_to_history,
21164 cx,
21165 )
21166 })
21167 })
21168 }
21169
21170 fn is_completion_trigger(
21171 &self,
21172 buffer: &Entity<Buffer>,
21173 position: language::Anchor,
21174 text: &str,
21175 trigger_in_words: bool,
21176 menu_is_open: bool,
21177 cx: &mut Context<Editor>,
21178 ) -> bool {
21179 let mut chars = text.chars();
21180 let char = if let Some(char) = chars.next() {
21181 char
21182 } else {
21183 return false;
21184 };
21185 if chars.next().is_some() {
21186 return false;
21187 }
21188
21189 let buffer = buffer.read(cx);
21190 let snapshot = buffer.snapshot();
21191 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21192 return false;
21193 }
21194 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21195 if trigger_in_words && classifier.is_word(char) {
21196 return true;
21197 }
21198
21199 buffer.completion_triggers().contains(text)
21200 }
21201}
21202
21203impl SemanticsProvider for Entity<Project> {
21204 fn hover(
21205 &self,
21206 buffer: &Entity<Buffer>,
21207 position: text::Anchor,
21208 cx: &mut App,
21209 ) -> Option<Task<Vec<project::Hover>>> {
21210 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21211 }
21212
21213 fn document_highlights(
21214 &self,
21215 buffer: &Entity<Buffer>,
21216 position: text::Anchor,
21217 cx: &mut App,
21218 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21219 Some(self.update(cx, |project, cx| {
21220 project.document_highlights(buffer, position, cx)
21221 }))
21222 }
21223
21224 fn definitions(
21225 &self,
21226 buffer: &Entity<Buffer>,
21227 position: text::Anchor,
21228 kind: GotoDefinitionKind,
21229 cx: &mut App,
21230 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21231 Some(self.update(cx, |project, cx| match kind {
21232 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21233 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21234 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21235 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21236 }))
21237 }
21238
21239 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21240 // TODO: make this work for remote projects
21241 self.update(cx, |project, cx| {
21242 if project
21243 .active_debug_session(cx)
21244 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21245 {
21246 return true;
21247 }
21248
21249 buffer.update(cx, |buffer, cx| {
21250 project.any_language_server_supports_inlay_hints(buffer, cx)
21251 })
21252 })
21253 }
21254
21255 fn inline_values(
21256 &self,
21257 buffer_handle: Entity<Buffer>,
21258
21259 range: Range<text::Anchor>,
21260 cx: &mut App,
21261 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21262 self.update(cx, |project, cx| {
21263 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21264
21265 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21266 })
21267 }
21268
21269 fn inlay_hints(
21270 &self,
21271 buffer_handle: Entity<Buffer>,
21272 range: Range<text::Anchor>,
21273 cx: &mut App,
21274 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21275 Some(self.update(cx, |project, cx| {
21276 project.inlay_hints(buffer_handle, range, cx)
21277 }))
21278 }
21279
21280 fn resolve_inlay_hint(
21281 &self,
21282 hint: InlayHint,
21283 buffer_handle: Entity<Buffer>,
21284 server_id: LanguageServerId,
21285 cx: &mut App,
21286 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21287 Some(self.update(cx, |project, cx| {
21288 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21289 }))
21290 }
21291
21292 fn range_for_rename(
21293 &self,
21294 buffer: &Entity<Buffer>,
21295 position: text::Anchor,
21296 cx: &mut App,
21297 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21298 Some(self.update(cx, |project, cx| {
21299 let buffer = buffer.clone();
21300 let task = project.prepare_rename(buffer.clone(), position, cx);
21301 cx.spawn(async move |_, cx| {
21302 Ok(match task.await? {
21303 PrepareRenameResponse::Success(range) => Some(range),
21304 PrepareRenameResponse::InvalidPosition => None,
21305 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21306 // Fallback on using TreeSitter info to determine identifier range
21307 buffer.read_with(cx, |buffer, _| {
21308 let snapshot = buffer.snapshot();
21309 let (range, kind) = snapshot.surrounding_word(position);
21310 if kind != Some(CharKind::Word) {
21311 return None;
21312 }
21313 Some(
21314 snapshot.anchor_before(range.start)
21315 ..snapshot.anchor_after(range.end),
21316 )
21317 })?
21318 }
21319 })
21320 })
21321 }))
21322 }
21323
21324 fn perform_rename(
21325 &self,
21326 buffer: &Entity<Buffer>,
21327 position: text::Anchor,
21328 new_name: String,
21329 cx: &mut App,
21330 ) -> Option<Task<Result<ProjectTransaction>>> {
21331 Some(self.update(cx, |project, cx| {
21332 project.perform_rename(buffer.clone(), position, new_name, cx)
21333 }))
21334 }
21335
21336 fn pull_diagnostics_for_buffer(
21337 &self,
21338 buffer: Entity<Buffer>,
21339 cx: &mut App,
21340 ) -> Task<anyhow::Result<()>> {
21341 let diagnostics = self.update(cx, |project, cx| {
21342 project
21343 .lsp_store()
21344 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21345 });
21346 let project = self.clone();
21347 cx.spawn(async move |cx| {
21348 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21349 project.update(cx, |project, cx| {
21350 project.lsp_store().update(cx, |lsp_store, cx| {
21351 for diagnostics_set in diagnostics {
21352 let LspPullDiagnostics::Response {
21353 server_id,
21354 uri,
21355 diagnostics,
21356 } = diagnostics_set
21357 else {
21358 continue;
21359 };
21360
21361 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21362 let disk_based_sources = adapter
21363 .as_ref()
21364 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21365 .unwrap_or(&[]);
21366 match diagnostics {
21367 PulledDiagnostics::Unchanged { result_id } => {
21368 lsp_store
21369 .merge_diagnostics(
21370 server_id,
21371 lsp::PublishDiagnosticsParams {
21372 uri: uri.clone(),
21373 diagnostics: Vec::new(),
21374 version: None,
21375 },
21376 Some(result_id),
21377 DiagnosticSourceKind::Pulled,
21378 disk_based_sources,
21379 |_, _| true,
21380 cx,
21381 )
21382 .log_err();
21383 }
21384 PulledDiagnostics::Changed {
21385 diagnostics,
21386 result_id,
21387 } => {
21388 lsp_store
21389 .merge_diagnostics(
21390 server_id,
21391 lsp::PublishDiagnosticsParams {
21392 uri: uri.clone(),
21393 diagnostics,
21394 version: None,
21395 },
21396 result_id,
21397 DiagnosticSourceKind::Pulled,
21398 disk_based_sources,
21399 |old_diagnostic, _| match old_diagnostic.source_kind {
21400 DiagnosticSourceKind::Pulled => false,
21401 DiagnosticSourceKind::Other
21402 | DiagnosticSourceKind::Pushed => true,
21403 },
21404 cx,
21405 )
21406 .log_err();
21407 }
21408 }
21409 }
21410 })
21411 })
21412 })
21413 }
21414}
21415
21416fn inlay_hint_settings(
21417 location: Anchor,
21418 snapshot: &MultiBufferSnapshot,
21419 cx: &mut Context<Editor>,
21420) -> InlayHintSettings {
21421 let file = snapshot.file_at(location);
21422 let language = snapshot.language_at(location).map(|l| l.name());
21423 language_settings(language, file, cx).inlay_hints
21424}
21425
21426fn consume_contiguous_rows(
21427 contiguous_row_selections: &mut Vec<Selection<Point>>,
21428 selection: &Selection<Point>,
21429 display_map: &DisplaySnapshot,
21430 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21431) -> (MultiBufferRow, MultiBufferRow) {
21432 contiguous_row_selections.push(selection.clone());
21433 let start_row = MultiBufferRow(selection.start.row);
21434 let mut end_row = ending_row(selection, display_map);
21435
21436 while let Some(next_selection) = selections.peek() {
21437 if next_selection.start.row <= end_row.0 {
21438 end_row = ending_row(next_selection, display_map);
21439 contiguous_row_selections.push(selections.next().unwrap().clone());
21440 } else {
21441 break;
21442 }
21443 }
21444 (start_row, end_row)
21445}
21446
21447fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21448 if next_selection.end.column > 0 || next_selection.is_empty() {
21449 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21450 } else {
21451 MultiBufferRow(next_selection.end.row)
21452 }
21453}
21454
21455impl EditorSnapshot {
21456 pub fn remote_selections_in_range<'a>(
21457 &'a self,
21458 range: &'a Range<Anchor>,
21459 collaboration_hub: &dyn CollaborationHub,
21460 cx: &'a App,
21461 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21462 let participant_names = collaboration_hub.user_names(cx);
21463 let participant_indices = collaboration_hub.user_participant_indices(cx);
21464 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21465 let collaborators_by_replica_id = collaborators_by_peer_id
21466 .values()
21467 .map(|collaborator| (collaborator.replica_id, collaborator))
21468 .collect::<HashMap<_, _>>();
21469 self.buffer_snapshot
21470 .selections_in_range(range, false)
21471 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21472 if replica_id == AGENT_REPLICA_ID {
21473 Some(RemoteSelection {
21474 replica_id,
21475 selection,
21476 cursor_shape,
21477 line_mode,
21478 collaborator_id: CollaboratorId::Agent,
21479 user_name: Some("Agent".into()),
21480 color: cx.theme().players().agent(),
21481 })
21482 } else {
21483 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21484 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21485 let user_name = participant_names.get(&collaborator.user_id).cloned();
21486 Some(RemoteSelection {
21487 replica_id,
21488 selection,
21489 cursor_shape,
21490 line_mode,
21491 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21492 user_name,
21493 color: if let Some(index) = participant_index {
21494 cx.theme().players().color_for_participant(index.0)
21495 } else {
21496 cx.theme().players().absent()
21497 },
21498 })
21499 }
21500 })
21501 }
21502
21503 pub fn hunks_for_ranges(
21504 &self,
21505 ranges: impl IntoIterator<Item = Range<Point>>,
21506 ) -> Vec<MultiBufferDiffHunk> {
21507 let mut hunks = Vec::new();
21508 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21509 HashMap::default();
21510 for query_range in ranges {
21511 let query_rows =
21512 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21513 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21514 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21515 ) {
21516 // Include deleted hunks that are adjacent to the query range, because
21517 // otherwise they would be missed.
21518 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21519 if hunk.status().is_deleted() {
21520 intersects_range |= hunk.row_range.start == query_rows.end;
21521 intersects_range |= hunk.row_range.end == query_rows.start;
21522 }
21523 if intersects_range {
21524 if !processed_buffer_rows
21525 .entry(hunk.buffer_id)
21526 .or_default()
21527 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21528 {
21529 continue;
21530 }
21531 hunks.push(hunk);
21532 }
21533 }
21534 }
21535
21536 hunks
21537 }
21538
21539 fn display_diff_hunks_for_rows<'a>(
21540 &'a self,
21541 display_rows: Range<DisplayRow>,
21542 folded_buffers: &'a HashSet<BufferId>,
21543 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21544 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21545 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21546
21547 self.buffer_snapshot
21548 .diff_hunks_in_range(buffer_start..buffer_end)
21549 .filter_map(|hunk| {
21550 if folded_buffers.contains(&hunk.buffer_id) {
21551 return None;
21552 }
21553
21554 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21555 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21556
21557 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21558 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21559
21560 let display_hunk = if hunk_display_start.column() != 0 {
21561 DisplayDiffHunk::Folded {
21562 display_row: hunk_display_start.row(),
21563 }
21564 } else {
21565 let mut end_row = hunk_display_end.row();
21566 if hunk_display_end.column() > 0 {
21567 end_row.0 += 1;
21568 }
21569 let is_created_file = hunk.is_created_file();
21570 DisplayDiffHunk::Unfolded {
21571 status: hunk.status(),
21572 diff_base_byte_range: hunk.diff_base_byte_range,
21573 display_row_range: hunk_display_start.row()..end_row,
21574 multi_buffer_range: Anchor::range_in_buffer(
21575 hunk.excerpt_id,
21576 hunk.buffer_id,
21577 hunk.buffer_range,
21578 ),
21579 is_created_file,
21580 }
21581 };
21582
21583 Some(display_hunk)
21584 })
21585 }
21586
21587 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21588 self.display_snapshot.buffer_snapshot.language_at(position)
21589 }
21590
21591 pub fn is_focused(&self) -> bool {
21592 self.is_focused
21593 }
21594
21595 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21596 self.placeholder_text.as_ref()
21597 }
21598
21599 pub fn scroll_position(&self) -> gpui::Point<f32> {
21600 self.scroll_anchor.scroll_position(&self.display_snapshot)
21601 }
21602
21603 fn gutter_dimensions(
21604 &self,
21605 font_id: FontId,
21606 font_size: Pixels,
21607 max_line_number_width: Pixels,
21608 cx: &App,
21609 ) -> Option<GutterDimensions> {
21610 if !self.show_gutter {
21611 return None;
21612 }
21613
21614 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21615 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21616
21617 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21618 matches!(
21619 ProjectSettings::get_global(cx).git.git_gutter,
21620 Some(GitGutterSetting::TrackedFiles)
21621 )
21622 });
21623 let gutter_settings = EditorSettings::get_global(cx).gutter;
21624 let show_line_numbers = self
21625 .show_line_numbers
21626 .unwrap_or(gutter_settings.line_numbers);
21627 let line_gutter_width = if show_line_numbers {
21628 // Avoid flicker-like gutter resizes when the line number gains another digit by
21629 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21630 let min_width_for_number_on_gutter =
21631 ch_advance * gutter_settings.min_line_number_digits as f32;
21632 max_line_number_width.max(min_width_for_number_on_gutter)
21633 } else {
21634 0.0.into()
21635 };
21636
21637 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21638 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21639
21640 let git_blame_entries_width =
21641 self.git_blame_gutter_max_author_length
21642 .map(|max_author_length| {
21643 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21644 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21645
21646 /// The number of characters to dedicate to gaps and margins.
21647 const SPACING_WIDTH: usize = 4;
21648
21649 let max_char_count = max_author_length.min(renderer.max_author_length())
21650 + ::git::SHORT_SHA_LENGTH
21651 + MAX_RELATIVE_TIMESTAMP.len()
21652 + SPACING_WIDTH;
21653
21654 ch_advance * max_char_count
21655 });
21656
21657 let is_singleton = self.buffer_snapshot.is_singleton();
21658
21659 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21660 left_padding += if !is_singleton {
21661 ch_width * 4.0
21662 } else if show_runnables || show_breakpoints {
21663 ch_width * 3.0
21664 } else if show_git_gutter && show_line_numbers {
21665 ch_width * 2.0
21666 } else if show_git_gutter || show_line_numbers {
21667 ch_width
21668 } else {
21669 px(0.)
21670 };
21671
21672 let shows_folds = is_singleton && gutter_settings.folds;
21673
21674 let right_padding = if shows_folds && show_line_numbers {
21675 ch_width * 4.0
21676 } else if shows_folds || (!is_singleton && show_line_numbers) {
21677 ch_width * 3.0
21678 } else if show_line_numbers {
21679 ch_width
21680 } else {
21681 px(0.)
21682 };
21683
21684 Some(GutterDimensions {
21685 left_padding,
21686 right_padding,
21687 width: line_gutter_width + left_padding + right_padding,
21688 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21689 git_blame_entries_width,
21690 })
21691 }
21692
21693 pub fn render_crease_toggle(
21694 &self,
21695 buffer_row: MultiBufferRow,
21696 row_contains_cursor: bool,
21697 editor: Entity<Editor>,
21698 window: &mut Window,
21699 cx: &mut App,
21700 ) -> Option<AnyElement> {
21701 let folded = self.is_line_folded(buffer_row);
21702 let mut is_foldable = false;
21703
21704 if let Some(crease) = self
21705 .crease_snapshot
21706 .query_row(buffer_row, &self.buffer_snapshot)
21707 {
21708 is_foldable = true;
21709 match crease {
21710 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21711 if let Some(render_toggle) = render_toggle {
21712 let toggle_callback =
21713 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21714 if folded {
21715 editor.update(cx, |editor, cx| {
21716 editor.fold_at(buffer_row, window, cx)
21717 });
21718 } else {
21719 editor.update(cx, |editor, cx| {
21720 editor.unfold_at(buffer_row, window, cx)
21721 });
21722 }
21723 });
21724 return Some((render_toggle)(
21725 buffer_row,
21726 folded,
21727 toggle_callback,
21728 window,
21729 cx,
21730 ));
21731 }
21732 }
21733 }
21734 }
21735
21736 is_foldable |= self.starts_indent(buffer_row);
21737
21738 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21739 Some(
21740 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21741 .toggle_state(folded)
21742 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21743 if folded {
21744 this.unfold_at(buffer_row, window, cx);
21745 } else {
21746 this.fold_at(buffer_row, window, cx);
21747 }
21748 }))
21749 .into_any_element(),
21750 )
21751 } else {
21752 None
21753 }
21754 }
21755
21756 pub fn render_crease_trailer(
21757 &self,
21758 buffer_row: MultiBufferRow,
21759 window: &mut Window,
21760 cx: &mut App,
21761 ) -> Option<AnyElement> {
21762 let folded = self.is_line_folded(buffer_row);
21763 if let Crease::Inline { render_trailer, .. } = self
21764 .crease_snapshot
21765 .query_row(buffer_row, &self.buffer_snapshot)?
21766 {
21767 let render_trailer = render_trailer.as_ref()?;
21768 Some(render_trailer(buffer_row, folded, window, cx))
21769 } else {
21770 None
21771 }
21772 }
21773}
21774
21775impl Deref for EditorSnapshot {
21776 type Target = DisplaySnapshot;
21777
21778 fn deref(&self) -> &Self::Target {
21779 &self.display_snapshot
21780 }
21781}
21782
21783#[derive(Clone, Debug, PartialEq, Eq)]
21784pub enum EditorEvent {
21785 InputIgnored {
21786 text: Arc<str>,
21787 },
21788 InputHandled {
21789 utf16_range_to_replace: Option<Range<isize>>,
21790 text: Arc<str>,
21791 },
21792 ExcerptsAdded {
21793 buffer: Entity<Buffer>,
21794 predecessor: ExcerptId,
21795 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21796 },
21797 ExcerptsRemoved {
21798 ids: Vec<ExcerptId>,
21799 removed_buffer_ids: Vec<BufferId>,
21800 },
21801 BufferFoldToggled {
21802 ids: Vec<ExcerptId>,
21803 folded: bool,
21804 },
21805 ExcerptsEdited {
21806 ids: Vec<ExcerptId>,
21807 },
21808 ExcerptsExpanded {
21809 ids: Vec<ExcerptId>,
21810 },
21811 BufferEdited,
21812 Edited {
21813 transaction_id: clock::Lamport,
21814 },
21815 Reparsed(BufferId),
21816 Focused,
21817 FocusedIn,
21818 Blurred,
21819 DirtyChanged,
21820 Saved,
21821 TitleChanged,
21822 DiffBaseChanged,
21823 SelectionsChanged {
21824 local: bool,
21825 },
21826 ScrollPositionChanged {
21827 local: bool,
21828 autoscroll: bool,
21829 },
21830 Closed,
21831 TransactionUndone {
21832 transaction_id: clock::Lamport,
21833 },
21834 TransactionBegun {
21835 transaction_id: clock::Lamport,
21836 },
21837 Reloaded,
21838 CursorShapeChanged,
21839 PushedToNavHistory {
21840 anchor: Anchor,
21841 is_deactivate: bool,
21842 },
21843}
21844
21845impl EventEmitter<EditorEvent> for Editor {}
21846
21847impl Focusable for Editor {
21848 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21849 self.focus_handle.clone()
21850 }
21851}
21852
21853impl Render for Editor {
21854 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21855 let settings = ThemeSettings::get_global(cx);
21856
21857 let mut text_style = match self.mode {
21858 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21859 color: cx.theme().colors().editor_foreground,
21860 font_family: settings.ui_font.family.clone(),
21861 font_features: settings.ui_font.features.clone(),
21862 font_fallbacks: settings.ui_font.fallbacks.clone(),
21863 font_size: rems(0.875).into(),
21864 font_weight: settings.ui_font.weight,
21865 line_height: relative(settings.buffer_line_height.value()),
21866 ..Default::default()
21867 },
21868 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21869 color: cx.theme().colors().editor_foreground,
21870 font_family: settings.buffer_font.family.clone(),
21871 font_features: settings.buffer_font.features.clone(),
21872 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21873 font_size: settings.buffer_font_size(cx).into(),
21874 font_weight: settings.buffer_font.weight,
21875 line_height: relative(settings.buffer_line_height.value()),
21876 ..Default::default()
21877 },
21878 };
21879 if let Some(text_style_refinement) = &self.text_style_refinement {
21880 text_style.refine(text_style_refinement)
21881 }
21882
21883 let background = match self.mode {
21884 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21885 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21886 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21887 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21888 };
21889
21890 EditorElement::new(
21891 &cx.entity(),
21892 EditorStyle {
21893 background,
21894 local_player: cx.theme().players().local(),
21895 text: text_style,
21896 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21897 syntax: cx.theme().syntax().clone(),
21898 status: cx.theme().status().clone(),
21899 inlay_hints_style: make_inlay_hints_style(cx),
21900 inline_completion_styles: make_suggestion_styles(cx),
21901 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21902 show_underlines: !self.mode.is_minimap(),
21903 },
21904 )
21905 }
21906}
21907
21908impl EntityInputHandler for Editor {
21909 fn text_for_range(
21910 &mut self,
21911 range_utf16: Range<usize>,
21912 adjusted_range: &mut Option<Range<usize>>,
21913 _: &mut Window,
21914 cx: &mut Context<Self>,
21915 ) -> Option<String> {
21916 let snapshot = self.buffer.read(cx).read(cx);
21917 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21918 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21919 if (start.0..end.0) != range_utf16 {
21920 adjusted_range.replace(start.0..end.0);
21921 }
21922 Some(snapshot.text_for_range(start..end).collect())
21923 }
21924
21925 fn selected_text_range(
21926 &mut self,
21927 ignore_disabled_input: bool,
21928 _: &mut Window,
21929 cx: &mut Context<Self>,
21930 ) -> Option<UTF16Selection> {
21931 // Prevent the IME menu from appearing when holding down an alphabetic key
21932 // while input is disabled.
21933 if !ignore_disabled_input && !self.input_enabled {
21934 return None;
21935 }
21936
21937 let selection = self.selections.newest::<OffsetUtf16>(cx);
21938 let range = selection.range();
21939
21940 Some(UTF16Selection {
21941 range: range.start.0..range.end.0,
21942 reversed: selection.reversed,
21943 })
21944 }
21945
21946 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21947 let snapshot = self.buffer.read(cx).read(cx);
21948 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21949 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21950 }
21951
21952 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21953 self.clear_highlights::<InputComposition>(cx);
21954 self.ime_transaction.take();
21955 }
21956
21957 fn replace_text_in_range(
21958 &mut self,
21959 range_utf16: Option<Range<usize>>,
21960 text: &str,
21961 window: &mut Window,
21962 cx: &mut Context<Self>,
21963 ) {
21964 if !self.input_enabled {
21965 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21966 return;
21967 }
21968
21969 self.transact(window, cx, |this, window, cx| {
21970 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21971 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21972 Some(this.selection_replacement_ranges(range_utf16, cx))
21973 } else {
21974 this.marked_text_ranges(cx)
21975 };
21976
21977 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21978 let newest_selection_id = this.selections.newest_anchor().id;
21979 this.selections
21980 .all::<OffsetUtf16>(cx)
21981 .iter()
21982 .zip(ranges_to_replace.iter())
21983 .find_map(|(selection, range)| {
21984 if selection.id == newest_selection_id {
21985 Some(
21986 (range.start.0 as isize - selection.head().0 as isize)
21987 ..(range.end.0 as isize - selection.head().0 as isize),
21988 )
21989 } else {
21990 None
21991 }
21992 })
21993 });
21994
21995 cx.emit(EditorEvent::InputHandled {
21996 utf16_range_to_replace: range_to_replace,
21997 text: text.into(),
21998 });
21999
22000 if let Some(new_selected_ranges) = new_selected_ranges {
22001 this.change_selections(None, window, cx, |selections| {
22002 selections.select_ranges(new_selected_ranges)
22003 });
22004 this.backspace(&Default::default(), window, cx);
22005 }
22006
22007 this.handle_input(text, window, cx);
22008 });
22009
22010 if let Some(transaction) = self.ime_transaction {
22011 self.buffer.update(cx, |buffer, cx| {
22012 buffer.group_until_transaction(transaction, cx);
22013 });
22014 }
22015
22016 self.unmark_text(window, cx);
22017 }
22018
22019 fn replace_and_mark_text_in_range(
22020 &mut self,
22021 range_utf16: Option<Range<usize>>,
22022 text: &str,
22023 new_selected_range_utf16: Option<Range<usize>>,
22024 window: &mut Window,
22025 cx: &mut Context<Self>,
22026 ) {
22027 if !self.input_enabled {
22028 return;
22029 }
22030
22031 let transaction = self.transact(window, cx, |this, window, cx| {
22032 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22033 let snapshot = this.buffer.read(cx).read(cx);
22034 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22035 for marked_range in &mut marked_ranges {
22036 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22037 marked_range.start.0 += relative_range_utf16.start;
22038 marked_range.start =
22039 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22040 marked_range.end =
22041 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22042 }
22043 }
22044 Some(marked_ranges)
22045 } else if let Some(range_utf16) = range_utf16 {
22046 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22047 Some(this.selection_replacement_ranges(range_utf16, cx))
22048 } else {
22049 None
22050 };
22051
22052 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22053 let newest_selection_id = this.selections.newest_anchor().id;
22054 this.selections
22055 .all::<OffsetUtf16>(cx)
22056 .iter()
22057 .zip(ranges_to_replace.iter())
22058 .find_map(|(selection, range)| {
22059 if selection.id == newest_selection_id {
22060 Some(
22061 (range.start.0 as isize - selection.head().0 as isize)
22062 ..(range.end.0 as isize - selection.head().0 as isize),
22063 )
22064 } else {
22065 None
22066 }
22067 })
22068 });
22069
22070 cx.emit(EditorEvent::InputHandled {
22071 utf16_range_to_replace: range_to_replace,
22072 text: text.into(),
22073 });
22074
22075 if let Some(ranges) = ranges_to_replace {
22076 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22077 }
22078
22079 let marked_ranges = {
22080 let snapshot = this.buffer.read(cx).read(cx);
22081 this.selections
22082 .disjoint_anchors()
22083 .iter()
22084 .map(|selection| {
22085 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22086 })
22087 .collect::<Vec<_>>()
22088 };
22089
22090 if text.is_empty() {
22091 this.unmark_text(window, cx);
22092 } else {
22093 this.highlight_text::<InputComposition>(
22094 marked_ranges.clone(),
22095 HighlightStyle {
22096 underline: Some(UnderlineStyle {
22097 thickness: px(1.),
22098 color: None,
22099 wavy: false,
22100 }),
22101 ..Default::default()
22102 },
22103 cx,
22104 );
22105 }
22106
22107 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22108 let use_autoclose = this.use_autoclose;
22109 let use_auto_surround = this.use_auto_surround;
22110 this.set_use_autoclose(false);
22111 this.set_use_auto_surround(false);
22112 this.handle_input(text, window, cx);
22113 this.set_use_autoclose(use_autoclose);
22114 this.set_use_auto_surround(use_auto_surround);
22115
22116 if let Some(new_selected_range) = new_selected_range_utf16 {
22117 let snapshot = this.buffer.read(cx).read(cx);
22118 let new_selected_ranges = marked_ranges
22119 .into_iter()
22120 .map(|marked_range| {
22121 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22122 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22123 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22124 snapshot.clip_offset_utf16(new_start, Bias::Left)
22125 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22126 })
22127 .collect::<Vec<_>>();
22128
22129 drop(snapshot);
22130 this.change_selections(None, window, cx, |selections| {
22131 selections.select_ranges(new_selected_ranges)
22132 });
22133 }
22134 });
22135
22136 self.ime_transaction = self.ime_transaction.or(transaction);
22137 if let Some(transaction) = self.ime_transaction {
22138 self.buffer.update(cx, |buffer, cx| {
22139 buffer.group_until_transaction(transaction, cx);
22140 });
22141 }
22142
22143 if self.text_highlights::<InputComposition>(cx).is_none() {
22144 self.ime_transaction.take();
22145 }
22146 }
22147
22148 fn bounds_for_range(
22149 &mut self,
22150 range_utf16: Range<usize>,
22151 element_bounds: gpui::Bounds<Pixels>,
22152 window: &mut Window,
22153 cx: &mut Context<Self>,
22154 ) -> Option<gpui::Bounds<Pixels>> {
22155 let text_layout_details = self.text_layout_details(window);
22156 let gpui::Size {
22157 width: em_width,
22158 height: line_height,
22159 } = self.character_size(window);
22160
22161 let snapshot = self.snapshot(window, cx);
22162 let scroll_position = snapshot.scroll_position();
22163 let scroll_left = scroll_position.x * em_width;
22164
22165 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22166 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22167 + self.gutter_dimensions.width
22168 + self.gutter_dimensions.margin;
22169 let y = line_height * (start.row().as_f32() - scroll_position.y);
22170
22171 Some(Bounds {
22172 origin: element_bounds.origin + point(x, y),
22173 size: size(em_width, line_height),
22174 })
22175 }
22176
22177 fn character_index_for_point(
22178 &mut self,
22179 point: gpui::Point<Pixels>,
22180 _window: &mut Window,
22181 _cx: &mut Context<Self>,
22182 ) -> Option<usize> {
22183 let position_map = self.last_position_map.as_ref()?;
22184 if !position_map.text_hitbox.contains(&point) {
22185 return None;
22186 }
22187 let display_point = position_map.point_for_position(point).previous_valid;
22188 let anchor = position_map
22189 .snapshot
22190 .display_point_to_anchor(display_point, Bias::Left);
22191 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22192 Some(utf16_offset.0)
22193 }
22194}
22195
22196trait SelectionExt {
22197 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22198 fn spanned_rows(
22199 &self,
22200 include_end_if_at_line_start: bool,
22201 map: &DisplaySnapshot,
22202 ) -> Range<MultiBufferRow>;
22203}
22204
22205impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22206 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22207 let start = self
22208 .start
22209 .to_point(&map.buffer_snapshot)
22210 .to_display_point(map);
22211 let end = self
22212 .end
22213 .to_point(&map.buffer_snapshot)
22214 .to_display_point(map);
22215 if self.reversed {
22216 end..start
22217 } else {
22218 start..end
22219 }
22220 }
22221
22222 fn spanned_rows(
22223 &self,
22224 include_end_if_at_line_start: bool,
22225 map: &DisplaySnapshot,
22226 ) -> Range<MultiBufferRow> {
22227 let start = self.start.to_point(&map.buffer_snapshot);
22228 let mut end = self.end.to_point(&map.buffer_snapshot);
22229 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22230 end.row -= 1;
22231 }
22232
22233 let buffer_start = map.prev_line_boundary(start).0;
22234 let buffer_end = map.next_line_boundary(end).0;
22235 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22236 }
22237}
22238
22239impl<T: InvalidationRegion> InvalidationStack<T> {
22240 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22241 where
22242 S: Clone + ToOffset,
22243 {
22244 while let Some(region) = self.last() {
22245 let all_selections_inside_invalidation_ranges =
22246 if selections.len() == region.ranges().len() {
22247 selections
22248 .iter()
22249 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22250 .all(|(selection, invalidation_range)| {
22251 let head = selection.head().to_offset(buffer);
22252 invalidation_range.start <= head && invalidation_range.end >= head
22253 })
22254 } else {
22255 false
22256 };
22257
22258 if all_selections_inside_invalidation_ranges {
22259 break;
22260 } else {
22261 self.pop();
22262 }
22263 }
22264 }
22265}
22266
22267impl<T> Default for InvalidationStack<T> {
22268 fn default() -> Self {
22269 Self(Default::default())
22270 }
22271}
22272
22273impl<T> Deref for InvalidationStack<T> {
22274 type Target = Vec<T>;
22275
22276 fn deref(&self) -> &Self::Target {
22277 &self.0
22278 }
22279}
22280
22281impl<T> DerefMut for InvalidationStack<T> {
22282 fn deref_mut(&mut self) -> &mut Self::Target {
22283 &mut self.0
22284 }
22285}
22286
22287impl InvalidationRegion for SnippetState {
22288 fn ranges(&self) -> &[Range<Anchor>] {
22289 &self.ranges[self.active_index]
22290 }
22291}
22292
22293fn inline_completion_edit_text(
22294 current_snapshot: &BufferSnapshot,
22295 edits: &[(Range<Anchor>, String)],
22296 edit_preview: &EditPreview,
22297 include_deletions: bool,
22298 cx: &App,
22299) -> HighlightedText {
22300 let edits = edits
22301 .iter()
22302 .map(|(anchor, text)| {
22303 (
22304 anchor.start.text_anchor..anchor.end.text_anchor,
22305 text.clone(),
22306 )
22307 })
22308 .collect::<Vec<_>>();
22309
22310 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22311}
22312
22313pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22314 match severity {
22315 lsp::DiagnosticSeverity::ERROR => colors.error,
22316 lsp::DiagnosticSeverity::WARNING => colors.warning,
22317 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22318 lsp::DiagnosticSeverity::HINT => colors.info,
22319 _ => colors.ignored,
22320 }
22321}
22322
22323pub fn styled_runs_for_code_label<'a>(
22324 label: &'a CodeLabel,
22325 syntax_theme: &'a theme::SyntaxTheme,
22326) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22327 let fade_out = HighlightStyle {
22328 fade_out: Some(0.35),
22329 ..Default::default()
22330 };
22331
22332 let mut prev_end = label.filter_range.end;
22333 label
22334 .runs
22335 .iter()
22336 .enumerate()
22337 .flat_map(move |(ix, (range, highlight_id))| {
22338 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22339 style
22340 } else {
22341 return Default::default();
22342 };
22343 let mut muted_style = style;
22344 muted_style.highlight(fade_out);
22345
22346 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22347 if range.start >= label.filter_range.end {
22348 if range.start > prev_end {
22349 runs.push((prev_end..range.start, fade_out));
22350 }
22351 runs.push((range.clone(), muted_style));
22352 } else if range.end <= label.filter_range.end {
22353 runs.push((range.clone(), style));
22354 } else {
22355 runs.push((range.start..label.filter_range.end, style));
22356 runs.push((label.filter_range.end..range.end, muted_style));
22357 }
22358 prev_end = cmp::max(prev_end, range.end);
22359
22360 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22361 runs.push((prev_end..label.text.len(), fade_out));
22362 }
22363
22364 runs
22365 })
22366}
22367
22368pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22369 let mut prev_index = 0;
22370 let mut prev_codepoint: Option<char> = None;
22371 text.char_indices()
22372 .chain([(text.len(), '\0')])
22373 .filter_map(move |(index, codepoint)| {
22374 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22375 let is_boundary = index == text.len()
22376 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22377 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22378 if is_boundary {
22379 let chunk = &text[prev_index..index];
22380 prev_index = index;
22381 Some(chunk)
22382 } else {
22383 None
22384 }
22385 })
22386}
22387
22388pub trait RangeToAnchorExt: Sized {
22389 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22390
22391 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22392 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22393 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22394 }
22395}
22396
22397impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22398 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22399 let start_offset = self.start.to_offset(snapshot);
22400 let end_offset = self.end.to_offset(snapshot);
22401 if start_offset == end_offset {
22402 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22403 } else {
22404 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22405 }
22406 }
22407}
22408
22409pub trait RowExt {
22410 fn as_f32(&self) -> f32;
22411
22412 fn next_row(&self) -> Self;
22413
22414 fn previous_row(&self) -> Self;
22415
22416 fn minus(&self, other: Self) -> u32;
22417}
22418
22419impl RowExt for DisplayRow {
22420 fn as_f32(&self) -> f32 {
22421 self.0 as f32
22422 }
22423
22424 fn next_row(&self) -> Self {
22425 Self(self.0 + 1)
22426 }
22427
22428 fn previous_row(&self) -> Self {
22429 Self(self.0.saturating_sub(1))
22430 }
22431
22432 fn minus(&self, other: Self) -> u32 {
22433 self.0 - other.0
22434 }
22435}
22436
22437impl RowExt for MultiBufferRow {
22438 fn as_f32(&self) -> f32 {
22439 self.0 as f32
22440 }
22441
22442 fn next_row(&self) -> Self {
22443 Self(self.0 + 1)
22444 }
22445
22446 fn previous_row(&self) -> Self {
22447 Self(self.0.saturating_sub(1))
22448 }
22449
22450 fn minus(&self, other: Self) -> u32 {
22451 self.0 - other.0
22452 }
22453}
22454
22455trait RowRangeExt {
22456 type Row;
22457
22458 fn len(&self) -> usize;
22459
22460 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22461}
22462
22463impl RowRangeExt for Range<MultiBufferRow> {
22464 type Row = MultiBufferRow;
22465
22466 fn len(&self) -> usize {
22467 (self.end.0 - self.start.0) as usize
22468 }
22469
22470 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22471 (self.start.0..self.end.0).map(MultiBufferRow)
22472 }
22473}
22474
22475impl RowRangeExt for Range<DisplayRow> {
22476 type Row = DisplayRow;
22477
22478 fn len(&self) -> usize {
22479 (self.end.0 - self.start.0) as usize
22480 }
22481
22482 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22483 (self.start.0..self.end.0).map(DisplayRow)
22484 }
22485}
22486
22487/// If select range has more than one line, we
22488/// just point the cursor to range.start.
22489fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22490 if range.start.row == range.end.row {
22491 range
22492 } else {
22493 range.start..range.start
22494 }
22495}
22496pub struct KillRing(ClipboardItem);
22497impl Global for KillRing {}
22498
22499const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22500
22501enum BreakpointPromptEditAction {
22502 Log,
22503 Condition,
22504 HitCondition,
22505}
22506
22507struct BreakpointPromptEditor {
22508 pub(crate) prompt: Entity<Editor>,
22509 editor: WeakEntity<Editor>,
22510 breakpoint_anchor: Anchor,
22511 breakpoint: Breakpoint,
22512 edit_action: BreakpointPromptEditAction,
22513 block_ids: HashSet<CustomBlockId>,
22514 editor_margins: Arc<Mutex<EditorMargins>>,
22515 _subscriptions: Vec<Subscription>,
22516}
22517
22518impl BreakpointPromptEditor {
22519 const MAX_LINES: u8 = 4;
22520
22521 fn new(
22522 editor: WeakEntity<Editor>,
22523 breakpoint_anchor: Anchor,
22524 breakpoint: Breakpoint,
22525 edit_action: BreakpointPromptEditAction,
22526 window: &mut Window,
22527 cx: &mut Context<Self>,
22528 ) -> Self {
22529 let base_text = match edit_action {
22530 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22531 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22532 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22533 }
22534 .map(|msg| msg.to_string())
22535 .unwrap_or_default();
22536
22537 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22538 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22539
22540 let prompt = cx.new(|cx| {
22541 let mut prompt = Editor::new(
22542 EditorMode::AutoHeight {
22543 max_lines: Self::MAX_LINES as usize,
22544 },
22545 buffer,
22546 None,
22547 window,
22548 cx,
22549 );
22550 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22551 prompt.set_show_cursor_when_unfocused(false, cx);
22552 prompt.set_placeholder_text(
22553 match edit_action {
22554 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22555 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22556 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22557 },
22558 cx,
22559 );
22560
22561 prompt
22562 });
22563
22564 Self {
22565 prompt,
22566 editor,
22567 breakpoint_anchor,
22568 breakpoint,
22569 edit_action,
22570 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22571 block_ids: Default::default(),
22572 _subscriptions: vec![],
22573 }
22574 }
22575
22576 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22577 self.block_ids.extend(block_ids)
22578 }
22579
22580 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22581 if let Some(editor) = self.editor.upgrade() {
22582 let message = self
22583 .prompt
22584 .read(cx)
22585 .buffer
22586 .read(cx)
22587 .as_singleton()
22588 .expect("A multi buffer in breakpoint prompt isn't possible")
22589 .read(cx)
22590 .as_rope()
22591 .to_string();
22592
22593 editor.update(cx, |editor, cx| {
22594 editor.edit_breakpoint_at_anchor(
22595 self.breakpoint_anchor,
22596 self.breakpoint.clone(),
22597 match self.edit_action {
22598 BreakpointPromptEditAction::Log => {
22599 BreakpointEditAction::EditLogMessage(message.into())
22600 }
22601 BreakpointPromptEditAction::Condition => {
22602 BreakpointEditAction::EditCondition(message.into())
22603 }
22604 BreakpointPromptEditAction::HitCondition => {
22605 BreakpointEditAction::EditHitCondition(message.into())
22606 }
22607 },
22608 cx,
22609 );
22610
22611 editor.remove_blocks(self.block_ids.clone(), None, cx);
22612 cx.focus_self(window);
22613 });
22614 }
22615 }
22616
22617 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22618 self.editor
22619 .update(cx, |editor, cx| {
22620 editor.remove_blocks(self.block_ids.clone(), None, cx);
22621 window.focus(&editor.focus_handle);
22622 })
22623 .log_err();
22624 }
22625
22626 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22627 let settings = ThemeSettings::get_global(cx);
22628 let text_style = TextStyle {
22629 color: if self.prompt.read(cx).read_only(cx) {
22630 cx.theme().colors().text_disabled
22631 } else {
22632 cx.theme().colors().text
22633 },
22634 font_family: settings.buffer_font.family.clone(),
22635 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22636 font_size: settings.buffer_font_size(cx).into(),
22637 font_weight: settings.buffer_font.weight,
22638 line_height: relative(settings.buffer_line_height.value()),
22639 ..Default::default()
22640 };
22641 EditorElement::new(
22642 &self.prompt,
22643 EditorStyle {
22644 background: cx.theme().colors().editor_background,
22645 local_player: cx.theme().players().local(),
22646 text: text_style,
22647 ..Default::default()
22648 },
22649 )
22650 }
22651}
22652
22653impl Render for BreakpointPromptEditor {
22654 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22655 let editor_margins = *self.editor_margins.lock();
22656 let gutter_dimensions = editor_margins.gutter;
22657 h_flex()
22658 .key_context("Editor")
22659 .bg(cx.theme().colors().editor_background)
22660 .border_y_1()
22661 .border_color(cx.theme().status().info_border)
22662 .size_full()
22663 .py(window.line_height() / 2.5)
22664 .on_action(cx.listener(Self::confirm))
22665 .on_action(cx.listener(Self::cancel))
22666 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22667 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22668 }
22669}
22670
22671impl Focusable for BreakpointPromptEditor {
22672 fn focus_handle(&self, cx: &App) -> FocusHandle {
22673 self.prompt.focus_handle(cx)
22674 }
22675}
22676
22677fn all_edits_insertions_or_deletions(
22678 edits: &Vec<(Range<Anchor>, String)>,
22679 snapshot: &MultiBufferSnapshot,
22680) -> bool {
22681 let mut all_insertions = true;
22682 let mut all_deletions = true;
22683
22684 for (range, new_text) in edits.iter() {
22685 let range_is_empty = range.to_offset(&snapshot).is_empty();
22686 let text_is_empty = new_text.is_empty();
22687
22688 if range_is_empty != text_is_empty {
22689 if range_is_empty {
22690 all_deletions = false;
22691 } else {
22692 all_insertions = false;
22693 }
22694 } else {
22695 return false;
22696 }
22697
22698 if !all_insertions && !all_deletions {
22699 return false;
22700 }
22701 }
22702 all_insertions || all_deletions
22703}
22704
22705struct MissingEditPredictionKeybindingTooltip;
22706
22707impl Render for MissingEditPredictionKeybindingTooltip {
22708 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22709 ui::tooltip_container(window, cx, |container, _, cx| {
22710 container
22711 .flex_shrink_0()
22712 .max_w_80()
22713 .min_h(rems_from_px(124.))
22714 .justify_between()
22715 .child(
22716 v_flex()
22717 .flex_1()
22718 .text_ui_sm(cx)
22719 .child(Label::new("Conflict with Accept Keybinding"))
22720 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22721 )
22722 .child(
22723 h_flex()
22724 .pb_1()
22725 .gap_1()
22726 .items_end()
22727 .w_full()
22728 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22729 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22730 }))
22731 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22732 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22733 })),
22734 )
22735 })
22736 }
22737}
22738
22739#[derive(Debug, Clone, Copy, PartialEq)]
22740pub struct LineHighlight {
22741 pub background: Background,
22742 pub border: Option<gpui::Hsla>,
22743 pub include_gutter: bool,
22744 pub type_id: Option<TypeId>,
22745}
22746
22747fn render_diff_hunk_controls(
22748 row: u32,
22749 status: &DiffHunkStatus,
22750 hunk_range: Range<Anchor>,
22751 is_created_file: bool,
22752 line_height: Pixels,
22753 editor: &Entity<Editor>,
22754 _window: &mut Window,
22755 cx: &mut App,
22756) -> AnyElement {
22757 h_flex()
22758 .h(line_height)
22759 .mr_1()
22760 .gap_1()
22761 .px_0p5()
22762 .pb_1()
22763 .border_x_1()
22764 .border_b_1()
22765 .border_color(cx.theme().colors().border_variant)
22766 .rounded_b_lg()
22767 .bg(cx.theme().colors().editor_background)
22768 .gap_1()
22769 .block_mouse_except_scroll()
22770 .shadow_md()
22771 .child(if status.has_secondary_hunk() {
22772 Button::new(("stage", row as u64), "Stage")
22773 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22774 .tooltip({
22775 let focus_handle = editor.focus_handle(cx);
22776 move |window, cx| {
22777 Tooltip::for_action_in(
22778 "Stage Hunk",
22779 &::git::ToggleStaged,
22780 &focus_handle,
22781 window,
22782 cx,
22783 )
22784 }
22785 })
22786 .on_click({
22787 let editor = editor.clone();
22788 move |_event, _window, cx| {
22789 editor.update(cx, |editor, cx| {
22790 editor.stage_or_unstage_diff_hunks(
22791 true,
22792 vec![hunk_range.start..hunk_range.start],
22793 cx,
22794 );
22795 });
22796 }
22797 })
22798 } else {
22799 Button::new(("unstage", row as u64), "Unstage")
22800 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22801 .tooltip({
22802 let focus_handle = editor.focus_handle(cx);
22803 move |window, cx| {
22804 Tooltip::for_action_in(
22805 "Unstage Hunk",
22806 &::git::ToggleStaged,
22807 &focus_handle,
22808 window,
22809 cx,
22810 )
22811 }
22812 })
22813 .on_click({
22814 let editor = editor.clone();
22815 move |_event, _window, cx| {
22816 editor.update(cx, |editor, cx| {
22817 editor.stage_or_unstage_diff_hunks(
22818 false,
22819 vec![hunk_range.start..hunk_range.start],
22820 cx,
22821 );
22822 });
22823 }
22824 })
22825 })
22826 .child(
22827 Button::new(("restore", row as u64), "Restore")
22828 .tooltip({
22829 let focus_handle = editor.focus_handle(cx);
22830 move |window, cx| {
22831 Tooltip::for_action_in(
22832 "Restore Hunk",
22833 &::git::Restore,
22834 &focus_handle,
22835 window,
22836 cx,
22837 )
22838 }
22839 })
22840 .on_click({
22841 let editor = editor.clone();
22842 move |_event, window, cx| {
22843 editor.update(cx, |editor, cx| {
22844 let snapshot = editor.snapshot(window, cx);
22845 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22846 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22847 });
22848 }
22849 })
22850 .disabled(is_created_file),
22851 )
22852 .when(
22853 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22854 |el| {
22855 el.child(
22856 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22857 .shape(IconButtonShape::Square)
22858 .icon_size(IconSize::Small)
22859 // .disabled(!has_multiple_hunks)
22860 .tooltip({
22861 let focus_handle = editor.focus_handle(cx);
22862 move |window, cx| {
22863 Tooltip::for_action_in(
22864 "Next Hunk",
22865 &GoToHunk,
22866 &focus_handle,
22867 window,
22868 cx,
22869 )
22870 }
22871 })
22872 .on_click({
22873 let editor = editor.clone();
22874 move |_event, window, cx| {
22875 editor.update(cx, |editor, cx| {
22876 let snapshot = editor.snapshot(window, cx);
22877 let position =
22878 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22879 editor.go_to_hunk_before_or_after_position(
22880 &snapshot,
22881 position,
22882 Direction::Next,
22883 window,
22884 cx,
22885 );
22886 editor.expand_selected_diff_hunks(cx);
22887 });
22888 }
22889 }),
22890 )
22891 .child(
22892 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22893 .shape(IconButtonShape::Square)
22894 .icon_size(IconSize::Small)
22895 // .disabled(!has_multiple_hunks)
22896 .tooltip({
22897 let focus_handle = editor.focus_handle(cx);
22898 move |window, cx| {
22899 Tooltip::for_action_in(
22900 "Previous Hunk",
22901 &GoToPreviousHunk,
22902 &focus_handle,
22903 window,
22904 cx,
22905 )
22906 }
22907 })
22908 .on_click({
22909 let editor = editor.clone();
22910 move |_event, window, cx| {
22911 editor.update(cx, |editor, cx| {
22912 let snapshot = editor.snapshot(window, cx);
22913 let point =
22914 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22915 editor.go_to_hunk_before_or_after_position(
22916 &snapshot,
22917 point,
22918 Direction::Prev,
22919 window,
22920 cx,
22921 );
22922 editor.expand_selected_diff_hunks(cx);
22923 });
22924 }
22925 }),
22926 )
22927 },
22928 )
22929 .into_any_element()
22930}