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 min_lines: usize,
479 max_lines: usize,
480 },
481 Full {
482 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
483 scale_ui_elements_with_buffer_font_size: bool,
484 /// When set to `true`, the editor will render a background for the active line.
485 show_active_line_background: bool,
486 /// When set to `true`, the editor's height will be determined by its content.
487 sized_by_content: bool,
488 },
489 Minimap {
490 parent: WeakEntity<Editor>,
491 },
492}
493
494impl EditorMode {
495 pub fn full() -> Self {
496 Self::Full {
497 scale_ui_elements_with_buffer_font_size: true,
498 show_active_line_background: true,
499 sized_by_content: false,
500 }
501 }
502
503 pub fn is_full(&self) -> bool {
504 matches!(self, Self::Full { .. })
505 }
506
507 fn is_minimap(&self) -> bool {
508 matches!(self, Self::Minimap { .. })
509 }
510}
511
512#[derive(Copy, Clone, Debug)]
513pub enum SoftWrap {
514 /// Prefer not to wrap at all.
515 ///
516 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
517 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
518 GitDiff,
519 /// Prefer a single line generally, unless an overly long line is encountered.
520 None,
521 /// Soft wrap lines that exceed the editor width.
522 EditorWidth,
523 /// Soft wrap lines at the preferred line length.
524 Column(u32),
525 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
526 Bounded(u32),
527}
528
529#[derive(Clone)]
530pub struct EditorStyle {
531 pub background: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub inline_completion_styles: InlineCompletionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 local_player: PlayerColor::default(),
548 text: TextStyle::default(),
549 scrollbar_width: Pixels::default(),
550 syntax: Default::default(),
551 // HACK: Status colors don't have a real default.
552 // We should look into removing the status colors from the editor
553 // style and retrieve them directly from the theme.
554 status: StatusColors::dark(),
555 inlay_hints_style: HighlightStyle::default(),
556 inline_completion_styles: InlineCompletionStyles {
557 insertion: HighlightStyle::default(),
558 whitespace: HighlightStyle::default(),
559 },
560 unnecessary_code_fade: Default::default(),
561 show_underlines: true,
562 }
563 }
564}
565
566pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
567 let show_background = language_settings::language_settings(None, None, cx)
568 .inlay_hints
569 .show_background;
570
571 HighlightStyle {
572 color: Some(cx.theme().status().hint),
573 background_color: show_background.then(|| cx.theme().status().hint_background),
574 ..HighlightStyle::default()
575 }
576}
577
578pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
579 InlineCompletionStyles {
580 insertion: HighlightStyle {
581 color: Some(cx.theme().status().predictive),
582 ..HighlightStyle::default()
583 },
584 whitespace: HighlightStyle {
585 background_color: Some(cx.theme().status().created_background),
586 ..HighlightStyle::default()
587 },
588 }
589}
590
591type CompletionId = usize;
592
593pub(crate) enum EditDisplayMode {
594 TabAccept,
595 DiffPopover,
596 Inline,
597}
598
599enum InlineCompletion {
600 Edit {
601 edits: Vec<(Range<Anchor>, String)>,
602 edit_preview: Option<EditPreview>,
603 display_mode: EditDisplayMode,
604 snapshot: BufferSnapshot,
605 },
606 Move {
607 target: Anchor,
608 snapshot: BufferSnapshot,
609 },
610}
611
612struct InlineCompletionState {
613 inlay_ids: Vec<InlayId>,
614 completion: InlineCompletion,
615 completion_id: Option<SharedString>,
616 invalidation_range: Range<Anchor>,
617}
618
619enum EditPredictionSettings {
620 Disabled,
621 Enabled {
622 show_in_menu: bool,
623 preview_requires_modifier: bool,
624 },
625}
626
627enum InlineCompletionHighlight {}
628
629#[derive(Debug, Clone)]
630struct InlineDiagnostic {
631 message: SharedString,
632 group_id: usize,
633 is_primary: bool,
634 start: Point,
635 severity: lsp::DiagnosticSeverity,
636}
637
638pub enum MenuInlineCompletionsPolicy {
639 Never,
640 ByProvider,
641}
642
643pub enum EditPredictionPreview {
644 /// Modifier is not pressed
645 Inactive { released_too_fast: bool },
646 /// Modifier pressed
647 Active {
648 since: Instant,
649 previous_scroll_position: Option<ScrollAnchor>,
650 },
651}
652
653impl EditPredictionPreview {
654 pub fn released_too_fast(&self) -> bool {
655 match self {
656 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
657 EditPredictionPreview::Active { .. } => false,
658 }
659 }
660
661 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
662 if let EditPredictionPreview::Active {
663 previous_scroll_position,
664 ..
665 } = self
666 {
667 *previous_scroll_position = scroll_position;
668 }
669 }
670}
671
672pub struct ContextMenuOptions {
673 pub min_entries_visible: usize,
674 pub max_entries_visible: usize,
675 pub placement: Option<ContextMenuPlacement>,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq)]
679pub enum ContextMenuPlacement {
680 Above,
681 Below,
682}
683
684#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
685struct EditorActionId(usize);
686
687impl EditorActionId {
688 pub fn post_inc(&mut self) -> Self {
689 let answer = self.0;
690
691 *self = Self(answer + 1);
692
693 Self(answer)
694 }
695}
696
697// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
698// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
699
700type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
701type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
702
703#[derive(Default)]
704struct ScrollbarMarkerState {
705 scrollbar_size: Size<Pixels>,
706 dirty: bool,
707 markers: Arc<[PaintQuad]>,
708 pending_refresh: Option<Task<Result<()>>>,
709}
710
711impl ScrollbarMarkerState {
712 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
713 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
714 }
715}
716
717#[derive(Clone, Copy, PartialEq, Eq)]
718pub enum MinimapVisibility {
719 Disabled,
720 Enabled {
721 /// The configuration currently present in the users settings.
722 setting_configuration: bool,
723 /// Whether to override the currently set visibility from the users setting.
724 toggle_override: bool,
725 },
726}
727
728impl MinimapVisibility {
729 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
730 if mode.is_full() {
731 Self::Enabled {
732 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
733 toggle_override: false,
734 }
735 } else {
736 Self::Disabled
737 }
738 }
739
740 fn hidden(&self) -> Self {
741 match *self {
742 Self::Enabled {
743 setting_configuration,
744 ..
745 } => Self::Enabled {
746 setting_configuration,
747 toggle_override: setting_configuration,
748 },
749 Self::Disabled => Self::Disabled,
750 }
751 }
752
753 fn disabled(&self) -> bool {
754 match *self {
755 Self::Disabled => true,
756 _ => false,
757 }
758 }
759
760 fn settings_visibility(&self) -> bool {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => setting_configuration,
766 _ => false,
767 }
768 }
769
770 fn visible(&self) -> bool {
771 match *self {
772 Self::Enabled {
773 setting_configuration,
774 toggle_override,
775 } => setting_configuration ^ toggle_override,
776 _ => false,
777 }
778 }
779
780 fn toggle_visibility(&self) -> Self {
781 match *self {
782 Self::Enabled {
783 toggle_override,
784 setting_configuration,
785 } => Self::Enabled {
786 setting_configuration,
787 toggle_override: !toggle_override,
788 },
789 Self::Disabled => Self::Disabled,
790 }
791 }
792}
793
794#[derive(Clone, Debug)]
795struct RunnableTasks {
796 templates: Vec<(TaskSourceKind, TaskTemplate)>,
797 offset: multi_buffer::Anchor,
798 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
799 column: u32,
800 // Values of all named captures, including those starting with '_'
801 extra_variables: HashMap<String, String>,
802 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
803 context_range: Range<BufferOffset>,
804}
805
806impl RunnableTasks {
807 fn resolve<'a>(
808 &'a self,
809 cx: &'a task::TaskContext,
810 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
811 self.templates.iter().filter_map(|(kind, template)| {
812 template
813 .resolve_task(&kind.to_id_base(), cx)
814 .map(|task| (kind.clone(), task))
815 })
816 }
817}
818
819#[derive(Clone)]
820pub struct ResolvedTasks {
821 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
822 position: Anchor,
823}
824
825#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
826struct BufferOffset(usize);
827
828// Addons allow storing per-editor state in other crates (e.g. Vim)
829pub trait Addon: 'static {
830 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
831
832 fn render_buffer_header_controls(
833 &self,
834 _: &ExcerptInfo,
835 _: &Window,
836 _: &App,
837 ) -> Option<AnyElement> {
838 None
839 }
840
841 fn to_any(&self) -> &dyn std::any::Any;
842
843 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
844 None
845 }
846}
847
848/// A set of caret positions, registered when the editor was edited.
849pub struct ChangeList {
850 changes: Vec<Vec<Anchor>>,
851 /// Currently "selected" change.
852 position: Option<usize>,
853}
854
855impl ChangeList {
856 pub fn new() -> Self {
857 Self {
858 changes: Vec::new(),
859 position: None,
860 }
861 }
862
863 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
864 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
865 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
866 if self.changes.is_empty() {
867 return None;
868 }
869
870 let prev = self.position.unwrap_or(self.changes.len());
871 let next = if direction == Direction::Prev {
872 prev.saturating_sub(count)
873 } else {
874 (prev + count).min(self.changes.len() - 1)
875 };
876 self.position = Some(next);
877 self.changes.get(next).map(|anchors| anchors.as_slice())
878 }
879
880 /// Adds a new change to the list, resetting the change list position.
881 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
882 self.position.take();
883 if pop_state {
884 self.changes.pop();
885 }
886 self.changes.push(new_positions.clone());
887 }
888
889 pub fn last(&self) -> Option<&[Anchor]> {
890 self.changes.last().map(|anchors| anchors.as_slice())
891 }
892}
893
894#[derive(Clone)]
895struct InlineBlamePopoverState {
896 scroll_handle: ScrollHandle,
897 commit_message: Option<ParsedCommitMessage>,
898 markdown: Entity<Markdown>,
899}
900
901struct InlineBlamePopover {
902 position: gpui::Point<Pixels>,
903 hide_task: Option<Task<()>>,
904 popover_bounds: Option<Bounds<Pixels>>,
905 popover_state: InlineBlamePopoverState,
906}
907
908enum SelectionDragState {
909 /// State when no drag related activity is detected.
910 None,
911 /// State when the mouse is down on a selection that is about to be dragged.
912 ReadyToDrag {
913 selection: Selection<Anchor>,
914 click_position: gpui::Point<Pixels>,
915 mouse_down_time: Instant,
916 },
917 /// State when the mouse is dragging the selection in the editor.
918 Dragging {
919 selection: Selection<Anchor>,
920 drop_cursor: Selection<Anchor>,
921 hide_drop_cursor: bool,
922 },
923}
924
925/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
926/// a breakpoint on them.
927#[derive(Clone, Copy, Debug, PartialEq, Eq)]
928struct PhantomBreakpointIndicator {
929 display_row: DisplayRow,
930 /// There's a small debounce between hovering over the line and showing the indicator.
931 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
932 is_active: bool,
933 collides_with_existing_breakpoint: bool,
934}
935
936/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
937///
938/// See the [module level documentation](self) for more information.
939pub struct Editor {
940 focus_handle: FocusHandle,
941 last_focused_descendant: Option<WeakFocusHandle>,
942 /// The text buffer being edited
943 buffer: Entity<MultiBuffer>,
944 /// Map of how text in the buffer should be displayed.
945 /// Handles soft wraps, folds, fake inlay text insertions, etc.
946 pub display_map: Entity<DisplayMap>,
947 pub selections: SelectionsCollection,
948 pub scroll_manager: ScrollManager,
949 /// When inline assist editors are linked, they all render cursors because
950 /// typing enters text into each of them, even the ones that aren't focused.
951 pub(crate) show_cursor_when_unfocused: bool,
952 columnar_selection_tail: Option<Anchor>,
953 columnar_display_point: Option<DisplayPoint>,
954 add_selections_state: Option<AddSelectionsState>,
955 select_next_state: Option<SelectNextState>,
956 select_prev_state: Option<SelectNextState>,
957 selection_history: SelectionHistory,
958 defer_selection_effects: bool,
959 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
960 autoclose_regions: Vec<AutocloseRegion>,
961 snippet_stack: InvalidationStack<SnippetState>,
962 select_syntax_node_history: SelectSyntaxNodeHistory,
963 ime_transaction: Option<TransactionId>,
964 pub diagnostics_max_severity: DiagnosticSeverity,
965 active_diagnostics: ActiveDiagnostic,
966 show_inline_diagnostics: bool,
967 inline_diagnostics_update: Task<()>,
968 inline_diagnostics_enabled: bool,
969 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
970 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
971 hard_wrap: Option<usize>,
972
973 // TODO: make this a access method
974 pub project: Option<Entity<Project>>,
975 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
976 completion_provider: Option<Rc<dyn CompletionProvider>>,
977 collaboration_hub: Option<Box<dyn CollaborationHub>>,
978 blink_manager: Entity<BlinkManager>,
979 show_cursor_names: bool,
980 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
981 pub show_local_selections: bool,
982 mode: EditorMode,
983 show_breadcrumbs: bool,
984 show_gutter: bool,
985 show_scrollbars: ScrollbarAxes,
986 minimap_visibility: MinimapVisibility,
987 offset_content: bool,
988 disable_expand_excerpt_buttons: bool,
989 show_line_numbers: Option<bool>,
990 use_relative_line_numbers: Option<bool>,
991 show_git_diff_gutter: Option<bool>,
992 show_code_actions: Option<bool>,
993 show_runnables: Option<bool>,
994 show_breakpoints: Option<bool>,
995 show_wrap_guides: Option<bool>,
996 show_indent_guides: Option<bool>,
997 placeholder_text: Option<Arc<str>>,
998 highlight_order: usize,
999 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1000 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1001 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1002 scrollbar_marker_state: ScrollbarMarkerState,
1003 active_indent_guides_state: ActiveIndentGuidesState,
1004 nav_history: Option<ItemNavHistory>,
1005 context_menu: RefCell<Option<CodeContextMenu>>,
1006 context_menu_options: Option<ContextMenuOptions>,
1007 mouse_context_menu: Option<MouseContextMenu>,
1008 completion_tasks: Vec<(CompletionId, Task<()>)>,
1009 inline_blame_popover: Option<InlineBlamePopover>,
1010 inline_blame_popover_show_task: Option<Task<()>>,
1011 signature_help_state: SignatureHelpState,
1012 auto_signature_help: Option<bool>,
1013 find_all_references_task_sources: Vec<Anchor>,
1014 next_completion_id: CompletionId,
1015 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1016 code_actions_task: Option<Task<Result<()>>>,
1017 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1018 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1019 document_highlights_task: Option<Task<()>>,
1020 linked_editing_range_task: Option<Task<Option<()>>>,
1021 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1022 pending_rename: Option<RenameState>,
1023 searchable: bool,
1024 cursor_shape: CursorShape,
1025 current_line_highlight: Option<CurrentLineHighlight>,
1026 collapse_matches: bool,
1027 autoindent_mode: Option<AutoindentMode>,
1028 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1029 input_enabled: bool,
1030 use_modal_editing: bool,
1031 read_only: bool,
1032 leader_id: Option<CollaboratorId>,
1033 remote_id: Option<ViewId>,
1034 pub hover_state: HoverState,
1035 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1036 gutter_hovered: bool,
1037 hovered_link_state: Option<HoveredLinkState>,
1038 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1039 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1040 active_inline_completion: Option<InlineCompletionState>,
1041 /// Used to prevent flickering as the user types while the menu is open
1042 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1043 edit_prediction_settings: EditPredictionSettings,
1044 inline_completions_hidden_for_vim_mode: bool,
1045 show_inline_completions_override: Option<bool>,
1046 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1047 edit_prediction_preview: EditPredictionPreview,
1048 edit_prediction_indent_conflict: bool,
1049 edit_prediction_requires_modifier_in_indent_conflict: bool,
1050 inlay_hint_cache: InlayHintCache,
1051 next_inlay_id: usize,
1052 _subscriptions: Vec<Subscription>,
1053 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1054 gutter_dimensions: GutterDimensions,
1055 style: Option<EditorStyle>,
1056 text_style_refinement: Option<TextStyleRefinement>,
1057 next_editor_action_id: EditorActionId,
1058 editor_actions: Rc<
1059 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1060 >,
1061 use_autoclose: bool,
1062 use_auto_surround: bool,
1063 auto_replace_emoji_shortcode: bool,
1064 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1065 show_git_blame_gutter: bool,
1066 show_git_blame_inline: bool,
1067 show_git_blame_inline_delay_task: Option<Task<()>>,
1068 git_blame_inline_enabled: bool,
1069 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1070 serialize_dirty_buffers: bool,
1071 show_selection_menu: Option<bool>,
1072 blame: Option<Entity<GitBlame>>,
1073 blame_subscription: Option<Subscription>,
1074 custom_context_menu: Option<
1075 Box<
1076 dyn 'static
1077 + Fn(
1078 &mut Self,
1079 DisplayPoint,
1080 &mut Window,
1081 &mut Context<Self>,
1082 ) -> Option<Entity<ui::ContextMenu>>,
1083 >,
1084 >,
1085 last_bounds: Option<Bounds<Pixels>>,
1086 last_position_map: Option<Rc<PositionMap>>,
1087 expect_bounds_change: Option<Bounds<Pixels>>,
1088 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1089 tasks_update_task: Option<Task<()>>,
1090 breakpoint_store: Option<Entity<BreakpointStore>>,
1091 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1092 hovered_diff_hunk_row: Option<DisplayRow>,
1093 pull_diagnostics_task: Task<()>,
1094 in_project_search: bool,
1095 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1096 breadcrumb_header: Option<String>,
1097 focused_block: Option<FocusedBlock>,
1098 next_scroll_position: NextScrollCursorCenterTopBottom,
1099 addons: HashMap<TypeId, Box<dyn Addon>>,
1100 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1101 load_diff_task: Option<Shared<Task<()>>>,
1102 /// Whether we are temporarily displaying a diff other than git's
1103 temporary_diff_override: bool,
1104 selection_mark_mode: bool,
1105 toggle_fold_multiple_buffers: Task<()>,
1106 _scroll_cursor_center_top_bottom_task: Task<()>,
1107 serialize_selections: Task<()>,
1108 serialize_folds: Task<()>,
1109 mouse_cursor_hidden: bool,
1110 minimap: Option<Entity<Self>>,
1111 hide_mouse_mode: HideMouseMode,
1112 pub change_list: ChangeList,
1113 inline_value_cache: InlineValueCache,
1114 selection_drag_state: SelectionDragState,
1115 drag_and_drop_selection_enabled: bool,
1116}
1117
1118#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1119enum NextScrollCursorCenterTopBottom {
1120 #[default]
1121 Center,
1122 Top,
1123 Bottom,
1124}
1125
1126impl NextScrollCursorCenterTopBottom {
1127 fn next(&self) -> Self {
1128 match self {
1129 Self::Center => Self::Top,
1130 Self::Top => Self::Bottom,
1131 Self::Bottom => Self::Center,
1132 }
1133 }
1134}
1135
1136#[derive(Clone)]
1137pub struct EditorSnapshot {
1138 pub mode: EditorMode,
1139 show_gutter: bool,
1140 show_line_numbers: Option<bool>,
1141 show_git_diff_gutter: Option<bool>,
1142 show_code_actions: Option<bool>,
1143 show_runnables: Option<bool>,
1144 show_breakpoints: Option<bool>,
1145 git_blame_gutter_max_author_length: Option<usize>,
1146 pub display_snapshot: DisplaySnapshot,
1147 pub placeholder_text: Option<Arc<str>>,
1148 is_focused: bool,
1149 scroll_anchor: ScrollAnchor,
1150 ongoing_scroll: OngoingScroll,
1151 current_line_highlight: CurrentLineHighlight,
1152 gutter_hovered: bool,
1153}
1154
1155#[derive(Default, Debug, Clone, Copy)]
1156pub struct GutterDimensions {
1157 pub left_padding: Pixels,
1158 pub right_padding: Pixels,
1159 pub width: Pixels,
1160 pub margin: Pixels,
1161 pub git_blame_entries_width: Option<Pixels>,
1162}
1163
1164impl GutterDimensions {
1165 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1166 Self {
1167 margin: Self::default_gutter_margin(font_id, font_size, cx),
1168 ..Default::default()
1169 }
1170 }
1171
1172 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1173 -cx.text_system().descent(font_id, font_size)
1174 }
1175 /// The full width of the space taken up by the gutter.
1176 pub fn full_width(&self) -> Pixels {
1177 self.margin + self.width
1178 }
1179
1180 /// The width of the space reserved for the fold indicators,
1181 /// use alongside 'justify_end' and `gutter_width` to
1182 /// right align content with the line numbers
1183 pub fn fold_area_width(&self) -> Pixels {
1184 self.margin + self.right_padding
1185 }
1186}
1187
1188#[derive(Debug)]
1189pub struct RemoteSelection {
1190 pub replica_id: ReplicaId,
1191 pub selection: Selection<Anchor>,
1192 pub cursor_shape: CursorShape,
1193 pub collaborator_id: CollaboratorId,
1194 pub line_mode: bool,
1195 pub user_name: Option<SharedString>,
1196 pub color: PlayerColor,
1197}
1198
1199#[derive(Clone, Debug)]
1200struct SelectionHistoryEntry {
1201 selections: Arc<[Selection<Anchor>]>,
1202 select_next_state: Option<SelectNextState>,
1203 select_prev_state: Option<SelectNextState>,
1204 add_selections_state: Option<AddSelectionsState>,
1205}
1206
1207#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1208enum SelectionHistoryMode {
1209 Normal,
1210 Undoing,
1211 Redoing,
1212 Skipping,
1213}
1214
1215#[derive(Clone, PartialEq, Eq, Hash)]
1216struct HoveredCursor {
1217 replica_id: u16,
1218 selection_id: usize,
1219}
1220
1221impl Default for SelectionHistoryMode {
1222 fn default() -> Self {
1223 Self::Normal
1224 }
1225}
1226
1227#[derive(Debug)]
1228pub struct SelectionEffects {
1229 nav_history: bool,
1230 completions: bool,
1231 scroll: Option<Autoscroll>,
1232}
1233
1234impl Default for SelectionEffects {
1235 fn default() -> Self {
1236 Self {
1237 nav_history: true,
1238 completions: true,
1239 scroll: Some(Autoscroll::fit()),
1240 }
1241 }
1242}
1243impl SelectionEffects {
1244 pub fn scroll(scroll: Autoscroll) -> Self {
1245 Self {
1246 scroll: Some(scroll),
1247 ..Default::default()
1248 }
1249 }
1250
1251 pub fn no_scroll() -> Self {
1252 Self {
1253 scroll: None,
1254 ..Default::default()
1255 }
1256 }
1257
1258 pub fn completions(self, completions: bool) -> Self {
1259 Self {
1260 completions,
1261 ..self
1262 }
1263 }
1264
1265 pub fn nav_history(self, nav_history: bool) -> Self {
1266 Self {
1267 nav_history,
1268 ..self
1269 }
1270 }
1271}
1272
1273struct DeferredSelectionEffectsState {
1274 changed: bool,
1275 effects: SelectionEffects,
1276 old_cursor_position: Anchor,
1277 history_entry: SelectionHistoryEntry,
1278}
1279
1280#[derive(Default)]
1281struct SelectionHistory {
1282 #[allow(clippy::type_complexity)]
1283 selections_by_transaction:
1284 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1285 mode: SelectionHistoryMode,
1286 undo_stack: VecDeque<SelectionHistoryEntry>,
1287 redo_stack: VecDeque<SelectionHistoryEntry>,
1288}
1289
1290impl SelectionHistory {
1291 #[track_caller]
1292 fn insert_transaction(
1293 &mut self,
1294 transaction_id: TransactionId,
1295 selections: Arc<[Selection<Anchor>]>,
1296 ) {
1297 if selections.is_empty() {
1298 log::error!(
1299 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1300 std::panic::Location::caller()
1301 );
1302 return;
1303 }
1304 self.selections_by_transaction
1305 .insert(transaction_id, (selections, None));
1306 }
1307
1308 #[allow(clippy::type_complexity)]
1309 fn transaction(
1310 &self,
1311 transaction_id: TransactionId,
1312 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1313 self.selections_by_transaction.get(&transaction_id)
1314 }
1315
1316 #[allow(clippy::type_complexity)]
1317 fn transaction_mut(
1318 &mut self,
1319 transaction_id: TransactionId,
1320 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1321 self.selections_by_transaction.get_mut(&transaction_id)
1322 }
1323
1324 fn push(&mut self, entry: SelectionHistoryEntry) {
1325 if !entry.selections.is_empty() {
1326 match self.mode {
1327 SelectionHistoryMode::Normal => {
1328 self.push_undo(entry);
1329 self.redo_stack.clear();
1330 }
1331 SelectionHistoryMode::Undoing => self.push_redo(entry),
1332 SelectionHistoryMode::Redoing => self.push_undo(entry),
1333 SelectionHistoryMode::Skipping => {}
1334 }
1335 }
1336 }
1337
1338 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1339 if self
1340 .undo_stack
1341 .back()
1342 .map_or(true, |e| e.selections != entry.selections)
1343 {
1344 self.undo_stack.push_back(entry);
1345 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1346 self.undo_stack.pop_front();
1347 }
1348 }
1349 }
1350
1351 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1352 if self
1353 .redo_stack
1354 .back()
1355 .map_or(true, |e| e.selections != entry.selections)
1356 {
1357 self.redo_stack.push_back(entry);
1358 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1359 self.redo_stack.pop_front();
1360 }
1361 }
1362 }
1363}
1364
1365#[derive(Clone, Copy)]
1366pub struct RowHighlightOptions {
1367 pub autoscroll: bool,
1368 pub include_gutter: bool,
1369}
1370
1371impl Default for RowHighlightOptions {
1372 fn default() -> Self {
1373 Self {
1374 autoscroll: Default::default(),
1375 include_gutter: true,
1376 }
1377 }
1378}
1379
1380struct RowHighlight {
1381 index: usize,
1382 range: Range<Anchor>,
1383 color: Hsla,
1384 options: RowHighlightOptions,
1385 type_id: TypeId,
1386}
1387
1388#[derive(Clone, Debug)]
1389struct AddSelectionsState {
1390 groups: Vec<AddSelectionsGroup>,
1391}
1392
1393#[derive(Clone, Debug)]
1394struct AddSelectionsGroup {
1395 above: bool,
1396 stack: Vec<usize>,
1397}
1398
1399#[derive(Clone)]
1400struct SelectNextState {
1401 query: AhoCorasick,
1402 wordwise: bool,
1403 done: bool,
1404}
1405
1406impl std::fmt::Debug for SelectNextState {
1407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1408 f.debug_struct(std::any::type_name::<Self>())
1409 .field("wordwise", &self.wordwise)
1410 .field("done", &self.done)
1411 .finish()
1412 }
1413}
1414
1415#[derive(Debug)]
1416struct AutocloseRegion {
1417 selection_id: usize,
1418 range: Range<Anchor>,
1419 pair: BracketPair,
1420}
1421
1422#[derive(Debug)]
1423struct SnippetState {
1424 ranges: Vec<Vec<Range<Anchor>>>,
1425 active_index: usize,
1426 choices: Vec<Option<Vec<String>>>,
1427}
1428
1429#[doc(hidden)]
1430pub struct RenameState {
1431 pub range: Range<Anchor>,
1432 pub old_name: Arc<str>,
1433 pub editor: Entity<Editor>,
1434 block_id: CustomBlockId,
1435}
1436
1437struct InvalidationStack<T>(Vec<T>);
1438
1439struct RegisteredInlineCompletionProvider {
1440 provider: Arc<dyn InlineCompletionProviderHandle>,
1441 _subscription: Subscription,
1442}
1443
1444#[derive(Debug, PartialEq, Eq)]
1445pub struct ActiveDiagnosticGroup {
1446 pub active_range: Range<Anchor>,
1447 pub active_message: String,
1448 pub group_id: usize,
1449 pub blocks: HashSet<CustomBlockId>,
1450}
1451
1452#[derive(Debug, PartialEq, Eq)]
1453
1454pub(crate) enum ActiveDiagnostic {
1455 None,
1456 All,
1457 Group(ActiveDiagnosticGroup),
1458}
1459
1460#[derive(Serialize, Deserialize, Clone, Debug)]
1461pub struct ClipboardSelection {
1462 /// The number of bytes in this selection.
1463 pub len: usize,
1464 /// Whether this was a full-line selection.
1465 pub is_entire_line: bool,
1466 /// The indentation of the first line when this content was originally copied.
1467 pub first_line_indent: u32,
1468}
1469
1470// selections, scroll behavior, was newest selection reversed
1471type SelectSyntaxNodeHistoryState = (
1472 Box<[Selection<usize>]>,
1473 SelectSyntaxNodeScrollBehavior,
1474 bool,
1475);
1476
1477#[derive(Default)]
1478struct SelectSyntaxNodeHistory {
1479 stack: Vec<SelectSyntaxNodeHistoryState>,
1480 // disable temporarily to allow changing selections without losing the stack
1481 pub disable_clearing: bool,
1482}
1483
1484impl SelectSyntaxNodeHistory {
1485 pub fn try_clear(&mut self) {
1486 if !self.disable_clearing {
1487 self.stack.clear();
1488 }
1489 }
1490
1491 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1492 self.stack.push(selection);
1493 }
1494
1495 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1496 self.stack.pop()
1497 }
1498}
1499
1500enum SelectSyntaxNodeScrollBehavior {
1501 CursorTop,
1502 FitSelection,
1503 CursorBottom,
1504}
1505
1506#[derive(Debug)]
1507pub(crate) struct NavigationData {
1508 cursor_anchor: Anchor,
1509 cursor_position: Point,
1510 scroll_anchor: ScrollAnchor,
1511 scroll_top_row: u32,
1512}
1513
1514#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1515pub enum GotoDefinitionKind {
1516 Symbol,
1517 Declaration,
1518 Type,
1519 Implementation,
1520}
1521
1522#[derive(Debug, Clone)]
1523enum InlayHintRefreshReason {
1524 ModifiersChanged(bool),
1525 Toggle(bool),
1526 SettingsChange(InlayHintSettings),
1527 NewLinesShown,
1528 BufferEdited(HashSet<Arc<Language>>),
1529 RefreshRequested,
1530 ExcerptsRemoved(Vec<ExcerptId>),
1531}
1532
1533impl InlayHintRefreshReason {
1534 fn description(&self) -> &'static str {
1535 match self {
1536 Self::ModifiersChanged(_) => "modifiers changed",
1537 Self::Toggle(_) => "toggle",
1538 Self::SettingsChange(_) => "settings change",
1539 Self::NewLinesShown => "new lines shown",
1540 Self::BufferEdited(_) => "buffer edited",
1541 Self::RefreshRequested => "refresh requested",
1542 Self::ExcerptsRemoved(_) => "excerpts removed",
1543 }
1544 }
1545}
1546
1547pub enum FormatTarget {
1548 Buffers(HashSet<Entity<Buffer>>),
1549 Ranges(Vec<Range<MultiBufferPoint>>),
1550}
1551
1552pub(crate) struct FocusedBlock {
1553 id: BlockId,
1554 focus_handle: WeakFocusHandle,
1555}
1556
1557#[derive(Clone)]
1558enum JumpData {
1559 MultiBufferRow {
1560 row: MultiBufferRow,
1561 line_offset_from_top: u32,
1562 },
1563 MultiBufferPoint {
1564 excerpt_id: ExcerptId,
1565 position: Point,
1566 anchor: text::Anchor,
1567 line_offset_from_top: u32,
1568 },
1569}
1570
1571pub enum MultibufferSelectionMode {
1572 First,
1573 All,
1574}
1575
1576#[derive(Clone, Copy, Debug, Default)]
1577pub struct RewrapOptions {
1578 pub override_language_settings: bool,
1579 pub preserve_existing_whitespace: bool,
1580}
1581
1582impl Editor {
1583 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1584 let buffer = cx.new(|cx| Buffer::local("", cx));
1585 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1586 Self::new(
1587 EditorMode::SingleLine { auto_width: false },
1588 buffer,
1589 None,
1590 window,
1591 cx,
1592 )
1593 }
1594
1595 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1596 let buffer = cx.new(|cx| Buffer::local("", cx));
1597 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1598 Self::new(EditorMode::full(), buffer, None, window, cx)
1599 }
1600
1601 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1602 let buffer = cx.new(|cx| Buffer::local("", cx));
1603 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1604 Self::new(
1605 EditorMode::SingleLine { auto_width: true },
1606 buffer,
1607 None,
1608 window,
1609 cx,
1610 )
1611 }
1612
1613 pub fn auto_height(
1614 min_lines: usize,
1615 max_lines: usize,
1616 window: &mut Window,
1617 cx: &mut Context<Self>,
1618 ) -> Self {
1619 let buffer = cx.new(|cx| Buffer::local("", cx));
1620 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1621 Self::new(
1622 EditorMode::AutoHeight {
1623 min_lines,
1624 max_lines,
1625 },
1626 buffer,
1627 None,
1628 window,
1629 cx,
1630 )
1631 }
1632
1633 pub fn for_buffer(
1634 buffer: Entity<Buffer>,
1635 project: Option<Entity<Project>>,
1636 window: &mut Window,
1637 cx: &mut Context<Self>,
1638 ) -> Self {
1639 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1640 Self::new(EditorMode::full(), buffer, project, window, cx)
1641 }
1642
1643 pub fn for_multibuffer(
1644 buffer: Entity<MultiBuffer>,
1645 project: Option<Entity<Project>>,
1646 window: &mut Window,
1647 cx: &mut Context<Self>,
1648 ) -> Self {
1649 Self::new(EditorMode::full(), buffer, project, window, cx)
1650 }
1651
1652 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1653 let mut clone = Self::new(
1654 self.mode.clone(),
1655 self.buffer.clone(),
1656 self.project.clone(),
1657 window,
1658 cx,
1659 );
1660 self.display_map.update(cx, |display_map, cx| {
1661 let snapshot = display_map.snapshot(cx);
1662 clone.display_map.update(cx, |display_map, cx| {
1663 display_map.set_state(&snapshot, cx);
1664 });
1665 });
1666 clone.folds_did_change(cx);
1667 clone.selections.clone_state(&self.selections);
1668 clone.scroll_manager.clone_state(&self.scroll_manager);
1669 clone.searchable = self.searchable;
1670 clone.read_only = self.read_only;
1671 clone
1672 }
1673
1674 pub fn new(
1675 mode: EditorMode,
1676 buffer: Entity<MultiBuffer>,
1677 project: Option<Entity<Project>>,
1678 window: &mut Window,
1679 cx: &mut Context<Self>,
1680 ) -> Self {
1681 Editor::new_internal(mode, buffer, project, None, window, cx)
1682 }
1683
1684 fn new_internal(
1685 mode: EditorMode,
1686 buffer: Entity<MultiBuffer>,
1687 project: Option<Entity<Project>>,
1688 display_map: Option<Entity<DisplayMap>>,
1689 window: &mut Window,
1690 cx: &mut Context<Self>,
1691 ) -> Self {
1692 debug_assert!(
1693 display_map.is_none() || mode.is_minimap(),
1694 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1695 );
1696
1697 let full_mode = mode.is_full();
1698 let diagnostics_max_severity = if full_mode {
1699 EditorSettings::get_global(cx)
1700 .diagnostics_max_severity
1701 .unwrap_or(DiagnosticSeverity::Hint)
1702 } else {
1703 DiagnosticSeverity::Off
1704 };
1705 let style = window.text_style();
1706 let font_size = style.font_size.to_pixels(window.rem_size());
1707 let editor = cx.entity().downgrade();
1708 let fold_placeholder = FoldPlaceholder {
1709 constrain_width: true,
1710 render: Arc::new(move |fold_id, fold_range, cx| {
1711 let editor = editor.clone();
1712 div()
1713 .id(fold_id)
1714 .bg(cx.theme().colors().ghost_element_background)
1715 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1716 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1717 .rounded_xs()
1718 .size_full()
1719 .cursor_pointer()
1720 .child("⋯")
1721 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1722 .on_click(move |_, _window, cx| {
1723 editor
1724 .update(cx, |editor, cx| {
1725 editor.unfold_ranges(
1726 &[fold_range.start..fold_range.end],
1727 true,
1728 false,
1729 cx,
1730 );
1731 cx.stop_propagation();
1732 })
1733 .ok();
1734 })
1735 .into_any()
1736 }),
1737 merge_adjacent: true,
1738 ..FoldPlaceholder::default()
1739 };
1740 let display_map = display_map.unwrap_or_else(|| {
1741 cx.new(|cx| {
1742 DisplayMap::new(
1743 buffer.clone(),
1744 style.font(),
1745 font_size,
1746 None,
1747 FILE_HEADER_HEIGHT,
1748 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1749 fold_placeholder,
1750 diagnostics_max_severity,
1751 cx,
1752 )
1753 })
1754 });
1755
1756 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1757
1758 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1759
1760 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1761 .then(|| language_settings::SoftWrap::None);
1762
1763 let mut project_subscriptions = Vec::new();
1764 if mode.is_full() {
1765 if let Some(project) = project.as_ref() {
1766 project_subscriptions.push(cx.subscribe_in(
1767 project,
1768 window,
1769 |editor, _, event, window, cx| match event {
1770 project::Event::RefreshCodeLens => {
1771 // we always query lens with actions, without storing them, always refreshing them
1772 }
1773 project::Event::RefreshInlayHints => {
1774 editor
1775 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1776 }
1777 project::Event::LanguageServerAdded(..)
1778 | project::Event::LanguageServerRemoved(..) => {
1779 if editor.tasks_update_task.is_none() {
1780 editor.tasks_update_task =
1781 Some(editor.refresh_runnables(window, cx));
1782 }
1783 editor.pull_diagnostics(None, window, cx);
1784 }
1785 project::Event::SnippetEdit(id, snippet_edits) => {
1786 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1787 let focus_handle = editor.focus_handle(cx);
1788 if focus_handle.is_focused(window) {
1789 let snapshot = buffer.read(cx).snapshot();
1790 for (range, snippet) in snippet_edits {
1791 let editor_range =
1792 language::range_from_lsp(*range).to_offset(&snapshot);
1793 editor
1794 .insert_snippet(
1795 &[editor_range],
1796 snippet.clone(),
1797 window,
1798 cx,
1799 )
1800 .ok();
1801 }
1802 }
1803 }
1804 }
1805 _ => {}
1806 },
1807 ));
1808 if let Some(task_inventory) = project
1809 .read(cx)
1810 .task_store()
1811 .read(cx)
1812 .task_inventory()
1813 .cloned()
1814 {
1815 project_subscriptions.push(cx.observe_in(
1816 &task_inventory,
1817 window,
1818 |editor, _, window, cx| {
1819 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1820 },
1821 ));
1822 };
1823
1824 project_subscriptions.push(cx.subscribe_in(
1825 &project.read(cx).breakpoint_store(),
1826 window,
1827 |editor, _, event, window, cx| match event {
1828 BreakpointStoreEvent::ClearDebugLines => {
1829 editor.clear_row_highlights::<ActiveDebugLine>();
1830 editor.refresh_inline_values(cx);
1831 }
1832 BreakpointStoreEvent::SetDebugLine => {
1833 if editor.go_to_active_debug_line(window, cx) {
1834 cx.stop_propagation();
1835 }
1836
1837 editor.refresh_inline_values(cx);
1838 }
1839 _ => {}
1840 },
1841 ));
1842 }
1843 }
1844
1845 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1846
1847 let inlay_hint_settings =
1848 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1849 let focus_handle = cx.focus_handle();
1850 cx.on_focus(&focus_handle, window, Self::handle_focus)
1851 .detach();
1852 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1853 .detach();
1854 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1855 .detach();
1856 cx.on_blur(&focus_handle, window, Self::handle_blur)
1857 .detach();
1858 cx.observe_pending_input(window, Self::observe_pending_input)
1859 .detach();
1860
1861 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1862 Some(false)
1863 } else {
1864 None
1865 };
1866
1867 let breakpoint_store = match (&mode, project.as_ref()) {
1868 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1869 _ => None,
1870 };
1871
1872 let mut code_action_providers = Vec::new();
1873 let mut load_uncommitted_diff = None;
1874 if let Some(project) = project.clone() {
1875 load_uncommitted_diff = Some(
1876 update_uncommitted_diff_for_buffer(
1877 cx.entity(),
1878 &project,
1879 buffer.read(cx).all_buffers(),
1880 buffer.clone(),
1881 cx,
1882 )
1883 .shared(),
1884 );
1885 code_action_providers.push(Rc::new(project) as Rc<_>);
1886 }
1887
1888 let mut editor = Self {
1889 focus_handle,
1890 show_cursor_when_unfocused: false,
1891 last_focused_descendant: None,
1892 buffer: buffer.clone(),
1893 display_map: display_map.clone(),
1894 selections,
1895 scroll_manager: ScrollManager::new(cx),
1896 columnar_selection_tail: None,
1897 columnar_display_point: None,
1898 add_selections_state: None,
1899 select_next_state: None,
1900 select_prev_state: None,
1901 selection_history: SelectionHistory::default(),
1902 defer_selection_effects: false,
1903 deferred_selection_effects_state: None,
1904 autoclose_regions: Vec::new(),
1905 snippet_stack: InvalidationStack::default(),
1906 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1907 ime_transaction: None,
1908 active_diagnostics: ActiveDiagnostic::None,
1909 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1910 inline_diagnostics_update: Task::ready(()),
1911 inline_diagnostics: Vec::new(),
1912 soft_wrap_mode_override,
1913 diagnostics_max_severity,
1914 hard_wrap: None,
1915 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1916 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1917 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1918 project,
1919 blink_manager: blink_manager.clone(),
1920 show_local_selections: true,
1921 show_scrollbars: ScrollbarAxes {
1922 horizontal: full_mode,
1923 vertical: full_mode,
1924 },
1925 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1926 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1927 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1928 show_gutter: mode.is_full(),
1929 show_line_numbers: None,
1930 use_relative_line_numbers: None,
1931 disable_expand_excerpt_buttons: false,
1932 show_git_diff_gutter: None,
1933 show_code_actions: None,
1934 show_runnables: None,
1935 show_breakpoints: None,
1936 show_wrap_guides: None,
1937 show_indent_guides,
1938 placeholder_text: None,
1939 highlight_order: 0,
1940 highlighted_rows: HashMap::default(),
1941 background_highlights: TreeMap::default(),
1942 gutter_highlights: TreeMap::default(),
1943 scrollbar_marker_state: ScrollbarMarkerState::default(),
1944 active_indent_guides_state: ActiveIndentGuidesState::default(),
1945 nav_history: None,
1946 context_menu: RefCell::new(None),
1947 context_menu_options: None,
1948 mouse_context_menu: None,
1949 completion_tasks: Vec::new(),
1950 inline_blame_popover: None,
1951 inline_blame_popover_show_task: None,
1952 signature_help_state: SignatureHelpState::default(),
1953 auto_signature_help: None,
1954 find_all_references_task_sources: Vec::new(),
1955 next_completion_id: 0,
1956 next_inlay_id: 0,
1957 code_action_providers,
1958 available_code_actions: None,
1959 code_actions_task: None,
1960 quick_selection_highlight_task: None,
1961 debounced_selection_highlight_task: None,
1962 document_highlights_task: None,
1963 linked_editing_range_task: None,
1964 pending_rename: None,
1965 searchable: true,
1966 cursor_shape: EditorSettings::get_global(cx)
1967 .cursor_shape
1968 .unwrap_or_default(),
1969 current_line_highlight: None,
1970 autoindent_mode: Some(AutoindentMode::EachLine),
1971 collapse_matches: false,
1972 workspace: None,
1973 input_enabled: true,
1974 use_modal_editing: mode.is_full(),
1975 read_only: mode.is_minimap(),
1976 use_autoclose: true,
1977 use_auto_surround: true,
1978 auto_replace_emoji_shortcode: false,
1979 jsx_tag_auto_close_enabled_in_any_buffer: false,
1980 leader_id: None,
1981 remote_id: None,
1982 hover_state: HoverState::default(),
1983 pending_mouse_down: None,
1984 hovered_link_state: None,
1985 edit_prediction_provider: None,
1986 active_inline_completion: None,
1987 stale_inline_completion_in_menu: None,
1988 edit_prediction_preview: EditPredictionPreview::Inactive {
1989 released_too_fast: false,
1990 },
1991 inline_diagnostics_enabled: mode.is_full(),
1992 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1993 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1994
1995 gutter_hovered: false,
1996 pixel_position_of_newest_cursor: None,
1997 last_bounds: None,
1998 last_position_map: None,
1999 expect_bounds_change: None,
2000 gutter_dimensions: GutterDimensions::default(),
2001 style: None,
2002 show_cursor_names: false,
2003 hovered_cursors: HashMap::default(),
2004 next_editor_action_id: EditorActionId::default(),
2005 editor_actions: Rc::default(),
2006 inline_completions_hidden_for_vim_mode: false,
2007 show_inline_completions_override: None,
2008 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2009 edit_prediction_settings: EditPredictionSettings::Disabled,
2010 edit_prediction_indent_conflict: false,
2011 edit_prediction_requires_modifier_in_indent_conflict: true,
2012 custom_context_menu: None,
2013 show_git_blame_gutter: false,
2014 show_git_blame_inline: false,
2015 show_selection_menu: None,
2016 show_git_blame_inline_delay_task: None,
2017 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2018 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2019 serialize_dirty_buffers: !mode.is_minimap()
2020 && ProjectSettings::get_global(cx)
2021 .session
2022 .restore_unsaved_buffers,
2023 blame: None,
2024 blame_subscription: None,
2025 tasks: BTreeMap::default(),
2026
2027 breakpoint_store,
2028 gutter_breakpoint_indicator: (None, None),
2029 hovered_diff_hunk_row: None,
2030 _subscriptions: vec![
2031 cx.observe(&buffer, Self::on_buffer_changed),
2032 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2033 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2034 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2035 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2036 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2037 cx.observe_window_activation(window, |editor, window, cx| {
2038 let active = window.is_window_active();
2039 editor.blink_manager.update(cx, |blink_manager, cx| {
2040 if active {
2041 blink_manager.enable(cx);
2042 } else {
2043 blink_manager.disable(cx);
2044 }
2045 });
2046 if active {
2047 editor.show_mouse_cursor();
2048 }
2049 }),
2050 ],
2051 tasks_update_task: None,
2052 pull_diagnostics_task: Task::ready(()),
2053 linked_edit_ranges: Default::default(),
2054 in_project_search: false,
2055 previous_search_ranges: None,
2056 breadcrumb_header: None,
2057 focused_block: None,
2058 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2059 addons: HashMap::default(),
2060 registered_buffers: HashMap::default(),
2061 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2062 selection_mark_mode: false,
2063 toggle_fold_multiple_buffers: Task::ready(()),
2064 serialize_selections: Task::ready(()),
2065 serialize_folds: Task::ready(()),
2066 text_style_refinement: None,
2067 load_diff_task: load_uncommitted_diff,
2068 temporary_diff_override: false,
2069 mouse_cursor_hidden: false,
2070 minimap: None,
2071 hide_mouse_mode: EditorSettings::get_global(cx)
2072 .hide_mouse
2073 .unwrap_or_default(),
2074 change_list: ChangeList::new(),
2075 mode,
2076 selection_drag_state: SelectionDragState::None,
2077 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2078 };
2079 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2080 editor
2081 ._subscriptions
2082 .push(cx.observe(breakpoints, |_, _, cx| {
2083 cx.notify();
2084 }));
2085 }
2086 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2087 editor._subscriptions.extend(project_subscriptions);
2088
2089 editor._subscriptions.push(cx.subscribe_in(
2090 &cx.entity(),
2091 window,
2092 |editor, _, e: &EditorEvent, window, cx| match e {
2093 EditorEvent::ScrollPositionChanged { local, .. } => {
2094 if *local {
2095 let new_anchor = editor.scroll_manager.anchor();
2096 let snapshot = editor.snapshot(window, cx);
2097 editor.update_restoration_data(cx, move |data| {
2098 data.scroll_position = (
2099 new_anchor.top_row(&snapshot.buffer_snapshot),
2100 new_anchor.offset,
2101 );
2102 });
2103 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2104 editor.inline_blame_popover.take();
2105 }
2106 }
2107 EditorEvent::Edited { .. } => {
2108 if !vim_enabled(cx) {
2109 let (map, selections) = editor.selections.all_adjusted_display(cx);
2110 let pop_state = editor
2111 .change_list
2112 .last()
2113 .map(|previous| {
2114 previous.len() == selections.len()
2115 && previous.iter().enumerate().all(|(ix, p)| {
2116 p.to_display_point(&map).row()
2117 == selections[ix].head().row()
2118 })
2119 })
2120 .unwrap_or(false);
2121 let new_positions = selections
2122 .into_iter()
2123 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2124 .collect();
2125 editor
2126 .change_list
2127 .push_to_change_list(pop_state, new_positions);
2128 }
2129 }
2130 _ => (),
2131 },
2132 ));
2133
2134 if let Some(dap_store) = editor
2135 .project
2136 .as_ref()
2137 .map(|project| project.read(cx).dap_store())
2138 {
2139 let weak_editor = cx.weak_entity();
2140
2141 editor
2142 ._subscriptions
2143 .push(
2144 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2145 let session_entity = cx.entity();
2146 weak_editor
2147 .update(cx, |editor, cx| {
2148 editor._subscriptions.push(
2149 cx.subscribe(&session_entity, Self::on_debug_session_event),
2150 );
2151 })
2152 .ok();
2153 }),
2154 );
2155
2156 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2157 editor
2158 ._subscriptions
2159 .push(cx.subscribe(&session, Self::on_debug_session_event));
2160 }
2161 }
2162
2163 // skip adding the initial selection to selection history
2164 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2165 editor.end_selection(window, cx);
2166 editor.selection_history.mode = SelectionHistoryMode::Normal;
2167
2168 editor.scroll_manager.show_scrollbars(window, cx);
2169 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2170
2171 if full_mode {
2172 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2173 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2174
2175 if editor.git_blame_inline_enabled {
2176 editor.start_git_blame_inline(false, window, cx);
2177 }
2178
2179 editor.go_to_active_debug_line(window, cx);
2180
2181 if let Some(buffer) = buffer.read(cx).as_singleton() {
2182 if let Some(project) = editor.project.as_ref() {
2183 let handle = project.update(cx, |project, cx| {
2184 project.register_buffer_with_language_servers(&buffer, cx)
2185 });
2186 editor
2187 .registered_buffers
2188 .insert(buffer.read(cx).remote_id(), handle);
2189 }
2190 }
2191
2192 editor.minimap =
2193 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2194 editor.pull_diagnostics(None, window, cx);
2195 }
2196
2197 editor.report_editor_event("Editor Opened", None, cx);
2198 editor
2199 }
2200
2201 pub fn deploy_mouse_context_menu(
2202 &mut self,
2203 position: gpui::Point<Pixels>,
2204 context_menu: Entity<ContextMenu>,
2205 window: &mut Window,
2206 cx: &mut Context<Self>,
2207 ) {
2208 self.mouse_context_menu = Some(MouseContextMenu::new(
2209 self,
2210 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2211 context_menu,
2212 window,
2213 cx,
2214 ));
2215 }
2216
2217 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2218 self.mouse_context_menu
2219 .as_ref()
2220 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2221 }
2222
2223 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2224 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2225 }
2226
2227 fn key_context_internal(
2228 &self,
2229 has_active_edit_prediction: bool,
2230 window: &Window,
2231 cx: &App,
2232 ) -> KeyContext {
2233 let mut key_context = KeyContext::new_with_defaults();
2234 key_context.add("Editor");
2235 let mode = match self.mode {
2236 EditorMode::SingleLine { .. } => "single_line",
2237 EditorMode::AutoHeight { .. } => "auto_height",
2238 EditorMode::Minimap { .. } => "minimap",
2239 EditorMode::Full { .. } => "full",
2240 };
2241
2242 if EditorSettings::jupyter_enabled(cx) {
2243 key_context.add("jupyter");
2244 }
2245
2246 key_context.set("mode", mode);
2247 if self.pending_rename.is_some() {
2248 key_context.add("renaming");
2249 }
2250
2251 match self.context_menu.borrow().as_ref() {
2252 Some(CodeContextMenu::Completions(_)) => {
2253 key_context.add("menu");
2254 key_context.add("showing_completions");
2255 }
2256 Some(CodeContextMenu::CodeActions(_)) => {
2257 key_context.add("menu");
2258 key_context.add("showing_code_actions")
2259 }
2260 None => {}
2261 }
2262
2263 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2264 if !self.focus_handle(cx).contains_focused(window, cx)
2265 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2266 {
2267 for addon in self.addons.values() {
2268 addon.extend_key_context(&mut key_context, cx)
2269 }
2270 }
2271
2272 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2273 if let Some(extension) = singleton_buffer
2274 .read(cx)
2275 .file()
2276 .and_then(|file| file.path().extension()?.to_str())
2277 {
2278 key_context.set("extension", extension.to_string());
2279 }
2280 } else {
2281 key_context.add("multibuffer");
2282 }
2283
2284 if has_active_edit_prediction {
2285 if self.edit_prediction_in_conflict() {
2286 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2287 } else {
2288 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2289 key_context.add("copilot_suggestion");
2290 }
2291 }
2292
2293 if self.selection_mark_mode {
2294 key_context.add("selection_mode");
2295 }
2296
2297 key_context
2298 }
2299
2300 fn show_mouse_cursor(&mut self) {
2301 self.mouse_cursor_hidden = false;
2302 }
2303
2304 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2305 self.mouse_cursor_hidden = match origin {
2306 HideMouseCursorOrigin::TypingAction => {
2307 matches!(
2308 self.hide_mouse_mode,
2309 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2310 )
2311 }
2312 HideMouseCursorOrigin::MovementAction => {
2313 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2314 }
2315 };
2316 }
2317
2318 pub fn edit_prediction_in_conflict(&self) -> bool {
2319 if !self.show_edit_predictions_in_menu() {
2320 return false;
2321 }
2322
2323 let showing_completions = self
2324 .context_menu
2325 .borrow()
2326 .as_ref()
2327 .map_or(false, |context| {
2328 matches!(context, CodeContextMenu::Completions(_))
2329 });
2330
2331 showing_completions
2332 || self.edit_prediction_requires_modifier()
2333 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2334 // bindings to insert tab characters.
2335 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2336 }
2337
2338 pub fn accept_edit_prediction_keybind(
2339 &self,
2340 accept_partial: bool,
2341 window: &Window,
2342 cx: &App,
2343 ) -> AcceptEditPredictionBinding {
2344 let key_context = self.key_context_internal(true, window, cx);
2345 let in_conflict = self.edit_prediction_in_conflict();
2346
2347 let bindings = if accept_partial {
2348 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2349 } else {
2350 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2351 };
2352
2353 // TODO: if the binding contains multiple keystrokes, display all of them, not
2354 // just the first one.
2355 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2356 !in_conflict
2357 || binding
2358 .keystrokes()
2359 .first()
2360 .map_or(false, |keystroke| keystroke.modifiers.modified())
2361 }))
2362 }
2363
2364 pub fn new_file(
2365 workspace: &mut Workspace,
2366 _: &workspace::NewFile,
2367 window: &mut Window,
2368 cx: &mut Context<Workspace>,
2369 ) {
2370 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2371 "Failed to create buffer",
2372 window,
2373 cx,
2374 |e, _, _| match e.error_code() {
2375 ErrorCode::RemoteUpgradeRequired => Some(format!(
2376 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2377 e.error_tag("required").unwrap_or("the latest version")
2378 )),
2379 _ => None,
2380 },
2381 );
2382 }
2383
2384 pub fn new_in_workspace(
2385 workspace: &mut Workspace,
2386 window: &mut Window,
2387 cx: &mut Context<Workspace>,
2388 ) -> Task<Result<Entity<Editor>>> {
2389 let project = workspace.project().clone();
2390 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2391
2392 cx.spawn_in(window, async move |workspace, cx| {
2393 let buffer = create.await?;
2394 workspace.update_in(cx, |workspace, window, cx| {
2395 let editor =
2396 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2397 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2398 editor
2399 })
2400 })
2401 }
2402
2403 fn new_file_vertical(
2404 workspace: &mut Workspace,
2405 _: &workspace::NewFileSplitVertical,
2406 window: &mut Window,
2407 cx: &mut Context<Workspace>,
2408 ) {
2409 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2410 }
2411
2412 fn new_file_horizontal(
2413 workspace: &mut Workspace,
2414 _: &workspace::NewFileSplitHorizontal,
2415 window: &mut Window,
2416 cx: &mut Context<Workspace>,
2417 ) {
2418 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2419 }
2420
2421 fn new_file_in_direction(
2422 workspace: &mut Workspace,
2423 direction: SplitDirection,
2424 window: &mut Window,
2425 cx: &mut Context<Workspace>,
2426 ) {
2427 let project = workspace.project().clone();
2428 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2429
2430 cx.spawn_in(window, async move |workspace, cx| {
2431 let buffer = create.await?;
2432 workspace.update_in(cx, move |workspace, window, cx| {
2433 workspace.split_item(
2434 direction,
2435 Box::new(
2436 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2437 ),
2438 window,
2439 cx,
2440 )
2441 })?;
2442 anyhow::Ok(())
2443 })
2444 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2445 match e.error_code() {
2446 ErrorCode::RemoteUpgradeRequired => Some(format!(
2447 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2448 e.error_tag("required").unwrap_or("the latest version")
2449 )),
2450 _ => None,
2451 }
2452 });
2453 }
2454
2455 pub fn leader_id(&self) -> Option<CollaboratorId> {
2456 self.leader_id
2457 }
2458
2459 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2460 &self.buffer
2461 }
2462
2463 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2464 self.workspace.as_ref()?.0.upgrade()
2465 }
2466
2467 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2468 self.buffer().read(cx).title(cx)
2469 }
2470
2471 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2472 let git_blame_gutter_max_author_length = self
2473 .render_git_blame_gutter(cx)
2474 .then(|| {
2475 if let Some(blame) = self.blame.as_ref() {
2476 let max_author_length =
2477 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2478 Some(max_author_length)
2479 } else {
2480 None
2481 }
2482 })
2483 .flatten();
2484
2485 EditorSnapshot {
2486 mode: self.mode.clone(),
2487 show_gutter: self.show_gutter,
2488 show_line_numbers: self.show_line_numbers,
2489 show_git_diff_gutter: self.show_git_diff_gutter,
2490 show_code_actions: self.show_code_actions,
2491 show_runnables: self.show_runnables,
2492 show_breakpoints: self.show_breakpoints,
2493 git_blame_gutter_max_author_length,
2494 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2495 scroll_anchor: self.scroll_manager.anchor(),
2496 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2497 placeholder_text: self.placeholder_text.clone(),
2498 is_focused: self.focus_handle.is_focused(window),
2499 current_line_highlight: self
2500 .current_line_highlight
2501 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2502 gutter_hovered: self.gutter_hovered,
2503 }
2504 }
2505
2506 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2507 self.buffer.read(cx).language_at(point, cx)
2508 }
2509
2510 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2511 self.buffer.read(cx).read(cx).file_at(point).cloned()
2512 }
2513
2514 pub fn active_excerpt(
2515 &self,
2516 cx: &App,
2517 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2518 self.buffer
2519 .read(cx)
2520 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2521 }
2522
2523 pub fn mode(&self) -> &EditorMode {
2524 &self.mode
2525 }
2526
2527 pub fn set_mode(&mut self, mode: EditorMode) {
2528 self.mode = mode;
2529 }
2530
2531 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2532 self.collaboration_hub.as_deref()
2533 }
2534
2535 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2536 self.collaboration_hub = Some(hub);
2537 }
2538
2539 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2540 self.in_project_search = in_project_search;
2541 }
2542
2543 pub fn set_custom_context_menu(
2544 &mut self,
2545 f: impl 'static
2546 + Fn(
2547 &mut Self,
2548 DisplayPoint,
2549 &mut Window,
2550 &mut Context<Self>,
2551 ) -> Option<Entity<ui::ContextMenu>>,
2552 ) {
2553 self.custom_context_menu = Some(Box::new(f))
2554 }
2555
2556 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2557 self.completion_provider = provider;
2558 }
2559
2560 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2561 self.semantics_provider.clone()
2562 }
2563
2564 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2565 self.semantics_provider = provider;
2566 }
2567
2568 pub fn set_edit_prediction_provider<T>(
2569 &mut self,
2570 provider: Option<Entity<T>>,
2571 window: &mut Window,
2572 cx: &mut Context<Self>,
2573 ) where
2574 T: EditPredictionProvider,
2575 {
2576 self.edit_prediction_provider =
2577 provider.map(|provider| RegisteredInlineCompletionProvider {
2578 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2579 if this.focus_handle.is_focused(window) {
2580 this.update_visible_inline_completion(window, cx);
2581 }
2582 }),
2583 provider: Arc::new(provider),
2584 });
2585 self.update_edit_prediction_settings(cx);
2586 self.refresh_inline_completion(false, false, window, cx);
2587 }
2588
2589 pub fn placeholder_text(&self) -> Option<&str> {
2590 self.placeholder_text.as_deref()
2591 }
2592
2593 pub fn set_placeholder_text(
2594 &mut self,
2595 placeholder_text: impl Into<Arc<str>>,
2596 cx: &mut Context<Self>,
2597 ) {
2598 let placeholder_text = Some(placeholder_text.into());
2599 if self.placeholder_text != placeholder_text {
2600 self.placeholder_text = placeholder_text;
2601 cx.notify();
2602 }
2603 }
2604
2605 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2606 self.cursor_shape = cursor_shape;
2607
2608 // Disrupt blink for immediate user feedback that the cursor shape has changed
2609 self.blink_manager.update(cx, BlinkManager::show_cursor);
2610
2611 cx.notify();
2612 }
2613
2614 pub fn set_current_line_highlight(
2615 &mut self,
2616 current_line_highlight: Option<CurrentLineHighlight>,
2617 ) {
2618 self.current_line_highlight = current_line_highlight;
2619 }
2620
2621 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2622 self.collapse_matches = collapse_matches;
2623 }
2624
2625 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2626 let buffers = self.buffer.read(cx).all_buffers();
2627 let Some(project) = self.project.as_ref() else {
2628 return;
2629 };
2630 project.update(cx, |project, cx| {
2631 for buffer in buffers {
2632 self.registered_buffers
2633 .entry(buffer.read(cx).remote_id())
2634 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2635 }
2636 })
2637 }
2638
2639 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2640 if self.collapse_matches {
2641 return range.start..range.start;
2642 }
2643 range.clone()
2644 }
2645
2646 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2647 if self.display_map.read(cx).clip_at_line_ends != clip {
2648 self.display_map
2649 .update(cx, |map, _| map.clip_at_line_ends = clip);
2650 }
2651 }
2652
2653 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2654 self.input_enabled = input_enabled;
2655 }
2656
2657 pub fn set_inline_completions_hidden_for_vim_mode(
2658 &mut self,
2659 hidden: bool,
2660 window: &mut Window,
2661 cx: &mut Context<Self>,
2662 ) {
2663 if hidden != self.inline_completions_hidden_for_vim_mode {
2664 self.inline_completions_hidden_for_vim_mode = hidden;
2665 if hidden {
2666 self.update_visible_inline_completion(window, cx);
2667 } else {
2668 self.refresh_inline_completion(true, false, window, cx);
2669 }
2670 }
2671 }
2672
2673 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2674 self.menu_inline_completions_policy = value;
2675 }
2676
2677 pub fn set_autoindent(&mut self, autoindent: bool) {
2678 if autoindent {
2679 self.autoindent_mode = Some(AutoindentMode::EachLine);
2680 } else {
2681 self.autoindent_mode = None;
2682 }
2683 }
2684
2685 pub fn read_only(&self, cx: &App) -> bool {
2686 self.read_only || self.buffer.read(cx).read_only()
2687 }
2688
2689 pub fn set_read_only(&mut self, read_only: bool) {
2690 self.read_only = read_only;
2691 }
2692
2693 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2694 self.use_autoclose = autoclose;
2695 }
2696
2697 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2698 self.use_auto_surround = auto_surround;
2699 }
2700
2701 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2702 self.auto_replace_emoji_shortcode = auto_replace;
2703 }
2704
2705 pub fn toggle_edit_predictions(
2706 &mut self,
2707 _: &ToggleEditPrediction,
2708 window: &mut Window,
2709 cx: &mut Context<Self>,
2710 ) {
2711 if self.show_inline_completions_override.is_some() {
2712 self.set_show_edit_predictions(None, window, cx);
2713 } else {
2714 let show_edit_predictions = !self.edit_predictions_enabled();
2715 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2716 }
2717 }
2718
2719 pub fn set_show_edit_predictions(
2720 &mut self,
2721 show_edit_predictions: Option<bool>,
2722 window: &mut Window,
2723 cx: &mut Context<Self>,
2724 ) {
2725 self.show_inline_completions_override = show_edit_predictions;
2726 self.update_edit_prediction_settings(cx);
2727
2728 if let Some(false) = show_edit_predictions {
2729 self.discard_inline_completion(false, cx);
2730 } else {
2731 self.refresh_inline_completion(false, true, window, cx);
2732 }
2733 }
2734
2735 fn inline_completions_disabled_in_scope(
2736 &self,
2737 buffer: &Entity<Buffer>,
2738 buffer_position: language::Anchor,
2739 cx: &App,
2740 ) -> bool {
2741 let snapshot = buffer.read(cx).snapshot();
2742 let settings = snapshot.settings_at(buffer_position, cx);
2743
2744 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2745 return false;
2746 };
2747
2748 scope.override_name().map_or(false, |scope_name| {
2749 settings
2750 .edit_predictions_disabled_in
2751 .iter()
2752 .any(|s| s == scope_name)
2753 })
2754 }
2755
2756 pub fn set_use_modal_editing(&mut self, to: bool) {
2757 self.use_modal_editing = to;
2758 }
2759
2760 pub fn use_modal_editing(&self) -> bool {
2761 self.use_modal_editing
2762 }
2763
2764 fn selections_did_change(
2765 &mut self,
2766 local: bool,
2767 old_cursor_position: &Anchor,
2768 effects: SelectionEffects,
2769 window: &mut Window,
2770 cx: &mut Context<Self>,
2771 ) {
2772 window.invalidate_character_coordinates();
2773
2774 // Copy selections to primary selection buffer
2775 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2776 if local {
2777 let selections = self.selections.all::<usize>(cx);
2778 let buffer_handle = self.buffer.read(cx).read(cx);
2779
2780 let mut text = String::new();
2781 for (index, selection) in selections.iter().enumerate() {
2782 let text_for_selection = buffer_handle
2783 .text_for_range(selection.start..selection.end)
2784 .collect::<String>();
2785
2786 text.push_str(&text_for_selection);
2787 if index != selections.len() - 1 {
2788 text.push('\n');
2789 }
2790 }
2791
2792 if !text.is_empty() {
2793 cx.write_to_primary(ClipboardItem::new_string(text));
2794 }
2795 }
2796
2797 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2798 self.buffer.update(cx, |buffer, cx| {
2799 buffer.set_active_selections(
2800 &self.selections.disjoint_anchors(),
2801 self.selections.line_mode,
2802 self.cursor_shape,
2803 cx,
2804 )
2805 });
2806 }
2807 let display_map = self
2808 .display_map
2809 .update(cx, |display_map, cx| display_map.snapshot(cx));
2810 let buffer = &display_map.buffer_snapshot;
2811 if self.selections.count() == 1 {
2812 self.add_selections_state = None;
2813 }
2814 self.select_next_state = None;
2815 self.select_prev_state = None;
2816 self.select_syntax_node_history.try_clear();
2817 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2818 self.snippet_stack
2819 .invalidate(&self.selections.disjoint_anchors(), buffer);
2820 self.take_rename(false, window, cx);
2821
2822 let newest_selection = self.selections.newest_anchor();
2823 let new_cursor_position = newest_selection.head();
2824 let selection_start = newest_selection.start;
2825
2826 if effects.nav_history {
2827 self.push_to_nav_history(
2828 *old_cursor_position,
2829 Some(new_cursor_position.to_point(buffer)),
2830 false,
2831 cx,
2832 );
2833 }
2834
2835 if local {
2836 if let Some(buffer_id) = new_cursor_position.buffer_id {
2837 if !self.registered_buffers.contains_key(&buffer_id) {
2838 if let Some(project) = self.project.as_ref() {
2839 project.update(cx, |project, cx| {
2840 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2841 return;
2842 };
2843 self.registered_buffers.insert(
2844 buffer_id,
2845 project.register_buffer_with_language_servers(&buffer, cx),
2846 );
2847 })
2848 }
2849 }
2850 }
2851
2852 let mut context_menu = self.context_menu.borrow_mut();
2853 let completion_menu = match context_menu.as_ref() {
2854 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2855 Some(CodeContextMenu::CodeActions(_)) => {
2856 *context_menu = None;
2857 None
2858 }
2859 None => None,
2860 };
2861 let completion_position = completion_menu.map(|menu| menu.initial_position);
2862 drop(context_menu);
2863
2864 if effects.completions {
2865 if let Some(completion_position) = completion_position {
2866 let start_offset = selection_start.to_offset(buffer);
2867 let position_matches = start_offset == completion_position.to_offset(buffer);
2868 let continue_showing = if position_matches {
2869 if self.snippet_stack.is_empty() {
2870 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2871 } else {
2872 // Snippet choices can be shown even when the cursor is in whitespace.
2873 // Dismissing the menu when actions like backspace
2874 true
2875 }
2876 } else {
2877 false
2878 };
2879
2880 if continue_showing {
2881 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2882 } else {
2883 self.hide_context_menu(window, cx);
2884 }
2885 }
2886 }
2887
2888 hide_hover(self, cx);
2889
2890 if old_cursor_position.to_display_point(&display_map).row()
2891 != new_cursor_position.to_display_point(&display_map).row()
2892 {
2893 self.available_code_actions.take();
2894 }
2895 self.refresh_code_actions(window, cx);
2896 self.refresh_document_highlights(cx);
2897 self.refresh_selected_text_highlights(false, window, cx);
2898 refresh_matching_bracket_highlights(self, window, cx);
2899 self.update_visible_inline_completion(window, cx);
2900 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2901 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2902 self.inline_blame_popover.take();
2903 if self.git_blame_inline_enabled {
2904 self.start_inline_blame_timer(window, cx);
2905 }
2906 }
2907
2908 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2909 cx.emit(EditorEvent::SelectionsChanged { local });
2910
2911 let selections = &self.selections.disjoint;
2912 if selections.len() == 1 {
2913 cx.emit(SearchEvent::ActiveMatchChanged)
2914 }
2915 if local {
2916 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2917 let inmemory_selections = selections
2918 .iter()
2919 .map(|s| {
2920 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2921 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2922 })
2923 .collect();
2924 self.update_restoration_data(cx, |data| {
2925 data.selections = inmemory_selections;
2926 });
2927
2928 if WorkspaceSettings::get(None, cx).restore_on_startup
2929 != RestoreOnStartupBehavior::None
2930 {
2931 if let Some(workspace_id) =
2932 self.workspace.as_ref().and_then(|workspace| workspace.1)
2933 {
2934 let snapshot = self.buffer().read(cx).snapshot(cx);
2935 let selections = selections.clone();
2936 let background_executor = cx.background_executor().clone();
2937 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2938 self.serialize_selections = cx.background_spawn(async move {
2939 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2940 let db_selections = selections
2941 .iter()
2942 .map(|selection| {
2943 (
2944 selection.start.to_offset(&snapshot),
2945 selection.end.to_offset(&snapshot),
2946 )
2947 })
2948 .collect();
2949
2950 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2951 .await
2952 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2953 .log_err();
2954 });
2955 }
2956 }
2957 }
2958 }
2959
2960 cx.notify();
2961 }
2962
2963 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2964 use text::ToOffset as _;
2965 use text::ToPoint as _;
2966
2967 if self.mode.is_minimap()
2968 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2969 {
2970 return;
2971 }
2972
2973 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2974 return;
2975 };
2976
2977 let snapshot = singleton.read(cx).snapshot();
2978 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2979 let display_snapshot = display_map.snapshot(cx);
2980
2981 display_snapshot
2982 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2983 .map(|fold| {
2984 fold.range.start.text_anchor.to_point(&snapshot)
2985 ..fold.range.end.text_anchor.to_point(&snapshot)
2986 })
2987 .collect()
2988 });
2989 self.update_restoration_data(cx, |data| {
2990 data.folds = inmemory_folds;
2991 });
2992
2993 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2994 return;
2995 };
2996 let background_executor = cx.background_executor().clone();
2997 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2998 let db_folds = self.display_map.update(cx, |display_map, cx| {
2999 display_map
3000 .snapshot(cx)
3001 .folds_in_range(0..snapshot.len())
3002 .map(|fold| {
3003 (
3004 fold.range.start.text_anchor.to_offset(&snapshot),
3005 fold.range.end.text_anchor.to_offset(&snapshot),
3006 )
3007 })
3008 .collect()
3009 });
3010 self.serialize_folds = cx.background_spawn(async move {
3011 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3012 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3013 .await
3014 .with_context(|| {
3015 format!(
3016 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3017 )
3018 })
3019 .log_err();
3020 });
3021 }
3022
3023 pub fn sync_selections(
3024 &mut self,
3025 other: Entity<Editor>,
3026 cx: &mut Context<Self>,
3027 ) -> gpui::Subscription {
3028 let other_selections = other.read(cx).selections.disjoint.to_vec();
3029 self.selections.change_with(cx, |selections| {
3030 selections.select_anchors(other_selections);
3031 });
3032
3033 let other_subscription =
3034 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3035 EditorEvent::SelectionsChanged { local: true } => {
3036 let other_selections = other.read(cx).selections.disjoint.to_vec();
3037 if other_selections.is_empty() {
3038 return;
3039 }
3040 this.selections.change_with(cx, |selections| {
3041 selections.select_anchors(other_selections);
3042 });
3043 }
3044 _ => {}
3045 });
3046
3047 let this_subscription =
3048 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3049 EditorEvent::SelectionsChanged { local: true } => {
3050 let these_selections = this.selections.disjoint.to_vec();
3051 if these_selections.is_empty() {
3052 return;
3053 }
3054 other.update(cx, |other_editor, cx| {
3055 other_editor.selections.change_with(cx, |selections| {
3056 selections.select_anchors(these_selections);
3057 })
3058 });
3059 }
3060 _ => {}
3061 });
3062
3063 Subscription::join(other_subscription, this_subscription)
3064 }
3065
3066 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3067 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3068 /// effects of selection change occur at the end of the transaction.
3069 pub fn change_selections<R>(
3070 &mut self,
3071 effects: impl Into<SelectionEffects>,
3072 window: &mut Window,
3073 cx: &mut Context<Self>,
3074 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3075 ) -> R {
3076 let effects = effects.into();
3077 if let Some(state) = &mut self.deferred_selection_effects_state {
3078 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3079 state.effects.completions = effects.completions;
3080 state.effects.nav_history |= effects.nav_history;
3081 let (changed, result) = self.selections.change_with(cx, change);
3082 state.changed |= changed;
3083 return result;
3084 }
3085 let mut state = DeferredSelectionEffectsState {
3086 changed: false,
3087 effects,
3088 old_cursor_position: self.selections.newest_anchor().head(),
3089 history_entry: SelectionHistoryEntry {
3090 selections: self.selections.disjoint_anchors(),
3091 select_next_state: self.select_next_state.clone(),
3092 select_prev_state: self.select_prev_state.clone(),
3093 add_selections_state: self.add_selections_state.clone(),
3094 },
3095 };
3096 let (changed, result) = self.selections.change_with(cx, change);
3097 state.changed = state.changed || changed;
3098 if self.defer_selection_effects {
3099 self.deferred_selection_effects_state = Some(state);
3100 } else {
3101 self.apply_selection_effects(state, window, cx);
3102 }
3103 result
3104 }
3105
3106 /// Defers the effects of selection change, so that the effects of multiple calls to
3107 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3108 /// to selection history and the state of popovers based on selection position aren't
3109 /// erroneously updated.
3110 pub fn with_selection_effects_deferred<R>(
3111 &mut self,
3112 window: &mut Window,
3113 cx: &mut Context<Self>,
3114 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3115 ) -> R {
3116 let already_deferred = self.defer_selection_effects;
3117 self.defer_selection_effects = true;
3118 let result = update(self, window, cx);
3119 if !already_deferred {
3120 self.defer_selection_effects = false;
3121 if let Some(state) = self.deferred_selection_effects_state.take() {
3122 self.apply_selection_effects(state, window, cx);
3123 }
3124 }
3125 result
3126 }
3127
3128 fn apply_selection_effects(
3129 &mut self,
3130 state: DeferredSelectionEffectsState,
3131 window: &mut Window,
3132 cx: &mut Context<Self>,
3133 ) {
3134 if state.changed {
3135 self.selection_history.push(state.history_entry);
3136
3137 if let Some(autoscroll) = state.effects.scroll {
3138 self.request_autoscroll(autoscroll, cx);
3139 }
3140
3141 let old_cursor_position = &state.old_cursor_position;
3142
3143 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3144
3145 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3146 self.show_signature_help(&ShowSignatureHelp, window, cx);
3147 }
3148 }
3149 }
3150
3151 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3152 where
3153 I: IntoIterator<Item = (Range<S>, T)>,
3154 S: ToOffset,
3155 T: Into<Arc<str>>,
3156 {
3157 if self.read_only(cx) {
3158 return;
3159 }
3160
3161 self.buffer
3162 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3163 }
3164
3165 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3166 where
3167 I: IntoIterator<Item = (Range<S>, T)>,
3168 S: ToOffset,
3169 T: Into<Arc<str>>,
3170 {
3171 if self.read_only(cx) {
3172 return;
3173 }
3174
3175 self.buffer.update(cx, |buffer, cx| {
3176 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3177 });
3178 }
3179
3180 pub fn edit_with_block_indent<I, S, T>(
3181 &mut self,
3182 edits: I,
3183 original_indent_columns: Vec<Option<u32>>,
3184 cx: &mut Context<Self>,
3185 ) where
3186 I: IntoIterator<Item = (Range<S>, T)>,
3187 S: ToOffset,
3188 T: Into<Arc<str>>,
3189 {
3190 if self.read_only(cx) {
3191 return;
3192 }
3193
3194 self.buffer.update(cx, |buffer, cx| {
3195 buffer.edit(
3196 edits,
3197 Some(AutoindentMode::Block {
3198 original_indent_columns,
3199 }),
3200 cx,
3201 )
3202 });
3203 }
3204
3205 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3206 self.hide_context_menu(window, cx);
3207
3208 match phase {
3209 SelectPhase::Begin {
3210 position,
3211 add,
3212 click_count,
3213 } => self.begin_selection(position, add, click_count, window, cx),
3214 SelectPhase::BeginColumnar {
3215 position,
3216 goal_column,
3217 reset,
3218 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3219 SelectPhase::Extend {
3220 position,
3221 click_count,
3222 } => self.extend_selection(position, click_count, window, cx),
3223 SelectPhase::Update {
3224 position,
3225 goal_column,
3226 scroll_delta,
3227 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3228 SelectPhase::End => self.end_selection(window, cx),
3229 }
3230 }
3231
3232 fn extend_selection(
3233 &mut self,
3234 position: DisplayPoint,
3235 click_count: usize,
3236 window: &mut Window,
3237 cx: &mut Context<Self>,
3238 ) {
3239 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3240 let tail = self.selections.newest::<usize>(cx).tail();
3241 self.begin_selection(position, false, click_count, window, cx);
3242
3243 let position = position.to_offset(&display_map, Bias::Left);
3244 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3245
3246 let mut pending_selection = self
3247 .selections
3248 .pending_anchor()
3249 .expect("extend_selection not called with pending selection");
3250 if position >= tail {
3251 pending_selection.start = tail_anchor;
3252 } else {
3253 pending_selection.end = tail_anchor;
3254 pending_selection.reversed = true;
3255 }
3256
3257 let mut pending_mode = self.selections.pending_mode().unwrap();
3258 match &mut pending_mode {
3259 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3260 _ => {}
3261 }
3262
3263 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3264 SelectionEffects::scroll(Autoscroll::fit())
3265 } else {
3266 SelectionEffects::no_scroll()
3267 };
3268
3269 self.change_selections(effects, window, cx, |s| {
3270 s.set_pending(pending_selection, pending_mode)
3271 });
3272 }
3273
3274 fn begin_selection(
3275 &mut self,
3276 position: DisplayPoint,
3277 add: bool,
3278 click_count: usize,
3279 window: &mut Window,
3280 cx: &mut Context<Self>,
3281 ) {
3282 if !self.focus_handle.is_focused(window) {
3283 self.last_focused_descendant = None;
3284 window.focus(&self.focus_handle);
3285 }
3286
3287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3288 let buffer = &display_map.buffer_snapshot;
3289 let position = display_map.clip_point(position, Bias::Left);
3290
3291 let start;
3292 let end;
3293 let mode;
3294 let mut auto_scroll;
3295 match click_count {
3296 1 => {
3297 start = buffer.anchor_before(position.to_point(&display_map));
3298 end = start;
3299 mode = SelectMode::Character;
3300 auto_scroll = true;
3301 }
3302 2 => {
3303 let range = movement::surrounding_word(&display_map, position);
3304 start = buffer.anchor_before(range.start.to_point(&display_map));
3305 end = buffer.anchor_before(range.end.to_point(&display_map));
3306 mode = SelectMode::Word(start..end);
3307 auto_scroll = true;
3308 }
3309 3 => {
3310 let position = display_map
3311 .clip_point(position, Bias::Left)
3312 .to_point(&display_map);
3313 let line_start = display_map.prev_line_boundary(position).0;
3314 let next_line_start = buffer.clip_point(
3315 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3316 Bias::Left,
3317 );
3318 start = buffer.anchor_before(line_start);
3319 end = buffer.anchor_before(next_line_start);
3320 mode = SelectMode::Line(start..end);
3321 auto_scroll = true;
3322 }
3323 _ => {
3324 start = buffer.anchor_before(0);
3325 end = buffer.anchor_before(buffer.len());
3326 mode = SelectMode::All;
3327 auto_scroll = false;
3328 }
3329 }
3330 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3331
3332 let point_to_delete: Option<usize> = {
3333 let selected_points: Vec<Selection<Point>> =
3334 self.selections.disjoint_in_range(start..end, cx);
3335
3336 if !add || click_count > 1 {
3337 None
3338 } else if !selected_points.is_empty() {
3339 Some(selected_points[0].id)
3340 } else {
3341 let clicked_point_already_selected =
3342 self.selections.disjoint.iter().find(|selection| {
3343 selection.start.to_point(buffer) == start.to_point(buffer)
3344 || selection.end.to_point(buffer) == end.to_point(buffer)
3345 });
3346
3347 clicked_point_already_selected.map(|selection| selection.id)
3348 }
3349 };
3350
3351 let selections_count = self.selections.count();
3352
3353 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3354 if let Some(point_to_delete) = point_to_delete {
3355 s.delete(point_to_delete);
3356
3357 if selections_count == 1 {
3358 s.set_pending_anchor_range(start..end, mode);
3359 }
3360 } else {
3361 if !add {
3362 s.clear_disjoint();
3363 }
3364
3365 s.set_pending_anchor_range(start..end, mode);
3366 }
3367 });
3368 }
3369
3370 fn begin_columnar_selection(
3371 &mut self,
3372 position: DisplayPoint,
3373 goal_column: u32,
3374 reset: bool,
3375 window: &mut Window,
3376 cx: &mut Context<Self>,
3377 ) {
3378 if !self.focus_handle.is_focused(window) {
3379 self.last_focused_descendant = None;
3380 window.focus(&self.focus_handle);
3381 }
3382
3383 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3384
3385 if reset {
3386 let pointer_position = display_map
3387 .buffer_snapshot
3388 .anchor_before(position.to_point(&display_map));
3389
3390 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3391 s.clear_disjoint();
3392 s.set_pending_anchor_range(
3393 pointer_position..pointer_position,
3394 SelectMode::Character,
3395 );
3396 });
3397 if position.column() != goal_column {
3398 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3399 } else {
3400 self.columnar_display_point = None;
3401 }
3402 }
3403
3404 let tail = self.selections.newest::<Point>(cx).tail();
3405 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3406
3407 if !reset {
3408 self.columnar_display_point = None;
3409 self.select_columns(
3410 tail.to_display_point(&display_map),
3411 position,
3412 goal_column,
3413 &display_map,
3414 window,
3415 cx,
3416 );
3417 }
3418 }
3419
3420 fn update_selection(
3421 &mut self,
3422 position: DisplayPoint,
3423 goal_column: u32,
3424 scroll_delta: gpui::Point<f32>,
3425 window: &mut Window,
3426 cx: &mut Context<Self>,
3427 ) {
3428 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3429
3430 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3431 let tail = self
3432 .columnar_display_point
3433 .unwrap_or_else(|| tail.to_display_point(&display_map));
3434 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3435 } else if let Some(mut pending) = self.selections.pending_anchor() {
3436 let buffer = self.buffer.read(cx).snapshot(cx);
3437 let head;
3438 let tail;
3439 let mode = self.selections.pending_mode().unwrap();
3440 match &mode {
3441 SelectMode::Character => {
3442 head = position.to_point(&display_map);
3443 tail = pending.tail().to_point(&buffer);
3444 }
3445 SelectMode::Word(original_range) => {
3446 let original_display_range = original_range.start.to_display_point(&display_map)
3447 ..original_range.end.to_display_point(&display_map);
3448 let original_buffer_range = original_display_range.start.to_point(&display_map)
3449 ..original_display_range.end.to_point(&display_map);
3450 if movement::is_inside_word(&display_map, position)
3451 || original_display_range.contains(&position)
3452 {
3453 let word_range = movement::surrounding_word(&display_map, position);
3454 if word_range.start < original_display_range.start {
3455 head = word_range.start.to_point(&display_map);
3456 } else {
3457 head = word_range.end.to_point(&display_map);
3458 }
3459 } else {
3460 head = position.to_point(&display_map);
3461 }
3462
3463 if head <= original_buffer_range.start {
3464 tail = original_buffer_range.end;
3465 } else {
3466 tail = original_buffer_range.start;
3467 }
3468 }
3469 SelectMode::Line(original_range) => {
3470 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3471
3472 let position = display_map
3473 .clip_point(position, Bias::Left)
3474 .to_point(&display_map);
3475 let line_start = display_map.prev_line_boundary(position).0;
3476 let next_line_start = buffer.clip_point(
3477 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3478 Bias::Left,
3479 );
3480
3481 if line_start < original_range.start {
3482 head = line_start
3483 } else {
3484 head = next_line_start
3485 }
3486
3487 if head <= original_range.start {
3488 tail = original_range.end;
3489 } else {
3490 tail = original_range.start;
3491 }
3492 }
3493 SelectMode::All => {
3494 return;
3495 }
3496 };
3497
3498 if head < tail {
3499 pending.start = buffer.anchor_before(head);
3500 pending.end = buffer.anchor_before(tail);
3501 pending.reversed = true;
3502 } else {
3503 pending.start = buffer.anchor_before(tail);
3504 pending.end = buffer.anchor_before(head);
3505 pending.reversed = false;
3506 }
3507
3508 self.change_selections(None, window, cx, |s| {
3509 s.set_pending(pending, mode);
3510 });
3511 } else {
3512 log::error!("update_selection dispatched with no pending selection");
3513 return;
3514 }
3515
3516 self.apply_scroll_delta(scroll_delta, window, cx);
3517 cx.notify();
3518 }
3519
3520 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3521 self.columnar_selection_tail.take();
3522 if self.selections.pending_anchor().is_some() {
3523 let selections = self.selections.all::<usize>(cx);
3524 self.change_selections(None, window, cx, |s| {
3525 s.select(selections);
3526 s.clear_pending();
3527 });
3528 }
3529 }
3530
3531 fn select_columns(
3532 &mut self,
3533 tail: DisplayPoint,
3534 head: DisplayPoint,
3535 goal_column: u32,
3536 display_map: &DisplaySnapshot,
3537 window: &mut Window,
3538 cx: &mut Context<Self>,
3539 ) {
3540 let start_row = cmp::min(tail.row(), head.row());
3541 let end_row = cmp::max(tail.row(), head.row());
3542 let start_column = cmp::min(tail.column(), goal_column);
3543 let end_column = cmp::max(tail.column(), goal_column);
3544 let reversed = start_column < tail.column();
3545
3546 let selection_ranges = (start_row.0..=end_row.0)
3547 .map(DisplayRow)
3548 .filter_map(|row| {
3549 if !display_map.is_block_line(row) {
3550 let start = display_map
3551 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3552 .to_point(display_map);
3553 let end = display_map
3554 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3555 .to_point(display_map);
3556 if reversed {
3557 Some(end..start)
3558 } else {
3559 Some(start..end)
3560 }
3561 } else {
3562 None
3563 }
3564 })
3565 .collect::<Vec<_>>();
3566
3567 let mut non_empty_ranges = selection_ranges
3568 .iter()
3569 .filter(|selection_range| selection_range.start != selection_range.end)
3570 .peekable();
3571
3572 let ranges = if non_empty_ranges.peek().is_some() {
3573 non_empty_ranges.cloned().collect()
3574 } else {
3575 selection_ranges
3576 };
3577
3578 self.change_selections(None, window, cx, |s| {
3579 s.select_ranges(ranges);
3580 });
3581 cx.notify();
3582 }
3583
3584 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3585 self.selections
3586 .all_adjusted(cx)
3587 .iter()
3588 .any(|selection| !selection.is_empty())
3589 }
3590
3591 pub fn has_pending_nonempty_selection(&self) -> bool {
3592 let pending_nonempty_selection = match self.selections.pending_anchor() {
3593 Some(Selection { start, end, .. }) => start != end,
3594 None => false,
3595 };
3596
3597 pending_nonempty_selection
3598 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3599 }
3600
3601 pub fn has_pending_selection(&self) -> bool {
3602 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3603 }
3604
3605 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3606 self.selection_mark_mode = false;
3607 self.selection_drag_state = SelectionDragState::None;
3608
3609 if self.clear_expanded_diff_hunks(cx) {
3610 cx.notify();
3611 return;
3612 }
3613 if self.dismiss_menus_and_popups(true, window, cx) {
3614 return;
3615 }
3616
3617 if self.mode.is_full()
3618 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3619 {
3620 return;
3621 }
3622
3623 cx.propagate();
3624 }
3625
3626 pub fn dismiss_menus_and_popups(
3627 &mut self,
3628 is_user_requested: bool,
3629 window: &mut Window,
3630 cx: &mut Context<Self>,
3631 ) -> bool {
3632 if self.take_rename(false, window, cx).is_some() {
3633 return true;
3634 }
3635
3636 if hide_hover(self, cx) {
3637 return true;
3638 }
3639
3640 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3641 return true;
3642 }
3643
3644 if self.hide_context_menu(window, cx).is_some() {
3645 return true;
3646 }
3647
3648 if self.mouse_context_menu.take().is_some() {
3649 return true;
3650 }
3651
3652 if is_user_requested && self.discard_inline_completion(true, cx) {
3653 return true;
3654 }
3655
3656 if self.snippet_stack.pop().is_some() {
3657 return true;
3658 }
3659
3660 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3661 self.dismiss_diagnostics(cx);
3662 return true;
3663 }
3664
3665 false
3666 }
3667
3668 fn linked_editing_ranges_for(
3669 &self,
3670 selection: Range<text::Anchor>,
3671 cx: &App,
3672 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3673 if self.linked_edit_ranges.is_empty() {
3674 return None;
3675 }
3676 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3677 selection.end.buffer_id.and_then(|end_buffer_id| {
3678 if selection.start.buffer_id != Some(end_buffer_id) {
3679 return None;
3680 }
3681 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3682 let snapshot = buffer.read(cx).snapshot();
3683 self.linked_edit_ranges
3684 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3685 .map(|ranges| (ranges, snapshot, buffer))
3686 })?;
3687 use text::ToOffset as TO;
3688 // find offset from the start of current range to current cursor position
3689 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3690
3691 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3692 let start_difference = start_offset - start_byte_offset;
3693 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3694 let end_difference = end_offset - start_byte_offset;
3695 // Current range has associated linked ranges.
3696 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3697 for range in linked_ranges.iter() {
3698 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3699 let end_offset = start_offset + end_difference;
3700 let start_offset = start_offset + start_difference;
3701 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3702 continue;
3703 }
3704 if self.selections.disjoint_anchor_ranges().any(|s| {
3705 if s.start.buffer_id != selection.start.buffer_id
3706 || s.end.buffer_id != selection.end.buffer_id
3707 {
3708 return false;
3709 }
3710 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3711 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3712 }) {
3713 continue;
3714 }
3715 let start = buffer_snapshot.anchor_after(start_offset);
3716 let end = buffer_snapshot.anchor_after(end_offset);
3717 linked_edits
3718 .entry(buffer.clone())
3719 .or_default()
3720 .push(start..end);
3721 }
3722 Some(linked_edits)
3723 }
3724
3725 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3726 let text: Arc<str> = text.into();
3727
3728 if self.read_only(cx) {
3729 return;
3730 }
3731
3732 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3733
3734 let selections = self.selections.all_adjusted(cx);
3735 let mut bracket_inserted = false;
3736 let mut edits = Vec::new();
3737 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3738 let mut new_selections = Vec::with_capacity(selections.len());
3739 let mut new_autoclose_regions = Vec::new();
3740 let snapshot = self.buffer.read(cx).read(cx);
3741 let mut clear_linked_edit_ranges = false;
3742
3743 for (selection, autoclose_region) in
3744 self.selections_with_autoclose_regions(selections, &snapshot)
3745 {
3746 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3747 // Determine if the inserted text matches the opening or closing
3748 // bracket of any of this language's bracket pairs.
3749 let mut bracket_pair = None;
3750 let mut is_bracket_pair_start = false;
3751 let mut is_bracket_pair_end = false;
3752 if !text.is_empty() {
3753 let mut bracket_pair_matching_end = None;
3754 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3755 // and they are removing the character that triggered IME popup.
3756 for (pair, enabled) in scope.brackets() {
3757 if !pair.close && !pair.surround {
3758 continue;
3759 }
3760
3761 if enabled && pair.start.ends_with(text.as_ref()) {
3762 let prefix_len = pair.start.len() - text.len();
3763 let preceding_text_matches_prefix = prefix_len == 0
3764 || (selection.start.column >= (prefix_len as u32)
3765 && snapshot.contains_str_at(
3766 Point::new(
3767 selection.start.row,
3768 selection.start.column - (prefix_len as u32),
3769 ),
3770 &pair.start[..prefix_len],
3771 ));
3772 if preceding_text_matches_prefix {
3773 bracket_pair = Some(pair.clone());
3774 is_bracket_pair_start = true;
3775 break;
3776 }
3777 }
3778 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3779 {
3780 // take first bracket pair matching end, but don't break in case a later bracket
3781 // pair matches start
3782 bracket_pair_matching_end = Some(pair.clone());
3783 }
3784 }
3785 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3786 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3787 is_bracket_pair_end = true;
3788 }
3789 }
3790
3791 if let Some(bracket_pair) = bracket_pair {
3792 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3793 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3794 let auto_surround =
3795 self.use_auto_surround && snapshot_settings.use_auto_surround;
3796 if selection.is_empty() {
3797 if is_bracket_pair_start {
3798 // If the inserted text is a suffix of an opening bracket and the
3799 // selection is preceded by the rest of the opening bracket, then
3800 // insert the closing bracket.
3801 let following_text_allows_autoclose = snapshot
3802 .chars_at(selection.start)
3803 .next()
3804 .map_or(true, |c| scope.should_autoclose_before(c));
3805
3806 let preceding_text_allows_autoclose = selection.start.column == 0
3807 || snapshot.reversed_chars_at(selection.start).next().map_or(
3808 true,
3809 |c| {
3810 bracket_pair.start != bracket_pair.end
3811 || !snapshot
3812 .char_classifier_at(selection.start)
3813 .is_word(c)
3814 },
3815 );
3816
3817 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3818 && bracket_pair.start.len() == 1
3819 {
3820 let target = bracket_pair.start.chars().next().unwrap();
3821 let current_line_count = snapshot
3822 .reversed_chars_at(selection.start)
3823 .take_while(|&c| c != '\n')
3824 .filter(|&c| c == target)
3825 .count();
3826 current_line_count % 2 == 1
3827 } else {
3828 false
3829 };
3830
3831 if autoclose
3832 && bracket_pair.close
3833 && following_text_allows_autoclose
3834 && preceding_text_allows_autoclose
3835 && !is_closing_quote
3836 {
3837 let anchor = snapshot.anchor_before(selection.end);
3838 new_selections.push((selection.map(|_| anchor), text.len()));
3839 new_autoclose_regions.push((
3840 anchor,
3841 text.len(),
3842 selection.id,
3843 bracket_pair.clone(),
3844 ));
3845 edits.push((
3846 selection.range(),
3847 format!("{}{}", text, bracket_pair.end).into(),
3848 ));
3849 bracket_inserted = true;
3850 continue;
3851 }
3852 }
3853
3854 if let Some(region) = autoclose_region {
3855 // If the selection is followed by an auto-inserted closing bracket,
3856 // then don't insert that closing bracket again; just move the selection
3857 // past the closing bracket.
3858 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3859 && text.as_ref() == region.pair.end.as_str();
3860 if should_skip {
3861 let anchor = snapshot.anchor_after(selection.end);
3862 new_selections
3863 .push((selection.map(|_| anchor), region.pair.end.len()));
3864 continue;
3865 }
3866 }
3867
3868 let always_treat_brackets_as_autoclosed = snapshot
3869 .language_settings_at(selection.start, cx)
3870 .always_treat_brackets_as_autoclosed;
3871 if always_treat_brackets_as_autoclosed
3872 && is_bracket_pair_end
3873 && snapshot.contains_str_at(selection.end, text.as_ref())
3874 {
3875 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3876 // and the inserted text is a closing bracket and the selection is followed
3877 // by the closing bracket then move the selection past the closing bracket.
3878 let anchor = snapshot.anchor_after(selection.end);
3879 new_selections.push((selection.map(|_| anchor), text.len()));
3880 continue;
3881 }
3882 }
3883 // If an opening bracket is 1 character long and is typed while
3884 // text is selected, then surround that text with the bracket pair.
3885 else if auto_surround
3886 && bracket_pair.surround
3887 && is_bracket_pair_start
3888 && bracket_pair.start.chars().count() == 1
3889 {
3890 edits.push((selection.start..selection.start, text.clone()));
3891 edits.push((
3892 selection.end..selection.end,
3893 bracket_pair.end.as_str().into(),
3894 ));
3895 bracket_inserted = true;
3896 new_selections.push((
3897 Selection {
3898 id: selection.id,
3899 start: snapshot.anchor_after(selection.start),
3900 end: snapshot.anchor_before(selection.end),
3901 reversed: selection.reversed,
3902 goal: selection.goal,
3903 },
3904 0,
3905 ));
3906 continue;
3907 }
3908 }
3909 }
3910
3911 if self.auto_replace_emoji_shortcode
3912 && selection.is_empty()
3913 && text.as_ref().ends_with(':')
3914 {
3915 if let Some(possible_emoji_short_code) =
3916 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3917 {
3918 if !possible_emoji_short_code.is_empty() {
3919 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3920 let emoji_shortcode_start = Point::new(
3921 selection.start.row,
3922 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3923 );
3924
3925 // Remove shortcode from buffer
3926 edits.push((
3927 emoji_shortcode_start..selection.start,
3928 "".to_string().into(),
3929 ));
3930 new_selections.push((
3931 Selection {
3932 id: selection.id,
3933 start: snapshot.anchor_after(emoji_shortcode_start),
3934 end: snapshot.anchor_before(selection.start),
3935 reversed: selection.reversed,
3936 goal: selection.goal,
3937 },
3938 0,
3939 ));
3940
3941 // Insert emoji
3942 let selection_start_anchor = snapshot.anchor_after(selection.start);
3943 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3944 edits.push((selection.start..selection.end, emoji.to_string().into()));
3945
3946 continue;
3947 }
3948 }
3949 }
3950 }
3951
3952 // If not handling any auto-close operation, then just replace the selected
3953 // text with the given input and move the selection to the end of the
3954 // newly inserted text.
3955 let anchor = snapshot.anchor_after(selection.end);
3956 if !self.linked_edit_ranges.is_empty() {
3957 let start_anchor = snapshot.anchor_before(selection.start);
3958
3959 let is_word_char = text.chars().next().map_or(true, |char| {
3960 let classifier = snapshot
3961 .char_classifier_at(start_anchor.to_offset(&snapshot))
3962 .ignore_punctuation(true);
3963 classifier.is_word(char)
3964 });
3965
3966 if is_word_char {
3967 if let Some(ranges) = self
3968 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3969 {
3970 for (buffer, edits) in ranges {
3971 linked_edits
3972 .entry(buffer.clone())
3973 .or_default()
3974 .extend(edits.into_iter().map(|range| (range, text.clone())));
3975 }
3976 }
3977 } else {
3978 clear_linked_edit_ranges = true;
3979 }
3980 }
3981
3982 new_selections.push((selection.map(|_| anchor), 0));
3983 edits.push((selection.start..selection.end, text.clone()));
3984 }
3985
3986 drop(snapshot);
3987
3988 self.transact(window, cx, |this, window, cx| {
3989 if clear_linked_edit_ranges {
3990 this.linked_edit_ranges.clear();
3991 }
3992 let initial_buffer_versions =
3993 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3994
3995 this.buffer.update(cx, |buffer, cx| {
3996 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3997 });
3998 for (buffer, edits) in linked_edits {
3999 buffer.update(cx, |buffer, cx| {
4000 let snapshot = buffer.snapshot();
4001 let edits = edits
4002 .into_iter()
4003 .map(|(range, text)| {
4004 use text::ToPoint as TP;
4005 let end_point = TP::to_point(&range.end, &snapshot);
4006 let start_point = TP::to_point(&range.start, &snapshot);
4007 (start_point..end_point, text)
4008 })
4009 .sorted_by_key(|(range, _)| range.start);
4010 buffer.edit(edits, None, cx);
4011 })
4012 }
4013 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4014 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4015 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4016 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4017 .zip(new_selection_deltas)
4018 .map(|(selection, delta)| Selection {
4019 id: selection.id,
4020 start: selection.start + delta,
4021 end: selection.end + delta,
4022 reversed: selection.reversed,
4023 goal: SelectionGoal::None,
4024 })
4025 .collect::<Vec<_>>();
4026
4027 let mut i = 0;
4028 for (position, delta, selection_id, pair) in new_autoclose_regions {
4029 let position = position.to_offset(&map.buffer_snapshot) + delta;
4030 let start = map.buffer_snapshot.anchor_before(position);
4031 let end = map.buffer_snapshot.anchor_after(position);
4032 while let Some(existing_state) = this.autoclose_regions.get(i) {
4033 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4034 Ordering::Less => i += 1,
4035 Ordering::Greater => break,
4036 Ordering::Equal => {
4037 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4038 Ordering::Less => i += 1,
4039 Ordering::Equal => break,
4040 Ordering::Greater => break,
4041 }
4042 }
4043 }
4044 }
4045 this.autoclose_regions.insert(
4046 i,
4047 AutocloseRegion {
4048 selection_id,
4049 range: start..end,
4050 pair,
4051 },
4052 );
4053 }
4054
4055 let had_active_inline_completion = this.has_active_inline_completion();
4056 this.change_selections(
4057 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4058 window,
4059 cx,
4060 |s| s.select(new_selections),
4061 );
4062
4063 if !bracket_inserted {
4064 if let Some(on_type_format_task) =
4065 this.trigger_on_type_formatting(text.to_string(), window, cx)
4066 {
4067 on_type_format_task.detach_and_log_err(cx);
4068 }
4069 }
4070
4071 let editor_settings = EditorSettings::get_global(cx);
4072 if bracket_inserted
4073 && (editor_settings.auto_signature_help
4074 || editor_settings.show_signature_help_after_edits)
4075 {
4076 this.show_signature_help(&ShowSignatureHelp, window, cx);
4077 }
4078
4079 let trigger_in_words =
4080 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4081 if this.hard_wrap.is_some() {
4082 let latest: Range<Point> = this.selections.newest(cx).range();
4083 if latest.is_empty()
4084 && this
4085 .buffer()
4086 .read(cx)
4087 .snapshot(cx)
4088 .line_len(MultiBufferRow(latest.start.row))
4089 == latest.start.column
4090 {
4091 this.rewrap_impl(
4092 RewrapOptions {
4093 override_language_settings: true,
4094 preserve_existing_whitespace: true,
4095 },
4096 cx,
4097 )
4098 }
4099 }
4100 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4101 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4102 this.refresh_inline_completion(true, false, window, cx);
4103 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4104 });
4105 }
4106
4107 fn find_possible_emoji_shortcode_at_position(
4108 snapshot: &MultiBufferSnapshot,
4109 position: Point,
4110 ) -> Option<String> {
4111 let mut chars = Vec::new();
4112 let mut found_colon = false;
4113 for char in snapshot.reversed_chars_at(position).take(100) {
4114 // Found a possible emoji shortcode in the middle of the buffer
4115 if found_colon {
4116 if char.is_whitespace() {
4117 chars.reverse();
4118 return Some(chars.iter().collect());
4119 }
4120 // If the previous character is not a whitespace, we are in the middle of a word
4121 // and we only want to complete the shortcode if the word is made up of other emojis
4122 let mut containing_word = String::new();
4123 for ch in snapshot
4124 .reversed_chars_at(position)
4125 .skip(chars.len() + 1)
4126 .take(100)
4127 {
4128 if ch.is_whitespace() {
4129 break;
4130 }
4131 containing_word.push(ch);
4132 }
4133 let containing_word = containing_word.chars().rev().collect::<String>();
4134 if util::word_consists_of_emojis(containing_word.as_str()) {
4135 chars.reverse();
4136 return Some(chars.iter().collect());
4137 }
4138 }
4139
4140 if char.is_whitespace() || !char.is_ascii() {
4141 return None;
4142 }
4143 if char == ':' {
4144 found_colon = true;
4145 } else {
4146 chars.push(char);
4147 }
4148 }
4149 // Found a possible emoji shortcode at the beginning of the buffer
4150 chars.reverse();
4151 Some(chars.iter().collect())
4152 }
4153
4154 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4155 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4156 self.transact(window, cx, |this, window, cx| {
4157 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4158 let selections = this.selections.all::<usize>(cx);
4159 let multi_buffer = this.buffer.read(cx);
4160 let buffer = multi_buffer.snapshot(cx);
4161 selections
4162 .iter()
4163 .map(|selection| {
4164 let start_point = selection.start.to_point(&buffer);
4165 let mut existing_indent =
4166 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4167 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4168 let start = selection.start;
4169 let end = selection.end;
4170 let selection_is_empty = start == end;
4171 let language_scope = buffer.language_scope_at(start);
4172 let (
4173 comment_delimiter,
4174 doc_delimiter,
4175 insert_extra_newline,
4176 indent_on_newline,
4177 indent_on_extra_newline,
4178 ) = if let Some(language) = &language_scope {
4179 let mut insert_extra_newline =
4180 insert_extra_newline_brackets(&buffer, start..end, language)
4181 || insert_extra_newline_tree_sitter(&buffer, start..end);
4182
4183 // Comment extension on newline is allowed only for cursor selections
4184 let comment_delimiter = maybe!({
4185 if !selection_is_empty {
4186 return None;
4187 }
4188
4189 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4190 return None;
4191 }
4192
4193 let delimiters = language.line_comment_prefixes();
4194 let max_len_of_delimiter =
4195 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4196 let (snapshot, range) =
4197 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4198
4199 let num_of_whitespaces = snapshot
4200 .chars_for_range(range.clone())
4201 .take_while(|c| c.is_whitespace())
4202 .count();
4203 let comment_candidate = snapshot
4204 .chars_for_range(range)
4205 .skip(num_of_whitespaces)
4206 .take(max_len_of_delimiter)
4207 .collect::<String>();
4208 let (delimiter, trimmed_len) = delimiters
4209 .iter()
4210 .filter_map(|delimiter| {
4211 let prefix = delimiter.trim_end();
4212 if comment_candidate.starts_with(prefix) {
4213 Some((delimiter, prefix.len()))
4214 } else {
4215 None
4216 }
4217 })
4218 .max_by_key(|(_, len)| *len)?;
4219
4220 let cursor_is_placed_after_comment_marker =
4221 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4222 if cursor_is_placed_after_comment_marker {
4223 Some(delimiter.clone())
4224 } else {
4225 None
4226 }
4227 });
4228
4229 let mut indent_on_newline = IndentSize::spaces(0);
4230 let mut indent_on_extra_newline = IndentSize::spaces(0);
4231
4232 let doc_delimiter = maybe!({
4233 if !selection_is_empty {
4234 return None;
4235 }
4236
4237 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4238 return None;
4239 }
4240
4241 let DocumentationConfig {
4242 start: start_tag,
4243 end: end_tag,
4244 prefix: delimiter,
4245 tab_size: len,
4246 } = language.documentation()?;
4247
4248 let is_within_block_comment = buffer
4249 .language_scope_at(start_point)
4250 .is_some_and(|scope| scope.override_name() == Some("comment"));
4251 if !is_within_block_comment {
4252 return None;
4253 }
4254
4255 let (snapshot, range) =
4256 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4257
4258 let num_of_whitespaces = snapshot
4259 .chars_for_range(range.clone())
4260 .take_while(|c| c.is_whitespace())
4261 .count();
4262
4263 // 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.
4264 let column = start_point.column;
4265 let cursor_is_after_start_tag = {
4266 let start_tag_len = start_tag.len();
4267 let start_tag_line = snapshot
4268 .chars_for_range(range.clone())
4269 .skip(num_of_whitespaces)
4270 .take(start_tag_len)
4271 .collect::<String>();
4272 if start_tag_line.starts_with(start_tag.as_ref()) {
4273 num_of_whitespaces + start_tag_len <= column as usize
4274 } else {
4275 false
4276 }
4277 };
4278
4279 let cursor_is_after_delimiter = {
4280 let delimiter_trim = delimiter.trim_end();
4281 let delimiter_line = snapshot
4282 .chars_for_range(range.clone())
4283 .skip(num_of_whitespaces)
4284 .take(delimiter_trim.len())
4285 .collect::<String>();
4286 if delimiter_line.starts_with(delimiter_trim) {
4287 num_of_whitespaces + delimiter_trim.len() <= column as usize
4288 } else {
4289 false
4290 }
4291 };
4292
4293 let cursor_is_before_end_tag_if_exists = {
4294 let mut char_position = 0u32;
4295 let mut end_tag_offset = None;
4296
4297 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4298 if let Some(byte_pos) = chunk.find(&**end_tag) {
4299 let chars_before_match =
4300 chunk[..byte_pos].chars().count() as u32;
4301 end_tag_offset =
4302 Some(char_position + chars_before_match);
4303 break 'outer;
4304 }
4305 char_position += chunk.chars().count() as u32;
4306 }
4307
4308 if let Some(end_tag_offset) = end_tag_offset {
4309 let cursor_is_before_end_tag = column <= end_tag_offset;
4310 if cursor_is_after_start_tag {
4311 if cursor_is_before_end_tag {
4312 insert_extra_newline = true;
4313 }
4314 let cursor_is_at_start_of_end_tag =
4315 column == end_tag_offset;
4316 if cursor_is_at_start_of_end_tag {
4317 indent_on_extra_newline.len = (*len).into();
4318 }
4319 }
4320 cursor_is_before_end_tag
4321 } else {
4322 true
4323 }
4324 };
4325
4326 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4327 && cursor_is_before_end_tag_if_exists
4328 {
4329 if cursor_is_after_start_tag {
4330 indent_on_newline.len = (*len).into();
4331 }
4332 Some(delimiter.clone())
4333 } else {
4334 None
4335 }
4336 });
4337
4338 (
4339 comment_delimiter,
4340 doc_delimiter,
4341 insert_extra_newline,
4342 indent_on_newline,
4343 indent_on_extra_newline,
4344 )
4345 } else {
4346 (
4347 None,
4348 None,
4349 false,
4350 IndentSize::default(),
4351 IndentSize::default(),
4352 )
4353 };
4354
4355 let prevent_auto_indent = doc_delimiter.is_some();
4356 let delimiter = comment_delimiter.or(doc_delimiter);
4357
4358 let capacity_for_delimiter =
4359 delimiter.as_deref().map(str::len).unwrap_or_default();
4360 let mut new_text = String::with_capacity(
4361 1 + capacity_for_delimiter
4362 + existing_indent.len as usize
4363 + indent_on_newline.len as usize
4364 + indent_on_extra_newline.len as usize,
4365 );
4366 new_text.push('\n');
4367 new_text.extend(existing_indent.chars());
4368 new_text.extend(indent_on_newline.chars());
4369
4370 if let Some(delimiter) = &delimiter {
4371 new_text.push_str(delimiter);
4372 }
4373
4374 if insert_extra_newline {
4375 new_text.push('\n');
4376 new_text.extend(existing_indent.chars());
4377 new_text.extend(indent_on_extra_newline.chars());
4378 }
4379
4380 let anchor = buffer.anchor_after(end);
4381 let new_selection = selection.map(|_| anchor);
4382 (
4383 ((start..end, new_text), prevent_auto_indent),
4384 (insert_extra_newline, new_selection),
4385 )
4386 })
4387 .unzip()
4388 };
4389
4390 let mut auto_indent_edits = Vec::new();
4391 let mut edits = Vec::new();
4392 for (edit, prevent_auto_indent) in edits_with_flags {
4393 if prevent_auto_indent {
4394 edits.push(edit);
4395 } else {
4396 auto_indent_edits.push(edit);
4397 }
4398 }
4399 if !edits.is_empty() {
4400 this.edit(edits, cx);
4401 }
4402 if !auto_indent_edits.is_empty() {
4403 this.edit_with_autoindent(auto_indent_edits, cx);
4404 }
4405
4406 let buffer = this.buffer.read(cx).snapshot(cx);
4407 let new_selections = selection_info
4408 .into_iter()
4409 .map(|(extra_newline_inserted, new_selection)| {
4410 let mut cursor = new_selection.end.to_point(&buffer);
4411 if extra_newline_inserted {
4412 cursor.row -= 1;
4413 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4414 }
4415 new_selection.map(|_| cursor)
4416 })
4417 .collect();
4418
4419 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4420 s.select(new_selections)
4421 });
4422 this.refresh_inline_completion(true, false, window, cx);
4423 });
4424 }
4425
4426 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4427 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4428
4429 let buffer = self.buffer.read(cx);
4430 let snapshot = buffer.snapshot(cx);
4431
4432 let mut edits = Vec::new();
4433 let mut rows = Vec::new();
4434
4435 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4436 let cursor = selection.head();
4437 let row = cursor.row;
4438
4439 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4440
4441 let newline = "\n".to_string();
4442 edits.push((start_of_line..start_of_line, newline));
4443
4444 rows.push(row + rows_inserted as u32);
4445 }
4446
4447 self.transact(window, cx, |editor, window, cx| {
4448 editor.edit(edits, cx);
4449
4450 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4451 let mut index = 0;
4452 s.move_cursors_with(|map, _, _| {
4453 let row = rows[index];
4454 index += 1;
4455
4456 let point = Point::new(row, 0);
4457 let boundary = map.next_line_boundary(point).1;
4458 let clipped = map.clip_point(boundary, Bias::Left);
4459
4460 (clipped, SelectionGoal::None)
4461 });
4462 });
4463
4464 let mut indent_edits = Vec::new();
4465 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4466 for row in rows {
4467 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4468 for (row, indent) in indents {
4469 if indent.len == 0 {
4470 continue;
4471 }
4472
4473 let text = match indent.kind {
4474 IndentKind::Space => " ".repeat(indent.len as usize),
4475 IndentKind::Tab => "\t".repeat(indent.len as usize),
4476 };
4477 let point = Point::new(row.0, 0);
4478 indent_edits.push((point..point, text));
4479 }
4480 }
4481 editor.edit(indent_edits, cx);
4482 });
4483 }
4484
4485 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4486 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4487
4488 let buffer = self.buffer.read(cx);
4489 let snapshot = buffer.snapshot(cx);
4490
4491 let mut edits = Vec::new();
4492 let mut rows = Vec::new();
4493 let mut rows_inserted = 0;
4494
4495 for selection in self.selections.all_adjusted(cx) {
4496 let cursor = selection.head();
4497 let row = cursor.row;
4498
4499 let point = Point::new(row + 1, 0);
4500 let start_of_line = snapshot.clip_point(point, Bias::Left);
4501
4502 let newline = "\n".to_string();
4503 edits.push((start_of_line..start_of_line, newline));
4504
4505 rows_inserted += 1;
4506 rows.push(row + rows_inserted);
4507 }
4508
4509 self.transact(window, cx, |editor, window, cx| {
4510 editor.edit(edits, cx);
4511
4512 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4513 let mut index = 0;
4514 s.move_cursors_with(|map, _, _| {
4515 let row = rows[index];
4516 index += 1;
4517
4518 let point = Point::new(row, 0);
4519 let boundary = map.next_line_boundary(point).1;
4520 let clipped = map.clip_point(boundary, Bias::Left);
4521
4522 (clipped, SelectionGoal::None)
4523 });
4524 });
4525
4526 let mut indent_edits = Vec::new();
4527 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4528 for row in rows {
4529 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4530 for (row, indent) in indents {
4531 if indent.len == 0 {
4532 continue;
4533 }
4534
4535 let text = match indent.kind {
4536 IndentKind::Space => " ".repeat(indent.len as usize),
4537 IndentKind::Tab => "\t".repeat(indent.len as usize),
4538 };
4539 let point = Point::new(row.0, 0);
4540 indent_edits.push((point..point, text));
4541 }
4542 }
4543 editor.edit(indent_edits, cx);
4544 });
4545 }
4546
4547 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4548 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4549 original_indent_columns: Vec::new(),
4550 });
4551 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4552 }
4553
4554 fn insert_with_autoindent_mode(
4555 &mut self,
4556 text: &str,
4557 autoindent_mode: Option<AutoindentMode>,
4558 window: &mut Window,
4559 cx: &mut Context<Self>,
4560 ) {
4561 if self.read_only(cx) {
4562 return;
4563 }
4564
4565 let text: Arc<str> = text.into();
4566 self.transact(window, cx, |this, window, cx| {
4567 let old_selections = this.selections.all_adjusted(cx);
4568 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4569 let anchors = {
4570 let snapshot = buffer.read(cx);
4571 old_selections
4572 .iter()
4573 .map(|s| {
4574 let anchor = snapshot.anchor_after(s.head());
4575 s.map(|_| anchor)
4576 })
4577 .collect::<Vec<_>>()
4578 };
4579 buffer.edit(
4580 old_selections
4581 .iter()
4582 .map(|s| (s.start..s.end, text.clone())),
4583 autoindent_mode,
4584 cx,
4585 );
4586 anchors
4587 });
4588
4589 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4590 s.select_anchors(selection_anchors);
4591 });
4592
4593 cx.notify();
4594 });
4595 }
4596
4597 fn trigger_completion_on_input(
4598 &mut self,
4599 text: &str,
4600 trigger_in_words: bool,
4601 window: &mut Window,
4602 cx: &mut Context<Self>,
4603 ) {
4604 let completions_source = self
4605 .context_menu
4606 .borrow()
4607 .as_ref()
4608 .and_then(|menu| match menu {
4609 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4610 CodeContextMenu::CodeActions(_) => None,
4611 });
4612
4613 match completions_source {
4614 Some(CompletionsMenuSource::Words) => {
4615 self.show_word_completions(&ShowWordCompletions, window, cx)
4616 }
4617 Some(CompletionsMenuSource::Normal)
4618 | Some(CompletionsMenuSource::SnippetChoices)
4619 | None
4620 if self.is_completion_trigger(
4621 text,
4622 trigger_in_words,
4623 completions_source.is_some(),
4624 cx,
4625 ) =>
4626 {
4627 self.show_completions(
4628 &ShowCompletions {
4629 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4630 },
4631 window,
4632 cx,
4633 )
4634 }
4635 _ => {
4636 self.hide_context_menu(window, cx);
4637 }
4638 }
4639 }
4640
4641 fn is_completion_trigger(
4642 &self,
4643 text: &str,
4644 trigger_in_words: bool,
4645 menu_is_open: bool,
4646 cx: &mut Context<Self>,
4647 ) -> bool {
4648 let position = self.selections.newest_anchor().head();
4649 let multibuffer = self.buffer.read(cx);
4650 let Some(buffer) = position
4651 .buffer_id
4652 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4653 else {
4654 return false;
4655 };
4656
4657 if let Some(completion_provider) = &self.completion_provider {
4658 completion_provider.is_completion_trigger(
4659 &buffer,
4660 position.text_anchor,
4661 text,
4662 trigger_in_words,
4663 menu_is_open,
4664 cx,
4665 )
4666 } else {
4667 false
4668 }
4669 }
4670
4671 /// If any empty selections is touching the start of its innermost containing autoclose
4672 /// region, expand it to select the brackets.
4673 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4674 let selections = self.selections.all::<usize>(cx);
4675 let buffer = self.buffer.read(cx).read(cx);
4676 let new_selections = self
4677 .selections_with_autoclose_regions(selections, &buffer)
4678 .map(|(mut selection, region)| {
4679 if !selection.is_empty() {
4680 return selection;
4681 }
4682
4683 if let Some(region) = region {
4684 let mut range = region.range.to_offset(&buffer);
4685 if selection.start == range.start && range.start >= region.pair.start.len() {
4686 range.start -= region.pair.start.len();
4687 if buffer.contains_str_at(range.start, ®ion.pair.start)
4688 && buffer.contains_str_at(range.end, ®ion.pair.end)
4689 {
4690 range.end += region.pair.end.len();
4691 selection.start = range.start;
4692 selection.end = range.end;
4693
4694 return selection;
4695 }
4696 }
4697 }
4698
4699 let always_treat_brackets_as_autoclosed = buffer
4700 .language_settings_at(selection.start, cx)
4701 .always_treat_brackets_as_autoclosed;
4702
4703 if !always_treat_brackets_as_autoclosed {
4704 return selection;
4705 }
4706
4707 if let Some(scope) = buffer.language_scope_at(selection.start) {
4708 for (pair, enabled) in scope.brackets() {
4709 if !enabled || !pair.close {
4710 continue;
4711 }
4712
4713 if buffer.contains_str_at(selection.start, &pair.end) {
4714 let pair_start_len = pair.start.len();
4715 if buffer.contains_str_at(
4716 selection.start.saturating_sub(pair_start_len),
4717 &pair.start,
4718 ) {
4719 selection.start -= pair_start_len;
4720 selection.end += pair.end.len();
4721
4722 return selection;
4723 }
4724 }
4725 }
4726 }
4727
4728 selection
4729 })
4730 .collect();
4731
4732 drop(buffer);
4733 self.change_selections(None, window, cx, |selections| {
4734 selections.select(new_selections)
4735 });
4736 }
4737
4738 /// Iterate the given selections, and for each one, find the smallest surrounding
4739 /// autoclose region. This uses the ordering of the selections and the autoclose
4740 /// regions to avoid repeated comparisons.
4741 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4742 &'a self,
4743 selections: impl IntoIterator<Item = Selection<D>>,
4744 buffer: &'a MultiBufferSnapshot,
4745 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4746 let mut i = 0;
4747 let mut regions = self.autoclose_regions.as_slice();
4748 selections.into_iter().map(move |selection| {
4749 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4750
4751 let mut enclosing = None;
4752 while let Some(pair_state) = regions.get(i) {
4753 if pair_state.range.end.to_offset(buffer) < range.start {
4754 regions = ®ions[i + 1..];
4755 i = 0;
4756 } else if pair_state.range.start.to_offset(buffer) > range.end {
4757 break;
4758 } else {
4759 if pair_state.selection_id == selection.id {
4760 enclosing = Some(pair_state);
4761 }
4762 i += 1;
4763 }
4764 }
4765
4766 (selection, enclosing)
4767 })
4768 }
4769
4770 /// Remove any autoclose regions that no longer contain their selection.
4771 fn invalidate_autoclose_regions(
4772 &mut self,
4773 mut selections: &[Selection<Anchor>],
4774 buffer: &MultiBufferSnapshot,
4775 ) {
4776 self.autoclose_regions.retain(|state| {
4777 let mut i = 0;
4778 while let Some(selection) = selections.get(i) {
4779 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4780 selections = &selections[1..];
4781 continue;
4782 }
4783 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4784 break;
4785 }
4786 if selection.id == state.selection_id {
4787 return true;
4788 } else {
4789 i += 1;
4790 }
4791 }
4792 false
4793 });
4794 }
4795
4796 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4797 let offset = position.to_offset(buffer);
4798 let (word_range, kind) = buffer.surrounding_word(offset, true);
4799 if offset > word_range.start && kind == Some(CharKind::Word) {
4800 Some(
4801 buffer
4802 .text_for_range(word_range.start..offset)
4803 .collect::<String>(),
4804 )
4805 } else {
4806 None
4807 }
4808 }
4809
4810 pub fn toggle_inline_values(
4811 &mut self,
4812 _: &ToggleInlineValues,
4813 _: &mut Window,
4814 cx: &mut Context<Self>,
4815 ) {
4816 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4817
4818 self.refresh_inline_values(cx);
4819 }
4820
4821 pub fn toggle_inlay_hints(
4822 &mut self,
4823 _: &ToggleInlayHints,
4824 _: &mut Window,
4825 cx: &mut Context<Self>,
4826 ) {
4827 self.refresh_inlay_hints(
4828 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4829 cx,
4830 );
4831 }
4832
4833 pub fn inlay_hints_enabled(&self) -> bool {
4834 self.inlay_hint_cache.enabled
4835 }
4836
4837 pub fn inline_values_enabled(&self) -> bool {
4838 self.inline_value_cache.enabled
4839 }
4840
4841 #[cfg(any(test, feature = "test-support"))]
4842 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4843 self.display_map
4844 .read(cx)
4845 .current_inlays()
4846 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4847 .cloned()
4848 .collect()
4849 }
4850
4851 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4852 if self.semantics_provider.is_none() || !self.mode.is_full() {
4853 return;
4854 }
4855
4856 let reason_description = reason.description();
4857 let ignore_debounce = matches!(
4858 reason,
4859 InlayHintRefreshReason::SettingsChange(_)
4860 | InlayHintRefreshReason::Toggle(_)
4861 | InlayHintRefreshReason::ExcerptsRemoved(_)
4862 | InlayHintRefreshReason::ModifiersChanged(_)
4863 );
4864 let (invalidate_cache, required_languages) = match reason {
4865 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4866 match self.inlay_hint_cache.modifiers_override(enabled) {
4867 Some(enabled) => {
4868 if enabled {
4869 (InvalidationStrategy::RefreshRequested, None)
4870 } else {
4871 self.splice_inlays(
4872 &self
4873 .visible_inlay_hints(cx)
4874 .iter()
4875 .map(|inlay| inlay.id)
4876 .collect::<Vec<InlayId>>(),
4877 Vec::new(),
4878 cx,
4879 );
4880 return;
4881 }
4882 }
4883 None => return,
4884 }
4885 }
4886 InlayHintRefreshReason::Toggle(enabled) => {
4887 if self.inlay_hint_cache.toggle(enabled) {
4888 if enabled {
4889 (InvalidationStrategy::RefreshRequested, None)
4890 } else {
4891 self.splice_inlays(
4892 &self
4893 .visible_inlay_hints(cx)
4894 .iter()
4895 .map(|inlay| inlay.id)
4896 .collect::<Vec<InlayId>>(),
4897 Vec::new(),
4898 cx,
4899 );
4900 return;
4901 }
4902 } else {
4903 return;
4904 }
4905 }
4906 InlayHintRefreshReason::SettingsChange(new_settings) => {
4907 match self.inlay_hint_cache.update_settings(
4908 &self.buffer,
4909 new_settings,
4910 self.visible_inlay_hints(cx),
4911 cx,
4912 ) {
4913 ControlFlow::Break(Some(InlaySplice {
4914 to_remove,
4915 to_insert,
4916 })) => {
4917 self.splice_inlays(&to_remove, to_insert, cx);
4918 return;
4919 }
4920 ControlFlow::Break(None) => return,
4921 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4922 }
4923 }
4924 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4925 if let Some(InlaySplice {
4926 to_remove,
4927 to_insert,
4928 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4929 {
4930 self.splice_inlays(&to_remove, to_insert, cx);
4931 }
4932 self.display_map.update(cx, |display_map, _| {
4933 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4934 });
4935 return;
4936 }
4937 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4938 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4939 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4940 }
4941 InlayHintRefreshReason::RefreshRequested => {
4942 (InvalidationStrategy::RefreshRequested, None)
4943 }
4944 };
4945
4946 if let Some(InlaySplice {
4947 to_remove,
4948 to_insert,
4949 }) = self.inlay_hint_cache.spawn_hint_refresh(
4950 reason_description,
4951 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4952 invalidate_cache,
4953 ignore_debounce,
4954 cx,
4955 ) {
4956 self.splice_inlays(&to_remove, to_insert, cx);
4957 }
4958 }
4959
4960 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4961 self.display_map
4962 .read(cx)
4963 .current_inlays()
4964 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4965 .cloned()
4966 .collect()
4967 }
4968
4969 pub fn excerpts_for_inlay_hints_query(
4970 &self,
4971 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4972 cx: &mut Context<Editor>,
4973 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4974 let Some(project) = self.project.as_ref() else {
4975 return HashMap::default();
4976 };
4977 let project = project.read(cx);
4978 let multi_buffer = self.buffer().read(cx);
4979 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4980 let multi_buffer_visible_start = self
4981 .scroll_manager
4982 .anchor()
4983 .anchor
4984 .to_point(&multi_buffer_snapshot);
4985 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4986 multi_buffer_visible_start
4987 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4988 Bias::Left,
4989 );
4990 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4991 multi_buffer_snapshot
4992 .range_to_buffer_ranges(multi_buffer_visible_range)
4993 .into_iter()
4994 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4995 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4996 let buffer_file = project::File::from_dyn(buffer.file())?;
4997 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4998 let worktree_entry = buffer_worktree
4999 .read(cx)
5000 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5001 if worktree_entry.is_ignored {
5002 return None;
5003 }
5004
5005 let language = buffer.language()?;
5006 if let Some(restrict_to_languages) = restrict_to_languages {
5007 if !restrict_to_languages.contains(language) {
5008 return None;
5009 }
5010 }
5011 Some((
5012 excerpt_id,
5013 (
5014 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5015 buffer.version().clone(),
5016 excerpt_visible_range,
5017 ),
5018 ))
5019 })
5020 .collect()
5021 }
5022
5023 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5024 TextLayoutDetails {
5025 text_system: window.text_system().clone(),
5026 editor_style: self.style.clone().unwrap(),
5027 rem_size: window.rem_size(),
5028 scroll_anchor: self.scroll_manager.anchor(),
5029 visible_rows: self.visible_line_count(),
5030 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5031 }
5032 }
5033
5034 pub fn splice_inlays(
5035 &self,
5036 to_remove: &[InlayId],
5037 to_insert: Vec<Inlay>,
5038 cx: &mut Context<Self>,
5039 ) {
5040 self.display_map.update(cx, |display_map, cx| {
5041 display_map.splice_inlays(to_remove, to_insert, cx)
5042 });
5043 cx.notify();
5044 }
5045
5046 fn trigger_on_type_formatting(
5047 &self,
5048 input: String,
5049 window: &mut Window,
5050 cx: &mut Context<Self>,
5051 ) -> Option<Task<Result<()>>> {
5052 if input.len() != 1 {
5053 return None;
5054 }
5055
5056 let project = self.project.as_ref()?;
5057 let position = self.selections.newest_anchor().head();
5058 let (buffer, buffer_position) = self
5059 .buffer
5060 .read(cx)
5061 .text_anchor_for_position(position, cx)?;
5062
5063 let settings = language_settings::language_settings(
5064 buffer
5065 .read(cx)
5066 .language_at(buffer_position)
5067 .map(|l| l.name()),
5068 buffer.read(cx).file(),
5069 cx,
5070 );
5071 if !settings.use_on_type_format {
5072 return None;
5073 }
5074
5075 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5076 // hence we do LSP request & edit on host side only — add formats to host's history.
5077 let push_to_lsp_host_history = true;
5078 // If this is not the host, append its history with new edits.
5079 let push_to_client_history = project.read(cx).is_via_collab();
5080
5081 let on_type_formatting = project.update(cx, |project, cx| {
5082 project.on_type_format(
5083 buffer.clone(),
5084 buffer_position,
5085 input,
5086 push_to_lsp_host_history,
5087 cx,
5088 )
5089 });
5090 Some(cx.spawn_in(window, async move |editor, cx| {
5091 if let Some(transaction) = on_type_formatting.await? {
5092 if push_to_client_history {
5093 buffer
5094 .update(cx, |buffer, _| {
5095 buffer.push_transaction(transaction, Instant::now());
5096 buffer.finalize_last_transaction();
5097 })
5098 .ok();
5099 }
5100 editor.update(cx, |editor, cx| {
5101 editor.refresh_document_highlights(cx);
5102 })?;
5103 }
5104 Ok(())
5105 }))
5106 }
5107
5108 pub fn show_word_completions(
5109 &mut self,
5110 _: &ShowWordCompletions,
5111 window: &mut Window,
5112 cx: &mut Context<Self>,
5113 ) {
5114 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5115 }
5116
5117 pub fn show_completions(
5118 &mut self,
5119 options: &ShowCompletions,
5120 window: &mut Window,
5121 cx: &mut Context<Self>,
5122 ) {
5123 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5124 }
5125
5126 fn open_or_update_completions_menu(
5127 &mut self,
5128 requested_source: Option<CompletionsMenuSource>,
5129 trigger: Option<&str>,
5130 window: &mut Window,
5131 cx: &mut Context<Self>,
5132 ) {
5133 if self.pending_rename.is_some() {
5134 return;
5135 }
5136
5137 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5138
5139 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5140 // inserted and selected. To handle that case, the start of the selection is used so that
5141 // the menu starts with all choices.
5142 let position = self
5143 .selections
5144 .newest_anchor()
5145 .start
5146 .bias_right(&multibuffer_snapshot);
5147 if position.diff_base_anchor.is_some() {
5148 return;
5149 }
5150 let (buffer, buffer_position) =
5151 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5152 output
5153 } else {
5154 return;
5155 };
5156 let buffer_snapshot = buffer.read(cx).snapshot();
5157
5158 let query: Option<Arc<String>> =
5159 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5160
5161 drop(multibuffer_snapshot);
5162
5163 let provider = match requested_source {
5164 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5165 Some(CompletionsMenuSource::Words) => None,
5166 Some(CompletionsMenuSource::SnippetChoices) => {
5167 log::error!("bug: SnippetChoices requested_source is not handled");
5168 None
5169 }
5170 };
5171
5172 let sort_completions = provider
5173 .as_ref()
5174 .map_or(false, |provider| provider.sort_completions());
5175
5176 let filter_completions = provider
5177 .as_ref()
5178 .map_or(true, |provider| provider.filter_completions());
5179
5180 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5181 if filter_completions {
5182 menu.filter(query.clone(), provider.clone(), window, cx);
5183 }
5184 // When `is_incomplete` is false, no need to re-query completions when the current query
5185 // is a suffix of the initial query.
5186 if !menu.is_incomplete {
5187 // If the new query is a suffix of the old query (typing more characters) and
5188 // the previous result was complete, the existing completions can be filtered.
5189 //
5190 // Note that this is always true for snippet completions.
5191 let query_matches = match (&menu.initial_query, &query) {
5192 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5193 (None, _) => true,
5194 _ => false,
5195 };
5196 if query_matches {
5197 let position_matches = if menu.initial_position == position {
5198 true
5199 } else {
5200 let snapshot = self.buffer.read(cx).read(cx);
5201 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5202 };
5203 if position_matches {
5204 return;
5205 }
5206 }
5207 }
5208 };
5209
5210 let trigger_kind = match trigger {
5211 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5212 CompletionTriggerKind::TRIGGER_CHARACTER
5213 }
5214 _ => CompletionTriggerKind::INVOKED,
5215 };
5216 let completion_context = CompletionContext {
5217 trigger_character: trigger.and_then(|trigger| {
5218 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5219 Some(String::from(trigger))
5220 } else {
5221 None
5222 }
5223 }),
5224 trigger_kind,
5225 };
5226
5227 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5228 buffer_snapshot.surrounding_word(buffer_position)
5229 {
5230 let word_to_exclude = buffer_snapshot
5231 .text_for_range(word_range.clone())
5232 .collect::<String>();
5233 (
5234 buffer_snapshot.anchor_before(word_range.start)
5235 ..buffer_snapshot.anchor_after(buffer_position),
5236 Some(word_to_exclude),
5237 )
5238 } else {
5239 (buffer_position..buffer_position, None)
5240 };
5241
5242 let language = buffer_snapshot
5243 .language_at(buffer_position)
5244 .map(|language| language.name());
5245
5246 let completion_settings =
5247 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5248
5249 let show_completion_documentation = buffer_snapshot
5250 .settings_at(buffer_position, cx)
5251 .show_completion_documentation;
5252
5253 // The document can be large, so stay in reasonable bounds when searching for words,
5254 // otherwise completion pop-up might be slow to appear.
5255 const WORD_LOOKUP_ROWS: u32 = 5_000;
5256 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5257 let min_word_search = buffer_snapshot.clip_point(
5258 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5259 Bias::Left,
5260 );
5261 let max_word_search = buffer_snapshot.clip_point(
5262 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5263 Bias::Right,
5264 );
5265 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5266 ..buffer_snapshot.point_to_offset(max_word_search);
5267
5268 let skip_digits = query
5269 .as_ref()
5270 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5271
5272 let (mut words, provider_responses) = match &provider {
5273 Some(provider) => {
5274 let provider_responses = provider.completions(
5275 position.excerpt_id,
5276 &buffer,
5277 buffer_position,
5278 completion_context,
5279 window,
5280 cx,
5281 );
5282
5283 let words = match completion_settings.words {
5284 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5285 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5286 .background_spawn(async move {
5287 buffer_snapshot.words_in_range(WordsQuery {
5288 fuzzy_contents: None,
5289 range: word_search_range,
5290 skip_digits,
5291 })
5292 }),
5293 };
5294
5295 (words, provider_responses)
5296 }
5297 None => (
5298 cx.background_spawn(async move {
5299 buffer_snapshot.words_in_range(WordsQuery {
5300 fuzzy_contents: None,
5301 range: word_search_range,
5302 skip_digits,
5303 })
5304 }),
5305 Task::ready(Ok(Vec::new())),
5306 ),
5307 };
5308
5309 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5310
5311 let id = post_inc(&mut self.next_completion_id);
5312 let task = cx.spawn_in(window, async move |editor, cx| {
5313 let Ok(()) = editor.update(cx, |this, _| {
5314 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5315 }) else {
5316 return;
5317 };
5318
5319 // TODO: Ideally completions from different sources would be selectively re-queried, so
5320 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5321 let mut completions = Vec::new();
5322 let mut is_incomplete = false;
5323 if let Some(provider_responses) = provider_responses.await.log_err() {
5324 if !provider_responses.is_empty() {
5325 for response in provider_responses {
5326 completions.extend(response.completions);
5327 is_incomplete = is_incomplete || response.is_incomplete;
5328 }
5329 if completion_settings.words == WordsCompletionMode::Fallback {
5330 words = Task::ready(BTreeMap::default());
5331 }
5332 }
5333 }
5334
5335 let mut words = words.await;
5336 if let Some(word_to_exclude) = &word_to_exclude {
5337 words.remove(word_to_exclude);
5338 }
5339 for lsp_completion in &completions {
5340 words.remove(&lsp_completion.new_text);
5341 }
5342 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5343 replace_range: word_replace_range.clone(),
5344 new_text: word.clone(),
5345 label: CodeLabel::plain(word, None),
5346 icon_path: None,
5347 documentation: None,
5348 source: CompletionSource::BufferWord {
5349 word_range,
5350 resolved: false,
5351 },
5352 insert_text_mode: Some(InsertTextMode::AS_IS),
5353 confirm: None,
5354 }));
5355
5356 let menu = if completions.is_empty() {
5357 None
5358 } else {
5359 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5360 let languages = editor
5361 .workspace
5362 .as_ref()
5363 .and_then(|(workspace, _)| workspace.upgrade())
5364 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5365 let menu = CompletionsMenu::new(
5366 id,
5367 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5368 sort_completions,
5369 show_completion_documentation,
5370 position,
5371 query.clone(),
5372 is_incomplete,
5373 buffer.clone(),
5374 completions.into(),
5375 snippet_sort_order,
5376 languages,
5377 language,
5378 cx,
5379 );
5380
5381 let query = if filter_completions { query } else { None };
5382 let matches_task = if let Some(query) = query {
5383 menu.do_async_filtering(query, cx)
5384 } else {
5385 Task::ready(menu.unfiltered_matches())
5386 };
5387 (menu, matches_task)
5388 }) else {
5389 return;
5390 };
5391
5392 let matches = matches_task.await;
5393
5394 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5395 // Newer menu already set, so exit.
5396 match editor.context_menu.borrow().as_ref() {
5397 Some(CodeContextMenu::Completions(prev_menu)) => {
5398 if prev_menu.id > id {
5399 return;
5400 }
5401 }
5402 _ => {}
5403 };
5404
5405 // Only valid to take prev_menu because it the new menu is immediately set
5406 // below, or the menu is hidden.
5407 match editor.context_menu.borrow_mut().take() {
5408 Some(CodeContextMenu::Completions(prev_menu)) => {
5409 let position_matches =
5410 if prev_menu.initial_position == menu.initial_position {
5411 true
5412 } else {
5413 let snapshot = editor.buffer.read(cx).read(cx);
5414 prev_menu.initial_position.to_offset(&snapshot)
5415 == menu.initial_position.to_offset(&snapshot)
5416 };
5417 if position_matches {
5418 // Preserve markdown cache before `set_filter_results` because it will
5419 // try to populate the documentation cache.
5420 menu.preserve_markdown_cache(prev_menu);
5421 }
5422 }
5423 _ => {}
5424 };
5425
5426 menu.set_filter_results(matches, provider, window, cx);
5427 }) else {
5428 return;
5429 };
5430
5431 menu.visible().then_some(menu)
5432 };
5433
5434 editor
5435 .update_in(cx, |editor, window, cx| {
5436 if editor.focus_handle.is_focused(window) {
5437 if let Some(menu) = menu {
5438 *editor.context_menu.borrow_mut() =
5439 Some(CodeContextMenu::Completions(menu));
5440
5441 crate::hover_popover::hide_hover(editor, cx);
5442 if editor.show_edit_predictions_in_menu() {
5443 editor.update_visible_inline_completion(window, cx);
5444 } else {
5445 editor.discard_inline_completion(false, cx);
5446 }
5447
5448 cx.notify();
5449 return;
5450 }
5451 }
5452
5453 if editor.completion_tasks.len() <= 1 {
5454 // If there are no more completion tasks and the last menu was empty, we should hide it.
5455 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5456 // If it was already hidden and we don't show inline completions in the menu, we should
5457 // also show the inline-completion when available.
5458 if was_hidden && editor.show_edit_predictions_in_menu() {
5459 editor.update_visible_inline_completion(window, cx);
5460 }
5461 }
5462 })
5463 .ok();
5464 });
5465
5466 self.completion_tasks.push((id, task));
5467 }
5468
5469 #[cfg(feature = "test-support")]
5470 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5471 let menu = self.context_menu.borrow();
5472 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5473 let completions = menu.completions.borrow();
5474 Some(completions.to_vec())
5475 } else {
5476 None
5477 }
5478 }
5479
5480 pub fn with_completions_menu_matching_id<R>(
5481 &self,
5482 id: CompletionId,
5483 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5484 ) -> R {
5485 let mut context_menu = self.context_menu.borrow_mut();
5486 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5487 return f(None);
5488 };
5489 if completions_menu.id != id {
5490 return f(None);
5491 }
5492 f(Some(completions_menu))
5493 }
5494
5495 pub fn confirm_completion(
5496 &mut self,
5497 action: &ConfirmCompletion,
5498 window: &mut Window,
5499 cx: &mut Context<Self>,
5500 ) -> Option<Task<Result<()>>> {
5501 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5502 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5503 }
5504
5505 pub fn confirm_completion_insert(
5506 &mut self,
5507 _: &ConfirmCompletionInsert,
5508 window: &mut Window,
5509 cx: &mut Context<Self>,
5510 ) -> Option<Task<Result<()>>> {
5511 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5512 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5513 }
5514
5515 pub fn confirm_completion_replace(
5516 &mut self,
5517 _: &ConfirmCompletionReplace,
5518 window: &mut Window,
5519 cx: &mut Context<Self>,
5520 ) -> Option<Task<Result<()>>> {
5521 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5522 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5523 }
5524
5525 pub fn compose_completion(
5526 &mut self,
5527 action: &ComposeCompletion,
5528 window: &mut Window,
5529 cx: &mut Context<Self>,
5530 ) -> Option<Task<Result<()>>> {
5531 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5532 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5533 }
5534
5535 fn do_completion(
5536 &mut self,
5537 item_ix: Option<usize>,
5538 intent: CompletionIntent,
5539 window: &mut Window,
5540 cx: &mut Context<Editor>,
5541 ) -> Option<Task<Result<()>>> {
5542 use language::ToOffset as _;
5543
5544 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5545 else {
5546 return None;
5547 };
5548
5549 let candidate_id = {
5550 let entries = completions_menu.entries.borrow();
5551 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5552 if self.show_edit_predictions_in_menu() {
5553 self.discard_inline_completion(true, cx);
5554 }
5555 mat.candidate_id
5556 };
5557
5558 let completion = completions_menu
5559 .completions
5560 .borrow()
5561 .get(candidate_id)?
5562 .clone();
5563 cx.stop_propagation();
5564
5565 let buffer_handle = completions_menu.buffer.clone();
5566
5567 let CompletionEdit {
5568 new_text,
5569 snippet,
5570 replace_range,
5571 } = process_completion_for_edit(
5572 &completion,
5573 intent,
5574 &buffer_handle,
5575 &completions_menu.initial_position.text_anchor,
5576 cx,
5577 );
5578
5579 let buffer = buffer_handle.read(cx);
5580 let snapshot = self.buffer.read(cx).snapshot(cx);
5581 let newest_anchor = self.selections.newest_anchor();
5582 let replace_range_multibuffer = {
5583 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5584 let multibuffer_anchor = snapshot
5585 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5586 .unwrap()
5587 ..snapshot
5588 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5589 .unwrap();
5590 multibuffer_anchor.start.to_offset(&snapshot)
5591 ..multibuffer_anchor.end.to_offset(&snapshot)
5592 };
5593 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5594 return None;
5595 }
5596
5597 let old_text = buffer
5598 .text_for_range(replace_range.clone())
5599 .collect::<String>();
5600 let lookbehind = newest_anchor
5601 .start
5602 .text_anchor
5603 .to_offset(buffer)
5604 .saturating_sub(replace_range.start);
5605 let lookahead = replace_range
5606 .end
5607 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5608 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5609 let suffix = &old_text[lookbehind.min(old_text.len())..];
5610
5611 let selections = self.selections.all::<usize>(cx);
5612 let mut ranges = Vec::new();
5613 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5614
5615 for selection in &selections {
5616 let range = if selection.id == newest_anchor.id {
5617 replace_range_multibuffer.clone()
5618 } else {
5619 let mut range = selection.range();
5620
5621 // if prefix is present, don't duplicate it
5622 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5623 range.start = range.start.saturating_sub(lookbehind);
5624
5625 // if suffix is also present, mimic the newest cursor and replace it
5626 if selection.id != newest_anchor.id
5627 && snapshot.contains_str_at(range.end, suffix)
5628 {
5629 range.end += lookahead;
5630 }
5631 }
5632 range
5633 };
5634
5635 ranges.push(range.clone());
5636
5637 if !self.linked_edit_ranges.is_empty() {
5638 let start_anchor = snapshot.anchor_before(range.start);
5639 let end_anchor = snapshot.anchor_after(range.end);
5640 if let Some(ranges) = self
5641 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5642 {
5643 for (buffer, edits) in ranges {
5644 linked_edits
5645 .entry(buffer.clone())
5646 .or_default()
5647 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5648 }
5649 }
5650 }
5651 }
5652
5653 let common_prefix_len = old_text
5654 .chars()
5655 .zip(new_text.chars())
5656 .take_while(|(a, b)| a == b)
5657 .map(|(a, _)| a.len_utf8())
5658 .sum::<usize>();
5659
5660 cx.emit(EditorEvent::InputHandled {
5661 utf16_range_to_replace: None,
5662 text: new_text[common_prefix_len..].into(),
5663 });
5664
5665 self.transact(window, cx, |this, window, cx| {
5666 if let Some(mut snippet) = snippet {
5667 snippet.text = new_text.to_string();
5668 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5669 } else {
5670 this.buffer.update(cx, |buffer, cx| {
5671 let auto_indent = match completion.insert_text_mode {
5672 Some(InsertTextMode::AS_IS) => None,
5673 _ => this.autoindent_mode.clone(),
5674 };
5675 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5676 buffer.edit(edits, auto_indent, cx);
5677 });
5678 }
5679 for (buffer, edits) in linked_edits {
5680 buffer.update(cx, |buffer, cx| {
5681 let snapshot = buffer.snapshot();
5682 let edits = edits
5683 .into_iter()
5684 .map(|(range, text)| {
5685 use text::ToPoint as TP;
5686 let end_point = TP::to_point(&range.end, &snapshot);
5687 let start_point = TP::to_point(&range.start, &snapshot);
5688 (start_point..end_point, text)
5689 })
5690 .sorted_by_key(|(range, _)| range.start);
5691 buffer.edit(edits, None, cx);
5692 })
5693 }
5694
5695 this.refresh_inline_completion(true, false, window, cx);
5696 });
5697
5698 let show_new_completions_on_confirm = completion
5699 .confirm
5700 .as_ref()
5701 .map_or(false, |confirm| confirm(intent, window, cx));
5702 if show_new_completions_on_confirm {
5703 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5704 }
5705
5706 let provider = self.completion_provider.as_ref()?;
5707 drop(completion);
5708 let apply_edits = provider.apply_additional_edits_for_completion(
5709 buffer_handle,
5710 completions_menu.completions.clone(),
5711 candidate_id,
5712 true,
5713 cx,
5714 );
5715
5716 let editor_settings = EditorSettings::get_global(cx);
5717 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5718 // After the code completion is finished, users often want to know what signatures are needed.
5719 // so we should automatically call signature_help
5720 self.show_signature_help(&ShowSignatureHelp, window, cx);
5721 }
5722
5723 Some(cx.foreground_executor().spawn(async move {
5724 apply_edits.await?;
5725 Ok(())
5726 }))
5727 }
5728
5729 pub fn toggle_code_actions(
5730 &mut self,
5731 action: &ToggleCodeActions,
5732 window: &mut Window,
5733 cx: &mut Context<Self>,
5734 ) {
5735 let quick_launch = action.quick_launch;
5736 let mut context_menu = self.context_menu.borrow_mut();
5737 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5738 if code_actions.deployed_from == action.deployed_from {
5739 // Toggle if we're selecting the same one
5740 *context_menu = None;
5741 cx.notify();
5742 return;
5743 } else {
5744 // Otherwise, clear it and start a new one
5745 *context_menu = None;
5746 cx.notify();
5747 }
5748 }
5749 drop(context_menu);
5750 let snapshot = self.snapshot(window, cx);
5751 let deployed_from = action.deployed_from.clone();
5752 let action = action.clone();
5753 self.completion_tasks.clear();
5754 self.discard_inline_completion(false, cx);
5755
5756 let multibuffer_point = match &action.deployed_from {
5757 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5758 DisplayPoint::new(*row, 0).to_point(&snapshot)
5759 }
5760 _ => self.selections.newest::<Point>(cx).head(),
5761 };
5762 let Some((buffer, buffer_row)) = snapshot
5763 .buffer_snapshot
5764 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5765 .and_then(|(buffer_snapshot, range)| {
5766 self.buffer()
5767 .read(cx)
5768 .buffer(buffer_snapshot.remote_id())
5769 .map(|buffer| (buffer, range.start.row))
5770 })
5771 else {
5772 return;
5773 };
5774 let buffer_id = buffer.read(cx).remote_id();
5775 let tasks = self
5776 .tasks
5777 .get(&(buffer_id, buffer_row))
5778 .map(|t| Arc::new(t.to_owned()));
5779
5780 if !self.focus_handle.is_focused(window) {
5781 return;
5782 }
5783 let project = self.project.clone();
5784
5785 let code_actions_task = match deployed_from {
5786 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5787 _ => self.code_actions(buffer_row, window, cx),
5788 };
5789
5790 let runnable_task = match deployed_from {
5791 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5792 _ => {
5793 let mut task_context_task = Task::ready(None);
5794 if let Some(tasks) = &tasks {
5795 if let Some(project) = project {
5796 task_context_task =
5797 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5798 }
5799 }
5800
5801 cx.spawn_in(window, {
5802 let buffer = buffer.clone();
5803 async move |editor, cx| {
5804 let task_context = task_context_task.await;
5805
5806 let resolved_tasks =
5807 tasks
5808 .zip(task_context.clone())
5809 .map(|(tasks, task_context)| ResolvedTasks {
5810 templates: tasks.resolve(&task_context).collect(),
5811 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5812 multibuffer_point.row,
5813 tasks.column,
5814 )),
5815 });
5816 let debug_scenarios = editor
5817 .update(cx, |editor, cx| {
5818 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5819 })?
5820 .await;
5821 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5822 }
5823 })
5824 }
5825 };
5826
5827 cx.spawn_in(window, async move |editor, cx| {
5828 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5829 let code_actions = code_actions_task.await;
5830 let spawn_straight_away = quick_launch
5831 && resolved_tasks
5832 .as_ref()
5833 .map_or(false, |tasks| tasks.templates.len() == 1)
5834 && code_actions
5835 .as_ref()
5836 .map_or(true, |actions| actions.is_empty())
5837 && debug_scenarios.is_empty();
5838
5839 editor.update_in(cx, |editor, window, cx| {
5840 crate::hover_popover::hide_hover(editor, cx);
5841 *editor.context_menu.borrow_mut() =
5842 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5843 buffer,
5844 actions: CodeActionContents::new(
5845 resolved_tasks,
5846 code_actions,
5847 debug_scenarios,
5848 task_context.unwrap_or_default(),
5849 ),
5850 selected_item: Default::default(),
5851 scroll_handle: UniformListScrollHandle::default(),
5852 deployed_from,
5853 }));
5854 if spawn_straight_away {
5855 if let Some(task) = editor.confirm_code_action(
5856 &ConfirmCodeAction { item_ix: Some(0) },
5857 window,
5858 cx,
5859 ) {
5860 cx.notify();
5861 return task;
5862 }
5863 }
5864
5865 Task::ready(Ok(()))
5866 })
5867 })
5868 .detach_and_log_err(cx);
5869 }
5870
5871 fn debug_scenarios(
5872 &mut self,
5873 resolved_tasks: &Option<ResolvedTasks>,
5874 buffer: &Entity<Buffer>,
5875 cx: &mut App,
5876 ) -> Task<Vec<task::DebugScenario>> {
5877 if cx.has_flag::<DebuggerFeatureFlag>() {
5878 maybe!({
5879 let project = self.project.as_ref()?;
5880 let dap_store = project.read(cx).dap_store();
5881 let mut scenarios = vec![];
5882 let resolved_tasks = resolved_tasks.as_ref()?;
5883 let buffer = buffer.read(cx);
5884 let language = buffer.language()?;
5885 let file = buffer.file();
5886 let debug_adapter = language_settings(language.name().into(), file, cx)
5887 .debuggers
5888 .first()
5889 .map(SharedString::from)
5890 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5891
5892 dap_store.update(cx, |dap_store, cx| {
5893 for (_, task) in &resolved_tasks.templates {
5894 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5895 task.original_task().clone(),
5896 debug_adapter.clone().into(),
5897 task.display_label().to_owned().into(),
5898 cx,
5899 );
5900 scenarios.push(maybe_scenario);
5901 }
5902 });
5903 Some(cx.background_spawn(async move {
5904 let scenarios = futures::future::join_all(scenarios)
5905 .await
5906 .into_iter()
5907 .flatten()
5908 .collect::<Vec<_>>();
5909 scenarios
5910 }))
5911 })
5912 .unwrap_or_else(|| Task::ready(vec![]))
5913 } else {
5914 Task::ready(vec![])
5915 }
5916 }
5917
5918 fn code_actions(
5919 &mut self,
5920 buffer_row: u32,
5921 window: &mut Window,
5922 cx: &mut Context<Self>,
5923 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5924 let mut task = self.code_actions_task.take();
5925 cx.spawn_in(window, async move |editor, cx| {
5926 while let Some(prev_task) = task {
5927 prev_task.await.log_err();
5928 task = editor
5929 .update(cx, |this, _| this.code_actions_task.take())
5930 .ok()?;
5931 }
5932
5933 editor
5934 .update(cx, |editor, cx| {
5935 editor
5936 .available_code_actions
5937 .clone()
5938 .and_then(|(location, code_actions)| {
5939 let snapshot = location.buffer.read(cx).snapshot();
5940 let point_range = location.range.to_point(&snapshot);
5941 let point_range = point_range.start.row..=point_range.end.row;
5942 if point_range.contains(&buffer_row) {
5943 Some(code_actions)
5944 } else {
5945 None
5946 }
5947 })
5948 })
5949 .ok()
5950 .flatten()
5951 })
5952 }
5953
5954 pub fn confirm_code_action(
5955 &mut self,
5956 action: &ConfirmCodeAction,
5957 window: &mut Window,
5958 cx: &mut Context<Self>,
5959 ) -> Option<Task<Result<()>>> {
5960 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5961
5962 let actions_menu =
5963 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5964 menu
5965 } else {
5966 return None;
5967 };
5968
5969 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5970 let action = actions_menu.actions.get(action_ix)?;
5971 let title = action.label();
5972 let buffer = actions_menu.buffer;
5973 let workspace = self.workspace()?;
5974
5975 match action {
5976 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5977 workspace.update(cx, |workspace, cx| {
5978 workspace.schedule_resolved_task(
5979 task_source_kind,
5980 resolved_task,
5981 false,
5982 window,
5983 cx,
5984 );
5985
5986 Some(Task::ready(Ok(())))
5987 })
5988 }
5989 CodeActionsItem::CodeAction {
5990 excerpt_id,
5991 action,
5992 provider,
5993 } => {
5994 let apply_code_action =
5995 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5996 let workspace = workspace.downgrade();
5997 Some(cx.spawn_in(window, async move |editor, cx| {
5998 let project_transaction = apply_code_action.await?;
5999 Self::open_project_transaction(
6000 &editor,
6001 workspace,
6002 project_transaction,
6003 title,
6004 cx,
6005 )
6006 .await
6007 }))
6008 }
6009 CodeActionsItem::DebugScenario(scenario) => {
6010 let context = actions_menu.actions.context.clone();
6011
6012 workspace.update(cx, |workspace, cx| {
6013 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6014 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6015 });
6016 Some(Task::ready(Ok(())))
6017 }
6018 }
6019 }
6020
6021 pub async fn open_project_transaction(
6022 this: &WeakEntity<Editor>,
6023 workspace: WeakEntity<Workspace>,
6024 transaction: ProjectTransaction,
6025 title: String,
6026 cx: &mut AsyncWindowContext,
6027 ) -> Result<()> {
6028 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6029 cx.update(|_, cx| {
6030 entries.sort_unstable_by_key(|(buffer, _)| {
6031 buffer.read(cx).file().map(|f| f.path().clone())
6032 });
6033 })?;
6034
6035 // If the project transaction's edits are all contained within this editor, then
6036 // avoid opening a new editor to display them.
6037
6038 if let Some((buffer, transaction)) = entries.first() {
6039 if entries.len() == 1 {
6040 let excerpt = this.update(cx, |editor, cx| {
6041 editor
6042 .buffer()
6043 .read(cx)
6044 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6045 })?;
6046 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6047 if excerpted_buffer == *buffer {
6048 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6049 let excerpt_range = excerpt_range.to_offset(buffer);
6050 buffer
6051 .edited_ranges_for_transaction::<usize>(transaction)
6052 .all(|range| {
6053 excerpt_range.start <= range.start
6054 && excerpt_range.end >= range.end
6055 })
6056 })?;
6057
6058 if all_edits_within_excerpt {
6059 return Ok(());
6060 }
6061 }
6062 }
6063 }
6064 } else {
6065 return Ok(());
6066 }
6067
6068 let mut ranges_to_highlight = Vec::new();
6069 let excerpt_buffer = cx.new(|cx| {
6070 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6071 for (buffer_handle, transaction) in &entries {
6072 let edited_ranges = buffer_handle
6073 .read(cx)
6074 .edited_ranges_for_transaction::<Point>(transaction)
6075 .collect::<Vec<_>>();
6076 let (ranges, _) = multibuffer.set_excerpts_for_path(
6077 PathKey::for_buffer(buffer_handle, cx),
6078 buffer_handle.clone(),
6079 edited_ranges,
6080 DEFAULT_MULTIBUFFER_CONTEXT,
6081 cx,
6082 );
6083
6084 ranges_to_highlight.extend(ranges);
6085 }
6086 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6087 multibuffer
6088 })?;
6089
6090 workspace.update_in(cx, |workspace, window, cx| {
6091 let project = workspace.project().clone();
6092 let editor =
6093 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6094 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6095 editor.update(cx, |editor, cx| {
6096 editor.highlight_background::<Self>(
6097 &ranges_to_highlight,
6098 |theme| theme.editor_highlighted_line_background,
6099 cx,
6100 );
6101 });
6102 })?;
6103
6104 Ok(())
6105 }
6106
6107 pub fn clear_code_action_providers(&mut self) {
6108 self.code_action_providers.clear();
6109 self.available_code_actions.take();
6110 }
6111
6112 pub fn add_code_action_provider(
6113 &mut self,
6114 provider: Rc<dyn CodeActionProvider>,
6115 window: &mut Window,
6116 cx: &mut Context<Self>,
6117 ) {
6118 if self
6119 .code_action_providers
6120 .iter()
6121 .any(|existing_provider| existing_provider.id() == provider.id())
6122 {
6123 return;
6124 }
6125
6126 self.code_action_providers.push(provider);
6127 self.refresh_code_actions(window, cx);
6128 }
6129
6130 pub fn remove_code_action_provider(
6131 &mut self,
6132 id: Arc<str>,
6133 window: &mut Window,
6134 cx: &mut Context<Self>,
6135 ) {
6136 self.code_action_providers
6137 .retain(|provider| provider.id() != id);
6138 self.refresh_code_actions(window, cx);
6139 }
6140
6141 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6142 !self.code_action_providers.is_empty()
6143 && EditorSettings::get_global(cx).toolbar.code_actions
6144 }
6145
6146 pub fn has_available_code_actions(&self) -> bool {
6147 self.available_code_actions
6148 .as_ref()
6149 .is_some_and(|(_, actions)| !actions.is_empty())
6150 }
6151
6152 fn render_inline_code_actions(
6153 &self,
6154 icon_size: ui::IconSize,
6155 display_row: DisplayRow,
6156 is_active: bool,
6157 cx: &mut Context<Self>,
6158 ) -> AnyElement {
6159 let show_tooltip = !self.context_menu_visible();
6160 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6161 .icon_size(icon_size)
6162 .shape(ui::IconButtonShape::Square)
6163 .style(ButtonStyle::Transparent)
6164 .icon_color(ui::Color::Hidden)
6165 .toggle_state(is_active)
6166 .when(show_tooltip, |this| {
6167 this.tooltip({
6168 let focus_handle = self.focus_handle.clone();
6169 move |window, cx| {
6170 Tooltip::for_action_in(
6171 "Toggle Code Actions",
6172 &ToggleCodeActions {
6173 deployed_from: None,
6174 quick_launch: false,
6175 },
6176 &focus_handle,
6177 window,
6178 cx,
6179 )
6180 }
6181 })
6182 })
6183 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6184 window.focus(&editor.focus_handle(cx));
6185 editor.toggle_code_actions(
6186 &crate::actions::ToggleCodeActions {
6187 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6188 display_row,
6189 )),
6190 quick_launch: false,
6191 },
6192 window,
6193 cx,
6194 );
6195 }))
6196 .into_any_element()
6197 }
6198
6199 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6200 &self.context_menu
6201 }
6202
6203 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6204 let newest_selection = self.selections.newest_anchor().clone();
6205 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6206 let buffer = self.buffer.read(cx);
6207 if newest_selection.head().diff_base_anchor.is_some() {
6208 return None;
6209 }
6210 let (start_buffer, start) =
6211 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6212 let (end_buffer, end) =
6213 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6214 if start_buffer != end_buffer {
6215 return None;
6216 }
6217
6218 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6219 cx.background_executor()
6220 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6221 .await;
6222
6223 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6224 let providers = this.code_action_providers.clone();
6225 let tasks = this
6226 .code_action_providers
6227 .iter()
6228 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6229 .collect::<Vec<_>>();
6230 (providers, tasks)
6231 })?;
6232
6233 let mut actions = Vec::new();
6234 for (provider, provider_actions) in
6235 providers.into_iter().zip(future::join_all(tasks).await)
6236 {
6237 if let Some(provider_actions) = provider_actions.log_err() {
6238 actions.extend(provider_actions.into_iter().map(|action| {
6239 AvailableCodeAction {
6240 excerpt_id: newest_selection.start.excerpt_id,
6241 action,
6242 provider: provider.clone(),
6243 }
6244 }));
6245 }
6246 }
6247
6248 this.update(cx, |this, cx| {
6249 this.available_code_actions = if actions.is_empty() {
6250 None
6251 } else {
6252 Some((
6253 Location {
6254 buffer: start_buffer,
6255 range: start..end,
6256 },
6257 actions.into(),
6258 ))
6259 };
6260 cx.notify();
6261 })
6262 }));
6263 None
6264 }
6265
6266 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6267 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6268 self.show_git_blame_inline = false;
6269
6270 self.show_git_blame_inline_delay_task =
6271 Some(cx.spawn_in(window, async move |this, cx| {
6272 cx.background_executor().timer(delay).await;
6273
6274 this.update(cx, |this, cx| {
6275 this.show_git_blame_inline = true;
6276 cx.notify();
6277 })
6278 .log_err();
6279 }));
6280 }
6281 }
6282
6283 fn show_blame_popover(
6284 &mut self,
6285 blame_entry: &BlameEntry,
6286 position: gpui::Point<Pixels>,
6287 cx: &mut Context<Self>,
6288 ) {
6289 if let Some(state) = &mut self.inline_blame_popover {
6290 state.hide_task.take();
6291 } else {
6292 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6293 let blame_entry = blame_entry.clone();
6294 let show_task = cx.spawn(async move |editor, cx| {
6295 cx.background_executor()
6296 .timer(std::time::Duration::from_millis(delay))
6297 .await;
6298 editor
6299 .update(cx, |editor, cx| {
6300 editor.inline_blame_popover_show_task.take();
6301 let Some(blame) = editor.blame.as_ref() else {
6302 return;
6303 };
6304 let blame = blame.read(cx);
6305 let details = blame.details_for_entry(&blame_entry);
6306 let markdown = cx.new(|cx| {
6307 Markdown::new(
6308 details
6309 .as_ref()
6310 .map(|message| message.message.clone())
6311 .unwrap_or_default(),
6312 None,
6313 None,
6314 cx,
6315 )
6316 });
6317 editor.inline_blame_popover = Some(InlineBlamePopover {
6318 position,
6319 hide_task: None,
6320 popover_bounds: None,
6321 popover_state: InlineBlamePopoverState {
6322 scroll_handle: ScrollHandle::new(),
6323 commit_message: details,
6324 markdown,
6325 },
6326 });
6327 cx.notify();
6328 })
6329 .ok();
6330 });
6331 self.inline_blame_popover_show_task = Some(show_task);
6332 }
6333 }
6334
6335 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6336 self.inline_blame_popover_show_task.take();
6337 if let Some(state) = &mut self.inline_blame_popover {
6338 let hide_task = cx.spawn(async move |editor, cx| {
6339 cx.background_executor()
6340 .timer(std::time::Duration::from_millis(100))
6341 .await;
6342 editor
6343 .update(cx, |editor, cx| {
6344 editor.inline_blame_popover.take();
6345 cx.notify();
6346 })
6347 .ok();
6348 });
6349 state.hide_task = Some(hide_task);
6350 }
6351 }
6352
6353 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6354 if self.pending_rename.is_some() {
6355 return None;
6356 }
6357
6358 let provider = self.semantics_provider.clone()?;
6359 let buffer = self.buffer.read(cx);
6360 let newest_selection = self.selections.newest_anchor().clone();
6361 let cursor_position = newest_selection.head();
6362 let (cursor_buffer, cursor_buffer_position) =
6363 buffer.text_anchor_for_position(cursor_position, cx)?;
6364 let (tail_buffer, tail_buffer_position) =
6365 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6366 if cursor_buffer != tail_buffer {
6367 return None;
6368 }
6369
6370 let snapshot = cursor_buffer.read(cx).snapshot();
6371 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6372 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6373 if start_word_range != end_word_range {
6374 self.document_highlights_task.take();
6375 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6376 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6377 return None;
6378 }
6379
6380 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6381 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6382 cx.background_executor()
6383 .timer(Duration::from_millis(debounce))
6384 .await;
6385
6386 let highlights = if let Some(highlights) = cx
6387 .update(|cx| {
6388 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6389 })
6390 .ok()
6391 .flatten()
6392 {
6393 highlights.await.log_err()
6394 } else {
6395 None
6396 };
6397
6398 if let Some(highlights) = highlights {
6399 this.update(cx, |this, cx| {
6400 if this.pending_rename.is_some() {
6401 return;
6402 }
6403
6404 let buffer_id = cursor_position.buffer_id;
6405 let buffer = this.buffer.read(cx);
6406 if !buffer
6407 .text_anchor_for_position(cursor_position, cx)
6408 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6409 {
6410 return;
6411 }
6412
6413 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6414 let mut write_ranges = Vec::new();
6415 let mut read_ranges = Vec::new();
6416 for highlight in highlights {
6417 for (excerpt_id, excerpt_range) in
6418 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6419 {
6420 let start = highlight
6421 .range
6422 .start
6423 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6424 let end = highlight
6425 .range
6426 .end
6427 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6428 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6429 continue;
6430 }
6431
6432 let range = Anchor {
6433 buffer_id,
6434 excerpt_id,
6435 text_anchor: start,
6436 diff_base_anchor: None,
6437 }..Anchor {
6438 buffer_id,
6439 excerpt_id,
6440 text_anchor: end,
6441 diff_base_anchor: None,
6442 };
6443 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6444 write_ranges.push(range);
6445 } else {
6446 read_ranges.push(range);
6447 }
6448 }
6449 }
6450
6451 this.highlight_background::<DocumentHighlightRead>(
6452 &read_ranges,
6453 |theme| theme.editor_document_highlight_read_background,
6454 cx,
6455 );
6456 this.highlight_background::<DocumentHighlightWrite>(
6457 &write_ranges,
6458 |theme| theme.editor_document_highlight_write_background,
6459 cx,
6460 );
6461 cx.notify();
6462 })
6463 .log_err();
6464 }
6465 }));
6466 None
6467 }
6468
6469 fn prepare_highlight_query_from_selection(
6470 &mut self,
6471 cx: &mut Context<Editor>,
6472 ) -> Option<(String, Range<Anchor>)> {
6473 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6474 return None;
6475 }
6476 if !EditorSettings::get_global(cx).selection_highlight {
6477 return None;
6478 }
6479 if self.selections.count() != 1 || self.selections.line_mode {
6480 return None;
6481 }
6482 let selection = self.selections.newest::<Point>(cx);
6483 if selection.is_empty() || selection.start.row != selection.end.row {
6484 return None;
6485 }
6486 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6487 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6488 let query = multi_buffer_snapshot
6489 .text_for_range(selection_anchor_range.clone())
6490 .collect::<String>();
6491 if query.trim().is_empty() {
6492 return None;
6493 }
6494 Some((query, selection_anchor_range))
6495 }
6496
6497 fn update_selection_occurrence_highlights(
6498 &mut self,
6499 query_text: String,
6500 query_range: Range<Anchor>,
6501 multi_buffer_range_to_query: Range<Point>,
6502 use_debounce: bool,
6503 window: &mut Window,
6504 cx: &mut Context<Editor>,
6505 ) -> Task<()> {
6506 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6507 cx.spawn_in(window, async move |editor, cx| {
6508 if use_debounce {
6509 cx.background_executor()
6510 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6511 .await;
6512 }
6513 let match_task = cx.background_spawn(async move {
6514 let buffer_ranges = multi_buffer_snapshot
6515 .range_to_buffer_ranges(multi_buffer_range_to_query)
6516 .into_iter()
6517 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6518 let mut match_ranges = Vec::new();
6519 let Ok(regex) = project::search::SearchQuery::text(
6520 query_text.clone(),
6521 false,
6522 false,
6523 false,
6524 Default::default(),
6525 Default::default(),
6526 false,
6527 None,
6528 ) else {
6529 return Vec::default();
6530 };
6531 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6532 match_ranges.extend(
6533 regex
6534 .search(&buffer_snapshot, Some(search_range.clone()))
6535 .await
6536 .into_iter()
6537 .filter_map(|match_range| {
6538 let match_start = buffer_snapshot
6539 .anchor_after(search_range.start + match_range.start);
6540 let match_end = buffer_snapshot
6541 .anchor_before(search_range.start + match_range.end);
6542 let match_anchor_range = Anchor::range_in_buffer(
6543 excerpt_id,
6544 buffer_snapshot.remote_id(),
6545 match_start..match_end,
6546 );
6547 (match_anchor_range != query_range).then_some(match_anchor_range)
6548 }),
6549 );
6550 }
6551 match_ranges
6552 });
6553 let match_ranges = match_task.await;
6554 editor
6555 .update_in(cx, |editor, _, cx| {
6556 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6557 if !match_ranges.is_empty() {
6558 editor.highlight_background::<SelectedTextHighlight>(
6559 &match_ranges,
6560 |theme| theme.editor_document_highlight_bracket_background,
6561 cx,
6562 )
6563 }
6564 })
6565 .log_err();
6566 })
6567 }
6568
6569 fn refresh_selected_text_highlights(
6570 &mut self,
6571 on_buffer_edit: bool,
6572 window: &mut Window,
6573 cx: &mut Context<Editor>,
6574 ) {
6575 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6576 else {
6577 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6578 self.quick_selection_highlight_task.take();
6579 self.debounced_selection_highlight_task.take();
6580 return;
6581 };
6582 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6583 if on_buffer_edit
6584 || self
6585 .quick_selection_highlight_task
6586 .as_ref()
6587 .map_or(true, |(prev_anchor_range, _)| {
6588 prev_anchor_range != &query_range
6589 })
6590 {
6591 let multi_buffer_visible_start = self
6592 .scroll_manager
6593 .anchor()
6594 .anchor
6595 .to_point(&multi_buffer_snapshot);
6596 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6597 multi_buffer_visible_start
6598 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6599 Bias::Left,
6600 );
6601 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6602 self.quick_selection_highlight_task = Some((
6603 query_range.clone(),
6604 self.update_selection_occurrence_highlights(
6605 query_text.clone(),
6606 query_range.clone(),
6607 multi_buffer_visible_range,
6608 false,
6609 window,
6610 cx,
6611 ),
6612 ));
6613 }
6614 if on_buffer_edit
6615 || self
6616 .debounced_selection_highlight_task
6617 .as_ref()
6618 .map_or(true, |(prev_anchor_range, _)| {
6619 prev_anchor_range != &query_range
6620 })
6621 {
6622 let multi_buffer_start = multi_buffer_snapshot
6623 .anchor_before(0)
6624 .to_point(&multi_buffer_snapshot);
6625 let multi_buffer_end = multi_buffer_snapshot
6626 .anchor_after(multi_buffer_snapshot.len())
6627 .to_point(&multi_buffer_snapshot);
6628 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6629 self.debounced_selection_highlight_task = Some((
6630 query_range.clone(),
6631 self.update_selection_occurrence_highlights(
6632 query_text,
6633 query_range,
6634 multi_buffer_full_range,
6635 true,
6636 window,
6637 cx,
6638 ),
6639 ));
6640 }
6641 }
6642
6643 pub fn refresh_inline_completion(
6644 &mut self,
6645 debounce: bool,
6646 user_requested: bool,
6647 window: &mut Window,
6648 cx: &mut Context<Self>,
6649 ) -> Option<()> {
6650 let provider = self.edit_prediction_provider()?;
6651 let cursor = self.selections.newest_anchor().head();
6652 let (buffer, cursor_buffer_position) =
6653 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6654
6655 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6656 self.discard_inline_completion(false, cx);
6657 return None;
6658 }
6659
6660 if !user_requested
6661 && (!self.should_show_edit_predictions()
6662 || !self.is_focused(window)
6663 || buffer.read(cx).is_empty())
6664 {
6665 self.discard_inline_completion(false, cx);
6666 return None;
6667 }
6668
6669 self.update_visible_inline_completion(window, cx);
6670 provider.refresh(
6671 self.project.clone(),
6672 buffer,
6673 cursor_buffer_position,
6674 debounce,
6675 cx,
6676 );
6677 Some(())
6678 }
6679
6680 fn show_edit_predictions_in_menu(&self) -> bool {
6681 match self.edit_prediction_settings {
6682 EditPredictionSettings::Disabled => false,
6683 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6684 }
6685 }
6686
6687 pub fn edit_predictions_enabled(&self) -> bool {
6688 match self.edit_prediction_settings {
6689 EditPredictionSettings::Disabled => false,
6690 EditPredictionSettings::Enabled { .. } => true,
6691 }
6692 }
6693
6694 fn edit_prediction_requires_modifier(&self) -> bool {
6695 match self.edit_prediction_settings {
6696 EditPredictionSettings::Disabled => false,
6697 EditPredictionSettings::Enabled {
6698 preview_requires_modifier,
6699 ..
6700 } => preview_requires_modifier,
6701 }
6702 }
6703
6704 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6705 if self.edit_prediction_provider.is_none() {
6706 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6707 } else {
6708 let selection = self.selections.newest_anchor();
6709 let cursor = selection.head();
6710
6711 if let Some((buffer, cursor_buffer_position)) =
6712 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6713 {
6714 self.edit_prediction_settings =
6715 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6716 }
6717 }
6718 }
6719
6720 fn edit_prediction_settings_at_position(
6721 &self,
6722 buffer: &Entity<Buffer>,
6723 buffer_position: language::Anchor,
6724 cx: &App,
6725 ) -> EditPredictionSettings {
6726 if !self.mode.is_full()
6727 || !self.show_inline_completions_override.unwrap_or(true)
6728 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6729 {
6730 return EditPredictionSettings::Disabled;
6731 }
6732
6733 let buffer = buffer.read(cx);
6734
6735 let file = buffer.file();
6736
6737 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6738 return EditPredictionSettings::Disabled;
6739 };
6740
6741 let by_provider = matches!(
6742 self.menu_inline_completions_policy,
6743 MenuInlineCompletionsPolicy::ByProvider
6744 );
6745
6746 let show_in_menu = by_provider
6747 && self
6748 .edit_prediction_provider
6749 .as_ref()
6750 .map_or(false, |provider| {
6751 provider.provider.show_completions_in_menu()
6752 });
6753
6754 let preview_requires_modifier =
6755 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6756
6757 EditPredictionSettings::Enabled {
6758 show_in_menu,
6759 preview_requires_modifier,
6760 }
6761 }
6762
6763 fn should_show_edit_predictions(&self) -> bool {
6764 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6765 }
6766
6767 pub fn edit_prediction_preview_is_active(&self) -> bool {
6768 matches!(
6769 self.edit_prediction_preview,
6770 EditPredictionPreview::Active { .. }
6771 )
6772 }
6773
6774 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6775 let cursor = self.selections.newest_anchor().head();
6776 if let Some((buffer, cursor_position)) =
6777 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6778 {
6779 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6780 } else {
6781 false
6782 }
6783 }
6784
6785 pub fn supports_minimap(&self, cx: &App) -> bool {
6786 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6787 }
6788
6789 fn edit_predictions_enabled_in_buffer(
6790 &self,
6791 buffer: &Entity<Buffer>,
6792 buffer_position: language::Anchor,
6793 cx: &App,
6794 ) -> bool {
6795 maybe!({
6796 if self.read_only(cx) {
6797 return Some(false);
6798 }
6799 let provider = self.edit_prediction_provider()?;
6800 if !provider.is_enabled(&buffer, buffer_position, cx) {
6801 return Some(false);
6802 }
6803 let buffer = buffer.read(cx);
6804 let Some(file) = buffer.file() else {
6805 return Some(true);
6806 };
6807 let settings = all_language_settings(Some(file), cx);
6808 Some(settings.edit_predictions_enabled_for_file(file, cx))
6809 })
6810 .unwrap_or(false)
6811 }
6812
6813 fn cycle_inline_completion(
6814 &mut self,
6815 direction: Direction,
6816 window: &mut Window,
6817 cx: &mut Context<Self>,
6818 ) -> Option<()> {
6819 let provider = self.edit_prediction_provider()?;
6820 let cursor = self.selections.newest_anchor().head();
6821 let (buffer, cursor_buffer_position) =
6822 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6823 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6824 return None;
6825 }
6826
6827 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6828 self.update_visible_inline_completion(window, cx);
6829
6830 Some(())
6831 }
6832
6833 pub fn show_inline_completion(
6834 &mut self,
6835 _: &ShowEditPrediction,
6836 window: &mut Window,
6837 cx: &mut Context<Self>,
6838 ) {
6839 if !self.has_active_inline_completion() {
6840 self.refresh_inline_completion(false, true, window, cx);
6841 return;
6842 }
6843
6844 self.update_visible_inline_completion(window, cx);
6845 }
6846
6847 pub fn display_cursor_names(
6848 &mut self,
6849 _: &DisplayCursorNames,
6850 window: &mut Window,
6851 cx: &mut Context<Self>,
6852 ) {
6853 self.show_cursor_names(window, cx);
6854 }
6855
6856 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6857 self.show_cursor_names = true;
6858 cx.notify();
6859 cx.spawn_in(window, async move |this, cx| {
6860 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6861 this.update(cx, |this, cx| {
6862 this.show_cursor_names = false;
6863 cx.notify()
6864 })
6865 .ok()
6866 })
6867 .detach();
6868 }
6869
6870 pub fn next_edit_prediction(
6871 &mut self,
6872 _: &NextEditPrediction,
6873 window: &mut Window,
6874 cx: &mut Context<Self>,
6875 ) {
6876 if self.has_active_inline_completion() {
6877 self.cycle_inline_completion(Direction::Next, window, cx);
6878 } else {
6879 let is_copilot_disabled = self
6880 .refresh_inline_completion(false, true, window, cx)
6881 .is_none();
6882 if is_copilot_disabled {
6883 cx.propagate();
6884 }
6885 }
6886 }
6887
6888 pub fn previous_edit_prediction(
6889 &mut self,
6890 _: &PreviousEditPrediction,
6891 window: &mut Window,
6892 cx: &mut Context<Self>,
6893 ) {
6894 if self.has_active_inline_completion() {
6895 self.cycle_inline_completion(Direction::Prev, window, cx);
6896 } else {
6897 let is_copilot_disabled = self
6898 .refresh_inline_completion(false, true, window, cx)
6899 .is_none();
6900 if is_copilot_disabled {
6901 cx.propagate();
6902 }
6903 }
6904 }
6905
6906 pub fn accept_edit_prediction(
6907 &mut self,
6908 _: &AcceptEditPrediction,
6909 window: &mut Window,
6910 cx: &mut Context<Self>,
6911 ) {
6912 if self.show_edit_predictions_in_menu() {
6913 self.hide_context_menu(window, cx);
6914 }
6915
6916 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6917 return;
6918 };
6919
6920 self.report_inline_completion_event(
6921 active_inline_completion.completion_id.clone(),
6922 true,
6923 cx,
6924 );
6925
6926 match &active_inline_completion.completion {
6927 InlineCompletion::Move { target, .. } => {
6928 let target = *target;
6929
6930 if let Some(position_map) = &self.last_position_map {
6931 if position_map
6932 .visible_row_range
6933 .contains(&target.to_display_point(&position_map.snapshot).row())
6934 || !self.edit_prediction_requires_modifier()
6935 {
6936 self.unfold_ranges(&[target..target], true, false, cx);
6937 // Note that this is also done in vim's handler of the Tab action.
6938 self.change_selections(
6939 Some(Autoscroll::newest()),
6940 window,
6941 cx,
6942 |selections| {
6943 selections.select_anchor_ranges([target..target]);
6944 },
6945 );
6946 self.clear_row_highlights::<EditPredictionPreview>();
6947
6948 self.edit_prediction_preview
6949 .set_previous_scroll_position(None);
6950 } else {
6951 self.edit_prediction_preview
6952 .set_previous_scroll_position(Some(
6953 position_map.snapshot.scroll_anchor,
6954 ));
6955
6956 self.highlight_rows::<EditPredictionPreview>(
6957 target..target,
6958 cx.theme().colors().editor_highlighted_line_background,
6959 RowHighlightOptions {
6960 autoscroll: true,
6961 ..Default::default()
6962 },
6963 cx,
6964 );
6965 self.request_autoscroll(Autoscroll::fit(), cx);
6966 }
6967 }
6968 }
6969 InlineCompletion::Edit { edits, .. } => {
6970 if let Some(provider) = self.edit_prediction_provider() {
6971 provider.accept(cx);
6972 }
6973
6974 // Store the transaction ID and selections before applying the edit
6975 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6976
6977 let snapshot = self.buffer.read(cx).snapshot(cx);
6978 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6979
6980 self.buffer.update(cx, |buffer, cx| {
6981 buffer.edit(edits.iter().cloned(), None, cx)
6982 });
6983
6984 self.change_selections(None, window, cx, |s| {
6985 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6986 });
6987
6988 let selections = self.selections.disjoint_anchors();
6989 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6990 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6991 if has_new_transaction {
6992 self.selection_history
6993 .insert_transaction(transaction_id_now, selections);
6994 }
6995 }
6996
6997 self.update_visible_inline_completion(window, cx);
6998 if self.active_inline_completion.is_none() {
6999 self.refresh_inline_completion(true, true, window, cx);
7000 }
7001
7002 cx.notify();
7003 }
7004 }
7005
7006 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7007 }
7008
7009 pub fn accept_partial_inline_completion(
7010 &mut self,
7011 _: &AcceptPartialEditPrediction,
7012 window: &mut Window,
7013 cx: &mut Context<Self>,
7014 ) {
7015 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7016 return;
7017 };
7018 if self.selections.count() != 1 {
7019 return;
7020 }
7021
7022 self.report_inline_completion_event(
7023 active_inline_completion.completion_id.clone(),
7024 true,
7025 cx,
7026 );
7027
7028 match &active_inline_completion.completion {
7029 InlineCompletion::Move { target, .. } => {
7030 let target = *target;
7031 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7032 selections.select_anchor_ranges([target..target]);
7033 });
7034 }
7035 InlineCompletion::Edit { edits, .. } => {
7036 // Find an insertion that starts at the cursor position.
7037 let snapshot = self.buffer.read(cx).snapshot(cx);
7038 let cursor_offset = self.selections.newest::<usize>(cx).head();
7039 let insertion = edits.iter().find_map(|(range, text)| {
7040 let range = range.to_offset(&snapshot);
7041 if range.is_empty() && range.start == cursor_offset {
7042 Some(text)
7043 } else {
7044 None
7045 }
7046 });
7047
7048 if let Some(text) = insertion {
7049 let mut partial_completion = text
7050 .chars()
7051 .by_ref()
7052 .take_while(|c| c.is_alphabetic())
7053 .collect::<String>();
7054 if partial_completion.is_empty() {
7055 partial_completion = text
7056 .chars()
7057 .by_ref()
7058 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7059 .collect::<String>();
7060 }
7061
7062 cx.emit(EditorEvent::InputHandled {
7063 utf16_range_to_replace: None,
7064 text: partial_completion.clone().into(),
7065 });
7066
7067 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7068
7069 self.refresh_inline_completion(true, true, window, cx);
7070 cx.notify();
7071 } else {
7072 self.accept_edit_prediction(&Default::default(), window, cx);
7073 }
7074 }
7075 }
7076 }
7077
7078 fn discard_inline_completion(
7079 &mut self,
7080 should_report_inline_completion_event: bool,
7081 cx: &mut Context<Self>,
7082 ) -> bool {
7083 if should_report_inline_completion_event {
7084 let completion_id = self
7085 .active_inline_completion
7086 .as_ref()
7087 .and_then(|active_completion| active_completion.completion_id.clone());
7088
7089 self.report_inline_completion_event(completion_id, false, cx);
7090 }
7091
7092 if let Some(provider) = self.edit_prediction_provider() {
7093 provider.discard(cx);
7094 }
7095
7096 self.take_active_inline_completion(cx)
7097 }
7098
7099 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7100 let Some(provider) = self.edit_prediction_provider() else {
7101 return;
7102 };
7103
7104 let Some((_, buffer, _)) = self
7105 .buffer
7106 .read(cx)
7107 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7108 else {
7109 return;
7110 };
7111
7112 let extension = buffer
7113 .read(cx)
7114 .file()
7115 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7116
7117 let event_type = match accepted {
7118 true => "Edit Prediction Accepted",
7119 false => "Edit Prediction Discarded",
7120 };
7121 telemetry::event!(
7122 event_type,
7123 provider = provider.name(),
7124 prediction_id = id,
7125 suggestion_accepted = accepted,
7126 file_extension = extension,
7127 );
7128 }
7129
7130 pub fn has_active_inline_completion(&self) -> bool {
7131 self.active_inline_completion.is_some()
7132 }
7133
7134 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7135 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7136 return false;
7137 };
7138
7139 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7140 self.clear_highlights::<InlineCompletionHighlight>(cx);
7141 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7142 true
7143 }
7144
7145 /// Returns true when we're displaying the edit prediction popover below the cursor
7146 /// like we are not previewing and the LSP autocomplete menu is visible
7147 /// or we are in `when_holding_modifier` mode.
7148 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7149 if self.edit_prediction_preview_is_active()
7150 || !self.show_edit_predictions_in_menu()
7151 || !self.edit_predictions_enabled()
7152 {
7153 return false;
7154 }
7155
7156 if self.has_visible_completions_menu() {
7157 return true;
7158 }
7159
7160 has_completion && self.edit_prediction_requires_modifier()
7161 }
7162
7163 fn handle_modifiers_changed(
7164 &mut self,
7165 modifiers: Modifiers,
7166 position_map: &PositionMap,
7167 window: &mut Window,
7168 cx: &mut Context<Self>,
7169 ) {
7170 if self.show_edit_predictions_in_menu() {
7171 self.update_edit_prediction_preview(&modifiers, window, cx);
7172 }
7173
7174 self.update_selection_mode(&modifiers, position_map, window, cx);
7175
7176 let mouse_position = window.mouse_position();
7177 if !position_map.text_hitbox.is_hovered(window) {
7178 return;
7179 }
7180
7181 self.update_hovered_link(
7182 position_map.point_for_position(mouse_position),
7183 &position_map.snapshot,
7184 modifiers,
7185 window,
7186 cx,
7187 )
7188 }
7189
7190 fn multi_cursor_modifier(
7191 cursor_event: bool,
7192 modifiers: &Modifiers,
7193 cx: &mut Context<Self>,
7194 ) -> bool {
7195 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7196 if cursor_event {
7197 match multi_cursor_setting {
7198 MultiCursorModifier::Alt => modifiers.alt,
7199 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7200 }
7201 } else {
7202 match multi_cursor_setting {
7203 MultiCursorModifier::Alt => modifiers.secondary(),
7204 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7205 }
7206 }
7207 }
7208
7209 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7210 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7211 }
7212
7213 fn update_selection_mode(
7214 &mut self,
7215 modifiers: &Modifiers,
7216 position_map: &PositionMap,
7217 window: &mut Window,
7218 cx: &mut Context<Self>,
7219 ) {
7220 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7221 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7222 || self.selections.pending.is_none()
7223 {
7224 return;
7225 }
7226
7227 let mouse_position = window.mouse_position();
7228 let point_for_position = position_map.point_for_position(mouse_position);
7229 let position = point_for_position.previous_valid;
7230
7231 self.select(
7232 SelectPhase::BeginColumnar {
7233 position,
7234 reset: false,
7235 goal_column: point_for_position.exact_unclipped.column(),
7236 },
7237 window,
7238 cx,
7239 );
7240 }
7241
7242 fn update_edit_prediction_preview(
7243 &mut self,
7244 modifiers: &Modifiers,
7245 window: &mut Window,
7246 cx: &mut Context<Self>,
7247 ) {
7248 let mut modifiers_held = false;
7249 if let Some(accept_keystroke) = self
7250 .accept_edit_prediction_keybind(false, window, cx)
7251 .keystroke()
7252 {
7253 modifiers_held = modifiers_held
7254 || (&accept_keystroke.modifiers == modifiers
7255 && accept_keystroke.modifiers.modified());
7256 };
7257 if let Some(accept_partial_keystroke) = self
7258 .accept_edit_prediction_keybind(true, window, cx)
7259 .keystroke()
7260 {
7261 modifiers_held = modifiers_held
7262 || (&accept_partial_keystroke.modifiers == modifiers
7263 && accept_partial_keystroke.modifiers.modified());
7264 }
7265
7266 if modifiers_held {
7267 if matches!(
7268 self.edit_prediction_preview,
7269 EditPredictionPreview::Inactive { .. }
7270 ) {
7271 self.edit_prediction_preview = EditPredictionPreview::Active {
7272 previous_scroll_position: None,
7273 since: Instant::now(),
7274 };
7275
7276 self.update_visible_inline_completion(window, cx);
7277 cx.notify();
7278 }
7279 } else if let EditPredictionPreview::Active {
7280 previous_scroll_position,
7281 since,
7282 } = self.edit_prediction_preview
7283 {
7284 if let (Some(previous_scroll_position), Some(position_map)) =
7285 (previous_scroll_position, self.last_position_map.as_ref())
7286 {
7287 self.set_scroll_position(
7288 previous_scroll_position
7289 .scroll_position(&position_map.snapshot.display_snapshot),
7290 window,
7291 cx,
7292 );
7293 }
7294
7295 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7296 released_too_fast: since.elapsed() < Duration::from_millis(200),
7297 };
7298 self.clear_row_highlights::<EditPredictionPreview>();
7299 self.update_visible_inline_completion(window, cx);
7300 cx.notify();
7301 }
7302 }
7303
7304 fn update_visible_inline_completion(
7305 &mut self,
7306 _window: &mut Window,
7307 cx: &mut Context<Self>,
7308 ) -> Option<()> {
7309 let selection = self.selections.newest_anchor();
7310 let cursor = selection.head();
7311 let multibuffer = self.buffer.read(cx).snapshot(cx);
7312 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7313 let excerpt_id = cursor.excerpt_id;
7314
7315 let show_in_menu = self.show_edit_predictions_in_menu();
7316 let completions_menu_has_precedence = !show_in_menu
7317 && (self.context_menu.borrow().is_some()
7318 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7319
7320 if completions_menu_has_precedence
7321 || !offset_selection.is_empty()
7322 || self
7323 .active_inline_completion
7324 .as_ref()
7325 .map_or(false, |completion| {
7326 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7327 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7328 !invalidation_range.contains(&offset_selection.head())
7329 })
7330 {
7331 self.discard_inline_completion(false, cx);
7332 return None;
7333 }
7334
7335 self.take_active_inline_completion(cx);
7336 let Some(provider) = self.edit_prediction_provider() else {
7337 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7338 return None;
7339 };
7340
7341 let (buffer, cursor_buffer_position) =
7342 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7343
7344 self.edit_prediction_settings =
7345 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7346
7347 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7348
7349 if self.edit_prediction_indent_conflict {
7350 let cursor_point = cursor.to_point(&multibuffer);
7351
7352 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7353
7354 if let Some((_, indent)) = indents.iter().next() {
7355 if indent.len == cursor_point.column {
7356 self.edit_prediction_indent_conflict = false;
7357 }
7358 }
7359 }
7360
7361 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7362 let edits = inline_completion
7363 .edits
7364 .into_iter()
7365 .flat_map(|(range, new_text)| {
7366 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7367 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7368 Some((start..end, new_text))
7369 })
7370 .collect::<Vec<_>>();
7371 if edits.is_empty() {
7372 return None;
7373 }
7374
7375 let first_edit_start = edits.first().unwrap().0.start;
7376 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7377 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7378
7379 let last_edit_end = edits.last().unwrap().0.end;
7380 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7381 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7382
7383 let cursor_row = cursor.to_point(&multibuffer).row;
7384
7385 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7386
7387 let mut inlay_ids = Vec::new();
7388 let invalidation_row_range;
7389 let move_invalidation_row_range = if cursor_row < edit_start_row {
7390 Some(cursor_row..edit_end_row)
7391 } else if cursor_row > edit_end_row {
7392 Some(edit_start_row..cursor_row)
7393 } else {
7394 None
7395 };
7396 let is_move =
7397 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7398 let completion = if is_move {
7399 invalidation_row_range =
7400 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7401 let target = first_edit_start;
7402 InlineCompletion::Move { target, snapshot }
7403 } else {
7404 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7405 && !self.inline_completions_hidden_for_vim_mode;
7406
7407 if show_completions_in_buffer {
7408 if edits
7409 .iter()
7410 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7411 {
7412 let mut inlays = Vec::new();
7413 for (range, new_text) in &edits {
7414 let inlay = Inlay::inline_completion(
7415 post_inc(&mut self.next_inlay_id),
7416 range.start,
7417 new_text.as_str(),
7418 );
7419 inlay_ids.push(inlay.id);
7420 inlays.push(inlay);
7421 }
7422
7423 self.splice_inlays(&[], inlays, cx);
7424 } else {
7425 let background_color = cx.theme().status().deleted_background;
7426 self.highlight_text::<InlineCompletionHighlight>(
7427 edits.iter().map(|(range, _)| range.clone()).collect(),
7428 HighlightStyle {
7429 background_color: Some(background_color),
7430 ..Default::default()
7431 },
7432 cx,
7433 );
7434 }
7435 }
7436
7437 invalidation_row_range = edit_start_row..edit_end_row;
7438
7439 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7440 if provider.show_tab_accept_marker() {
7441 EditDisplayMode::TabAccept
7442 } else {
7443 EditDisplayMode::Inline
7444 }
7445 } else {
7446 EditDisplayMode::DiffPopover
7447 };
7448
7449 InlineCompletion::Edit {
7450 edits,
7451 edit_preview: inline_completion.edit_preview,
7452 display_mode,
7453 snapshot,
7454 }
7455 };
7456
7457 let invalidation_range = multibuffer
7458 .anchor_before(Point::new(invalidation_row_range.start, 0))
7459 ..multibuffer.anchor_after(Point::new(
7460 invalidation_row_range.end,
7461 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7462 ));
7463
7464 self.stale_inline_completion_in_menu = None;
7465 self.active_inline_completion = Some(InlineCompletionState {
7466 inlay_ids,
7467 completion,
7468 completion_id: inline_completion.id,
7469 invalidation_range,
7470 });
7471
7472 cx.notify();
7473
7474 Some(())
7475 }
7476
7477 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7478 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7479 }
7480
7481 fn clear_tasks(&mut self) {
7482 self.tasks.clear()
7483 }
7484
7485 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7486 if self.tasks.insert(key, value).is_some() {
7487 // This case should hopefully be rare, but just in case...
7488 log::error!(
7489 "multiple different run targets found on a single line, only the last target will be rendered"
7490 )
7491 }
7492 }
7493
7494 /// Get all display points of breakpoints that will be rendered within editor
7495 ///
7496 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7497 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7498 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7499 fn active_breakpoints(
7500 &self,
7501 range: Range<DisplayRow>,
7502 window: &mut Window,
7503 cx: &mut Context<Self>,
7504 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7505 let mut breakpoint_display_points = HashMap::default();
7506
7507 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7508 return breakpoint_display_points;
7509 };
7510
7511 let snapshot = self.snapshot(window, cx);
7512
7513 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7514 let Some(project) = self.project.as_ref() else {
7515 return breakpoint_display_points;
7516 };
7517
7518 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7519 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7520
7521 for (buffer_snapshot, range, excerpt_id) in
7522 multi_buffer_snapshot.range_to_buffer_ranges(range)
7523 {
7524 let Some(buffer) = project
7525 .read(cx)
7526 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7527 else {
7528 continue;
7529 };
7530 let breakpoints = breakpoint_store.read(cx).breakpoints(
7531 &buffer,
7532 Some(
7533 buffer_snapshot.anchor_before(range.start)
7534 ..buffer_snapshot.anchor_after(range.end),
7535 ),
7536 buffer_snapshot,
7537 cx,
7538 );
7539 for (breakpoint, state) in breakpoints {
7540 let multi_buffer_anchor =
7541 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7542 let position = multi_buffer_anchor
7543 .to_point(&multi_buffer_snapshot)
7544 .to_display_point(&snapshot);
7545
7546 breakpoint_display_points.insert(
7547 position.row(),
7548 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7549 );
7550 }
7551 }
7552
7553 breakpoint_display_points
7554 }
7555
7556 fn breakpoint_context_menu(
7557 &self,
7558 anchor: Anchor,
7559 window: &mut Window,
7560 cx: &mut Context<Self>,
7561 ) -> Entity<ui::ContextMenu> {
7562 let weak_editor = cx.weak_entity();
7563 let focus_handle = self.focus_handle(cx);
7564
7565 let row = self
7566 .buffer
7567 .read(cx)
7568 .snapshot(cx)
7569 .summary_for_anchor::<Point>(&anchor)
7570 .row;
7571
7572 let breakpoint = self
7573 .breakpoint_at_row(row, window, cx)
7574 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7575
7576 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7577 "Edit Log Breakpoint"
7578 } else {
7579 "Set Log Breakpoint"
7580 };
7581
7582 let condition_breakpoint_msg = if breakpoint
7583 .as_ref()
7584 .is_some_and(|bp| bp.1.condition.is_some())
7585 {
7586 "Edit Condition Breakpoint"
7587 } else {
7588 "Set Condition Breakpoint"
7589 };
7590
7591 let hit_condition_breakpoint_msg = if breakpoint
7592 .as_ref()
7593 .is_some_and(|bp| bp.1.hit_condition.is_some())
7594 {
7595 "Edit Hit Condition Breakpoint"
7596 } else {
7597 "Set Hit Condition Breakpoint"
7598 };
7599
7600 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7601 "Unset Breakpoint"
7602 } else {
7603 "Set Breakpoint"
7604 };
7605
7606 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7607
7608 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7609 BreakpointState::Enabled => Some("Disable"),
7610 BreakpointState::Disabled => Some("Enable"),
7611 });
7612
7613 let (anchor, breakpoint) =
7614 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7615
7616 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7617 menu.on_blur_subscription(Subscription::new(|| {}))
7618 .context(focus_handle)
7619 .when(run_to_cursor, |this| {
7620 let weak_editor = weak_editor.clone();
7621 this.entry("Run to cursor", None, move |window, cx| {
7622 weak_editor
7623 .update(cx, |editor, cx| {
7624 editor.change_selections(None, window, cx, |s| {
7625 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7626 });
7627 })
7628 .ok();
7629
7630 window.dispatch_action(Box::new(RunToCursor), cx);
7631 })
7632 .separator()
7633 })
7634 .when_some(toggle_state_msg, |this, msg| {
7635 this.entry(msg, None, {
7636 let weak_editor = weak_editor.clone();
7637 let breakpoint = breakpoint.clone();
7638 move |_window, cx| {
7639 weak_editor
7640 .update(cx, |this, cx| {
7641 this.edit_breakpoint_at_anchor(
7642 anchor,
7643 breakpoint.as_ref().clone(),
7644 BreakpointEditAction::InvertState,
7645 cx,
7646 );
7647 })
7648 .log_err();
7649 }
7650 })
7651 })
7652 .entry(set_breakpoint_msg, None, {
7653 let weak_editor = weak_editor.clone();
7654 let breakpoint = breakpoint.clone();
7655 move |_window, cx| {
7656 weak_editor
7657 .update(cx, |this, cx| {
7658 this.edit_breakpoint_at_anchor(
7659 anchor,
7660 breakpoint.as_ref().clone(),
7661 BreakpointEditAction::Toggle,
7662 cx,
7663 );
7664 })
7665 .log_err();
7666 }
7667 })
7668 .entry(log_breakpoint_msg, None, {
7669 let breakpoint = breakpoint.clone();
7670 let weak_editor = weak_editor.clone();
7671 move |window, cx| {
7672 weak_editor
7673 .update(cx, |this, cx| {
7674 this.add_edit_breakpoint_block(
7675 anchor,
7676 breakpoint.as_ref(),
7677 BreakpointPromptEditAction::Log,
7678 window,
7679 cx,
7680 );
7681 })
7682 .log_err();
7683 }
7684 })
7685 .entry(condition_breakpoint_msg, None, {
7686 let breakpoint = breakpoint.clone();
7687 let weak_editor = weak_editor.clone();
7688 move |window, cx| {
7689 weak_editor
7690 .update(cx, |this, cx| {
7691 this.add_edit_breakpoint_block(
7692 anchor,
7693 breakpoint.as_ref(),
7694 BreakpointPromptEditAction::Condition,
7695 window,
7696 cx,
7697 );
7698 })
7699 .log_err();
7700 }
7701 })
7702 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7703 weak_editor
7704 .update(cx, |this, cx| {
7705 this.add_edit_breakpoint_block(
7706 anchor,
7707 breakpoint.as_ref(),
7708 BreakpointPromptEditAction::HitCondition,
7709 window,
7710 cx,
7711 );
7712 })
7713 .log_err();
7714 })
7715 })
7716 }
7717
7718 fn render_breakpoint(
7719 &self,
7720 position: Anchor,
7721 row: DisplayRow,
7722 breakpoint: &Breakpoint,
7723 state: Option<BreakpointSessionState>,
7724 cx: &mut Context<Self>,
7725 ) -> IconButton {
7726 let is_rejected = state.is_some_and(|s| !s.verified);
7727 // Is it a breakpoint that shows up when hovering over gutter?
7728 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7729 (false, false),
7730 |PhantomBreakpointIndicator {
7731 is_active,
7732 display_row,
7733 collides_with_existing_breakpoint,
7734 }| {
7735 (
7736 is_active && display_row == row,
7737 collides_with_existing_breakpoint,
7738 )
7739 },
7740 );
7741
7742 let (color, icon) = {
7743 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7744 (false, false) => ui::IconName::DebugBreakpoint,
7745 (true, false) => ui::IconName::DebugLogBreakpoint,
7746 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7747 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7748 };
7749
7750 let color = if is_phantom {
7751 Color::Hint
7752 } else if is_rejected {
7753 Color::Disabled
7754 } else {
7755 Color::Debugger
7756 };
7757
7758 (color, icon)
7759 };
7760
7761 let breakpoint = Arc::from(breakpoint.clone());
7762
7763 let alt_as_text = gpui::Keystroke {
7764 modifiers: Modifiers::secondary_key(),
7765 ..Default::default()
7766 };
7767 let primary_action_text = if breakpoint.is_disabled() {
7768 "Enable breakpoint"
7769 } else if is_phantom && !collides_with_existing {
7770 "Set breakpoint"
7771 } else {
7772 "Unset breakpoint"
7773 };
7774 let focus_handle = self.focus_handle.clone();
7775
7776 let meta = if is_rejected {
7777 SharedString::from("No executable code is associated with this line.")
7778 } else if collides_with_existing && !breakpoint.is_disabled() {
7779 SharedString::from(format!(
7780 "{alt_as_text}-click to disable,\nright-click for more options."
7781 ))
7782 } else {
7783 SharedString::from("Right-click for more options.")
7784 };
7785 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7786 .icon_size(IconSize::XSmall)
7787 .size(ui::ButtonSize::None)
7788 .when(is_rejected, |this| {
7789 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7790 })
7791 .icon_color(color)
7792 .style(ButtonStyle::Transparent)
7793 .on_click(cx.listener({
7794 let breakpoint = breakpoint.clone();
7795
7796 move |editor, event: &ClickEvent, window, cx| {
7797 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7798 BreakpointEditAction::InvertState
7799 } else {
7800 BreakpointEditAction::Toggle
7801 };
7802
7803 window.focus(&editor.focus_handle(cx));
7804 editor.edit_breakpoint_at_anchor(
7805 position,
7806 breakpoint.as_ref().clone(),
7807 edit_action,
7808 cx,
7809 );
7810 }
7811 }))
7812 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7813 editor.set_breakpoint_context_menu(
7814 row,
7815 Some(position),
7816 event.down.position,
7817 window,
7818 cx,
7819 );
7820 }))
7821 .tooltip(move |window, cx| {
7822 Tooltip::with_meta_in(
7823 primary_action_text,
7824 Some(&ToggleBreakpoint),
7825 meta.clone(),
7826 &focus_handle,
7827 window,
7828 cx,
7829 )
7830 })
7831 }
7832
7833 fn build_tasks_context(
7834 project: &Entity<Project>,
7835 buffer: &Entity<Buffer>,
7836 buffer_row: u32,
7837 tasks: &Arc<RunnableTasks>,
7838 cx: &mut Context<Self>,
7839 ) -> Task<Option<task::TaskContext>> {
7840 let position = Point::new(buffer_row, tasks.column);
7841 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7842 let location = Location {
7843 buffer: buffer.clone(),
7844 range: range_start..range_start,
7845 };
7846 // Fill in the environmental variables from the tree-sitter captures
7847 let mut captured_task_variables = TaskVariables::default();
7848 for (capture_name, value) in tasks.extra_variables.clone() {
7849 captured_task_variables.insert(
7850 task::VariableName::Custom(capture_name.into()),
7851 value.clone(),
7852 );
7853 }
7854 project.update(cx, |project, cx| {
7855 project.task_store().update(cx, |task_store, cx| {
7856 task_store.task_context_for_location(captured_task_variables, location, cx)
7857 })
7858 })
7859 }
7860
7861 pub fn spawn_nearest_task(
7862 &mut self,
7863 action: &SpawnNearestTask,
7864 window: &mut Window,
7865 cx: &mut Context<Self>,
7866 ) {
7867 let Some((workspace, _)) = self.workspace.clone() else {
7868 return;
7869 };
7870 let Some(project) = self.project.clone() else {
7871 return;
7872 };
7873
7874 // Try to find a closest, enclosing node using tree-sitter that has a
7875 // task
7876 let Some((buffer, buffer_row, tasks)) = self
7877 .find_enclosing_node_task(cx)
7878 // Or find the task that's closest in row-distance.
7879 .or_else(|| self.find_closest_task(cx))
7880 else {
7881 return;
7882 };
7883
7884 let reveal_strategy = action.reveal;
7885 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7886 cx.spawn_in(window, async move |_, cx| {
7887 let context = task_context.await?;
7888 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7889
7890 let resolved = &mut resolved_task.resolved;
7891 resolved.reveal = reveal_strategy;
7892
7893 workspace
7894 .update_in(cx, |workspace, window, cx| {
7895 workspace.schedule_resolved_task(
7896 task_source_kind,
7897 resolved_task,
7898 false,
7899 window,
7900 cx,
7901 );
7902 })
7903 .ok()
7904 })
7905 .detach();
7906 }
7907
7908 fn find_closest_task(
7909 &mut self,
7910 cx: &mut Context<Self>,
7911 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7912 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7913
7914 let ((buffer_id, row), tasks) = self
7915 .tasks
7916 .iter()
7917 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7918
7919 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7920 let tasks = Arc::new(tasks.to_owned());
7921 Some((buffer, *row, tasks))
7922 }
7923
7924 fn find_enclosing_node_task(
7925 &mut self,
7926 cx: &mut Context<Self>,
7927 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7928 let snapshot = self.buffer.read(cx).snapshot(cx);
7929 let offset = self.selections.newest::<usize>(cx).head();
7930 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7931 let buffer_id = excerpt.buffer().remote_id();
7932
7933 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7934 let mut cursor = layer.node().walk();
7935
7936 while cursor.goto_first_child_for_byte(offset).is_some() {
7937 if cursor.node().end_byte() == offset {
7938 cursor.goto_next_sibling();
7939 }
7940 }
7941
7942 // Ascend to the smallest ancestor that contains the range and has a task.
7943 loop {
7944 let node = cursor.node();
7945 let node_range = node.byte_range();
7946 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7947
7948 // Check if this node contains our offset
7949 if node_range.start <= offset && node_range.end >= offset {
7950 // If it contains offset, check for task
7951 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7952 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7953 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7954 }
7955 }
7956
7957 if !cursor.goto_parent() {
7958 break;
7959 }
7960 }
7961 None
7962 }
7963
7964 fn render_run_indicator(
7965 &self,
7966 _style: &EditorStyle,
7967 is_active: bool,
7968 row: DisplayRow,
7969 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7970 cx: &mut Context<Self>,
7971 ) -> IconButton {
7972 let color = Color::Muted;
7973 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7974
7975 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7976 .shape(ui::IconButtonShape::Square)
7977 .icon_size(IconSize::XSmall)
7978 .icon_color(color)
7979 .toggle_state(is_active)
7980 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7981 let quick_launch = e.down.button == MouseButton::Left;
7982 window.focus(&editor.focus_handle(cx));
7983 editor.toggle_code_actions(
7984 &ToggleCodeActions {
7985 deployed_from: Some(CodeActionSource::RunMenu(row)),
7986 quick_launch,
7987 },
7988 window,
7989 cx,
7990 );
7991 }))
7992 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7993 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7994 }))
7995 }
7996
7997 pub fn context_menu_visible(&self) -> bool {
7998 !self.edit_prediction_preview_is_active()
7999 && self
8000 .context_menu
8001 .borrow()
8002 .as_ref()
8003 .map_or(false, |menu| menu.visible())
8004 }
8005
8006 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8007 self.context_menu
8008 .borrow()
8009 .as_ref()
8010 .map(|menu| menu.origin())
8011 }
8012
8013 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8014 self.context_menu_options = Some(options);
8015 }
8016
8017 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8018 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8019
8020 fn render_edit_prediction_popover(
8021 &mut self,
8022 text_bounds: &Bounds<Pixels>,
8023 content_origin: gpui::Point<Pixels>,
8024 right_margin: Pixels,
8025 editor_snapshot: &EditorSnapshot,
8026 visible_row_range: Range<DisplayRow>,
8027 scroll_top: f32,
8028 scroll_bottom: f32,
8029 line_layouts: &[LineWithInvisibles],
8030 line_height: Pixels,
8031 scroll_pixel_position: gpui::Point<Pixels>,
8032 newest_selection_head: Option<DisplayPoint>,
8033 editor_width: Pixels,
8034 style: &EditorStyle,
8035 window: &mut Window,
8036 cx: &mut App,
8037 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8038 if self.mode().is_minimap() {
8039 return None;
8040 }
8041 let active_inline_completion = self.active_inline_completion.as_ref()?;
8042
8043 if self.edit_prediction_visible_in_cursor_popover(true) {
8044 return None;
8045 }
8046
8047 match &active_inline_completion.completion {
8048 InlineCompletion::Move { target, .. } => {
8049 let target_display_point = target.to_display_point(editor_snapshot);
8050
8051 if self.edit_prediction_requires_modifier() {
8052 if !self.edit_prediction_preview_is_active() {
8053 return None;
8054 }
8055
8056 self.render_edit_prediction_modifier_jump_popover(
8057 text_bounds,
8058 content_origin,
8059 visible_row_range,
8060 line_layouts,
8061 line_height,
8062 scroll_pixel_position,
8063 newest_selection_head,
8064 target_display_point,
8065 window,
8066 cx,
8067 )
8068 } else {
8069 self.render_edit_prediction_eager_jump_popover(
8070 text_bounds,
8071 content_origin,
8072 editor_snapshot,
8073 visible_row_range,
8074 scroll_top,
8075 scroll_bottom,
8076 line_height,
8077 scroll_pixel_position,
8078 target_display_point,
8079 editor_width,
8080 window,
8081 cx,
8082 )
8083 }
8084 }
8085 InlineCompletion::Edit {
8086 display_mode: EditDisplayMode::Inline,
8087 ..
8088 } => None,
8089 InlineCompletion::Edit {
8090 display_mode: EditDisplayMode::TabAccept,
8091 edits,
8092 ..
8093 } => {
8094 let range = &edits.first()?.0;
8095 let target_display_point = range.end.to_display_point(editor_snapshot);
8096
8097 self.render_edit_prediction_end_of_line_popover(
8098 "Accept",
8099 editor_snapshot,
8100 visible_row_range,
8101 target_display_point,
8102 line_height,
8103 scroll_pixel_position,
8104 content_origin,
8105 editor_width,
8106 window,
8107 cx,
8108 )
8109 }
8110 InlineCompletion::Edit {
8111 edits,
8112 edit_preview,
8113 display_mode: EditDisplayMode::DiffPopover,
8114 snapshot,
8115 } => self.render_edit_prediction_diff_popover(
8116 text_bounds,
8117 content_origin,
8118 right_margin,
8119 editor_snapshot,
8120 visible_row_range,
8121 line_layouts,
8122 line_height,
8123 scroll_pixel_position,
8124 newest_selection_head,
8125 editor_width,
8126 style,
8127 edits,
8128 edit_preview,
8129 snapshot,
8130 window,
8131 cx,
8132 ),
8133 }
8134 }
8135
8136 fn render_edit_prediction_modifier_jump_popover(
8137 &mut self,
8138 text_bounds: &Bounds<Pixels>,
8139 content_origin: gpui::Point<Pixels>,
8140 visible_row_range: Range<DisplayRow>,
8141 line_layouts: &[LineWithInvisibles],
8142 line_height: Pixels,
8143 scroll_pixel_position: gpui::Point<Pixels>,
8144 newest_selection_head: Option<DisplayPoint>,
8145 target_display_point: DisplayPoint,
8146 window: &mut Window,
8147 cx: &mut App,
8148 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8149 let scrolled_content_origin =
8150 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8151
8152 const SCROLL_PADDING_Y: Pixels = px(12.);
8153
8154 if target_display_point.row() < visible_row_range.start {
8155 return self.render_edit_prediction_scroll_popover(
8156 |_| SCROLL_PADDING_Y,
8157 IconName::ArrowUp,
8158 visible_row_range,
8159 line_layouts,
8160 newest_selection_head,
8161 scrolled_content_origin,
8162 window,
8163 cx,
8164 );
8165 } else if target_display_point.row() >= visible_row_range.end {
8166 return self.render_edit_prediction_scroll_popover(
8167 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8168 IconName::ArrowDown,
8169 visible_row_range,
8170 line_layouts,
8171 newest_selection_head,
8172 scrolled_content_origin,
8173 window,
8174 cx,
8175 );
8176 }
8177
8178 const POLE_WIDTH: Pixels = px(2.);
8179
8180 let line_layout =
8181 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8182 let target_column = target_display_point.column() as usize;
8183
8184 let target_x = line_layout.x_for_index(target_column);
8185 let target_y =
8186 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8187
8188 let flag_on_right = target_x < text_bounds.size.width / 2.;
8189
8190 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8191 border_color.l += 0.001;
8192
8193 let mut element = v_flex()
8194 .items_end()
8195 .when(flag_on_right, |el| el.items_start())
8196 .child(if flag_on_right {
8197 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8198 .rounded_bl(px(0.))
8199 .rounded_tl(px(0.))
8200 .border_l_2()
8201 .border_color(border_color)
8202 } else {
8203 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8204 .rounded_br(px(0.))
8205 .rounded_tr(px(0.))
8206 .border_r_2()
8207 .border_color(border_color)
8208 })
8209 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8210 .into_any();
8211
8212 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8213
8214 let mut origin = scrolled_content_origin + point(target_x, target_y)
8215 - point(
8216 if flag_on_right {
8217 POLE_WIDTH
8218 } else {
8219 size.width - POLE_WIDTH
8220 },
8221 size.height - line_height,
8222 );
8223
8224 origin.x = origin.x.max(content_origin.x);
8225
8226 element.prepaint_at(origin, window, cx);
8227
8228 Some((element, origin))
8229 }
8230
8231 fn render_edit_prediction_scroll_popover(
8232 &mut self,
8233 to_y: impl Fn(Size<Pixels>) -> Pixels,
8234 scroll_icon: IconName,
8235 visible_row_range: Range<DisplayRow>,
8236 line_layouts: &[LineWithInvisibles],
8237 newest_selection_head: Option<DisplayPoint>,
8238 scrolled_content_origin: gpui::Point<Pixels>,
8239 window: &mut Window,
8240 cx: &mut App,
8241 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8242 let mut element = self
8243 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8244 .into_any();
8245
8246 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8247
8248 let cursor = newest_selection_head?;
8249 let cursor_row_layout =
8250 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8251 let cursor_column = cursor.column() as usize;
8252
8253 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8254
8255 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8256
8257 element.prepaint_at(origin, window, cx);
8258 Some((element, origin))
8259 }
8260
8261 fn render_edit_prediction_eager_jump_popover(
8262 &mut self,
8263 text_bounds: &Bounds<Pixels>,
8264 content_origin: gpui::Point<Pixels>,
8265 editor_snapshot: &EditorSnapshot,
8266 visible_row_range: Range<DisplayRow>,
8267 scroll_top: f32,
8268 scroll_bottom: f32,
8269 line_height: Pixels,
8270 scroll_pixel_position: gpui::Point<Pixels>,
8271 target_display_point: DisplayPoint,
8272 editor_width: Pixels,
8273 window: &mut Window,
8274 cx: &mut App,
8275 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8276 if target_display_point.row().as_f32() < scroll_top {
8277 let mut element = self
8278 .render_edit_prediction_line_popover(
8279 "Jump to Edit",
8280 Some(IconName::ArrowUp),
8281 window,
8282 cx,
8283 )?
8284 .into_any();
8285
8286 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8287 let offset = point(
8288 (text_bounds.size.width - size.width) / 2.,
8289 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8290 );
8291
8292 let origin = text_bounds.origin + offset;
8293 element.prepaint_at(origin, window, cx);
8294 Some((element, origin))
8295 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8296 let mut element = self
8297 .render_edit_prediction_line_popover(
8298 "Jump to Edit",
8299 Some(IconName::ArrowDown),
8300 window,
8301 cx,
8302 )?
8303 .into_any();
8304
8305 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8306 let offset = point(
8307 (text_bounds.size.width - size.width) / 2.,
8308 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8309 );
8310
8311 let origin = text_bounds.origin + offset;
8312 element.prepaint_at(origin, window, cx);
8313 Some((element, origin))
8314 } else {
8315 self.render_edit_prediction_end_of_line_popover(
8316 "Jump to Edit",
8317 editor_snapshot,
8318 visible_row_range,
8319 target_display_point,
8320 line_height,
8321 scroll_pixel_position,
8322 content_origin,
8323 editor_width,
8324 window,
8325 cx,
8326 )
8327 }
8328 }
8329
8330 fn render_edit_prediction_end_of_line_popover(
8331 self: &mut Editor,
8332 label: &'static str,
8333 editor_snapshot: &EditorSnapshot,
8334 visible_row_range: Range<DisplayRow>,
8335 target_display_point: DisplayPoint,
8336 line_height: Pixels,
8337 scroll_pixel_position: gpui::Point<Pixels>,
8338 content_origin: gpui::Point<Pixels>,
8339 editor_width: Pixels,
8340 window: &mut Window,
8341 cx: &mut App,
8342 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8343 let target_line_end = DisplayPoint::new(
8344 target_display_point.row(),
8345 editor_snapshot.line_len(target_display_point.row()),
8346 );
8347
8348 let mut element = self
8349 .render_edit_prediction_line_popover(label, None, window, cx)?
8350 .into_any();
8351
8352 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8353
8354 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8355
8356 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8357 let mut origin = start_point
8358 + line_origin
8359 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8360 origin.x = origin.x.max(content_origin.x);
8361
8362 let max_x = content_origin.x + editor_width - size.width;
8363
8364 if origin.x > max_x {
8365 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8366
8367 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8368 origin.y += offset;
8369 IconName::ArrowUp
8370 } else {
8371 origin.y -= offset;
8372 IconName::ArrowDown
8373 };
8374
8375 element = self
8376 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8377 .into_any();
8378
8379 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8380
8381 origin.x = content_origin.x + editor_width - size.width - px(2.);
8382 }
8383
8384 element.prepaint_at(origin, window, cx);
8385 Some((element, origin))
8386 }
8387
8388 fn render_edit_prediction_diff_popover(
8389 self: &Editor,
8390 text_bounds: &Bounds<Pixels>,
8391 content_origin: gpui::Point<Pixels>,
8392 right_margin: Pixels,
8393 editor_snapshot: &EditorSnapshot,
8394 visible_row_range: Range<DisplayRow>,
8395 line_layouts: &[LineWithInvisibles],
8396 line_height: Pixels,
8397 scroll_pixel_position: gpui::Point<Pixels>,
8398 newest_selection_head: Option<DisplayPoint>,
8399 editor_width: Pixels,
8400 style: &EditorStyle,
8401 edits: &Vec<(Range<Anchor>, String)>,
8402 edit_preview: &Option<language::EditPreview>,
8403 snapshot: &language::BufferSnapshot,
8404 window: &mut Window,
8405 cx: &mut App,
8406 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8407 let edit_start = edits
8408 .first()
8409 .unwrap()
8410 .0
8411 .start
8412 .to_display_point(editor_snapshot);
8413 let edit_end = edits
8414 .last()
8415 .unwrap()
8416 .0
8417 .end
8418 .to_display_point(editor_snapshot);
8419
8420 let is_visible = visible_row_range.contains(&edit_start.row())
8421 || visible_row_range.contains(&edit_end.row());
8422 if !is_visible {
8423 return None;
8424 }
8425
8426 let highlighted_edits =
8427 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8428
8429 let styled_text = highlighted_edits.to_styled_text(&style.text);
8430 let line_count = highlighted_edits.text.lines().count();
8431
8432 const BORDER_WIDTH: Pixels = px(1.);
8433
8434 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8435 let has_keybind = keybind.is_some();
8436
8437 let mut element = h_flex()
8438 .items_start()
8439 .child(
8440 h_flex()
8441 .bg(cx.theme().colors().editor_background)
8442 .border(BORDER_WIDTH)
8443 .shadow_sm()
8444 .border_color(cx.theme().colors().border)
8445 .rounded_l_lg()
8446 .when(line_count > 1, |el| el.rounded_br_lg())
8447 .pr_1()
8448 .child(styled_text),
8449 )
8450 .child(
8451 h_flex()
8452 .h(line_height + BORDER_WIDTH * 2.)
8453 .px_1p5()
8454 .gap_1()
8455 // Workaround: For some reason, there's a gap if we don't do this
8456 .ml(-BORDER_WIDTH)
8457 .shadow(vec![gpui::BoxShadow {
8458 color: gpui::black().opacity(0.05),
8459 offset: point(px(1.), px(1.)),
8460 blur_radius: px(2.),
8461 spread_radius: px(0.),
8462 }])
8463 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8464 .border(BORDER_WIDTH)
8465 .border_color(cx.theme().colors().border)
8466 .rounded_r_lg()
8467 .id("edit_prediction_diff_popover_keybind")
8468 .when(!has_keybind, |el| {
8469 let status_colors = cx.theme().status();
8470
8471 el.bg(status_colors.error_background)
8472 .border_color(status_colors.error.opacity(0.6))
8473 .child(Icon::new(IconName::Info).color(Color::Error))
8474 .cursor_default()
8475 .hoverable_tooltip(move |_window, cx| {
8476 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8477 })
8478 })
8479 .children(keybind),
8480 )
8481 .into_any();
8482
8483 let longest_row =
8484 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8485 let longest_line_width = if visible_row_range.contains(&longest_row) {
8486 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8487 } else {
8488 layout_line(
8489 longest_row,
8490 editor_snapshot,
8491 style,
8492 editor_width,
8493 |_| false,
8494 window,
8495 cx,
8496 )
8497 .width
8498 };
8499
8500 let viewport_bounds =
8501 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8502 right: -right_margin,
8503 ..Default::default()
8504 });
8505
8506 let x_after_longest =
8507 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8508 - scroll_pixel_position.x;
8509
8510 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8511
8512 // Fully visible if it can be displayed within the window (allow overlapping other
8513 // panes). However, this is only allowed if the popover starts within text_bounds.
8514 let can_position_to_the_right = x_after_longest < text_bounds.right()
8515 && x_after_longest + element_bounds.width < viewport_bounds.right();
8516
8517 let mut origin = if can_position_to_the_right {
8518 point(
8519 x_after_longest,
8520 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8521 - scroll_pixel_position.y,
8522 )
8523 } else {
8524 let cursor_row = newest_selection_head.map(|head| head.row());
8525 let above_edit = edit_start
8526 .row()
8527 .0
8528 .checked_sub(line_count as u32)
8529 .map(DisplayRow);
8530 let below_edit = Some(edit_end.row() + 1);
8531 let above_cursor =
8532 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8533 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8534
8535 // Place the edit popover adjacent to the edit if there is a location
8536 // available that is onscreen and does not obscure the cursor. Otherwise,
8537 // place it adjacent to the cursor.
8538 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8539 .into_iter()
8540 .flatten()
8541 .find(|&start_row| {
8542 let end_row = start_row + line_count as u32;
8543 visible_row_range.contains(&start_row)
8544 && visible_row_range.contains(&end_row)
8545 && cursor_row.map_or(true, |cursor_row| {
8546 !((start_row..end_row).contains(&cursor_row))
8547 })
8548 })?;
8549
8550 content_origin
8551 + point(
8552 -scroll_pixel_position.x,
8553 row_target.as_f32() * line_height - scroll_pixel_position.y,
8554 )
8555 };
8556
8557 origin.x -= BORDER_WIDTH;
8558
8559 window.defer_draw(element, origin, 1);
8560
8561 // Do not return an element, since it will already be drawn due to defer_draw.
8562 None
8563 }
8564
8565 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8566 px(30.)
8567 }
8568
8569 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8570 if self.read_only(cx) {
8571 cx.theme().players().read_only()
8572 } else {
8573 self.style.as_ref().unwrap().local_player
8574 }
8575 }
8576
8577 fn render_edit_prediction_accept_keybind(
8578 &self,
8579 window: &mut Window,
8580 cx: &App,
8581 ) -> Option<AnyElement> {
8582 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8583 let accept_keystroke = accept_binding.keystroke()?;
8584
8585 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8586
8587 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8588 Color::Accent
8589 } else {
8590 Color::Muted
8591 };
8592
8593 h_flex()
8594 .px_0p5()
8595 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8596 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8597 .text_size(TextSize::XSmall.rems(cx))
8598 .child(h_flex().children(ui::render_modifiers(
8599 &accept_keystroke.modifiers,
8600 PlatformStyle::platform(),
8601 Some(modifiers_color),
8602 Some(IconSize::XSmall.rems().into()),
8603 true,
8604 )))
8605 .when(is_platform_style_mac, |parent| {
8606 parent.child(accept_keystroke.key.clone())
8607 })
8608 .when(!is_platform_style_mac, |parent| {
8609 parent.child(
8610 Key::new(
8611 util::capitalize(&accept_keystroke.key),
8612 Some(Color::Default),
8613 )
8614 .size(Some(IconSize::XSmall.rems().into())),
8615 )
8616 })
8617 .into_any()
8618 .into()
8619 }
8620
8621 fn render_edit_prediction_line_popover(
8622 &self,
8623 label: impl Into<SharedString>,
8624 icon: Option<IconName>,
8625 window: &mut Window,
8626 cx: &App,
8627 ) -> Option<Stateful<Div>> {
8628 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8629
8630 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8631 let has_keybind = keybind.is_some();
8632
8633 let result = h_flex()
8634 .id("ep-line-popover")
8635 .py_0p5()
8636 .pl_1()
8637 .pr(padding_right)
8638 .gap_1()
8639 .rounded_md()
8640 .border_1()
8641 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8642 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8643 .shadow_sm()
8644 .when(!has_keybind, |el| {
8645 let status_colors = cx.theme().status();
8646
8647 el.bg(status_colors.error_background)
8648 .border_color(status_colors.error.opacity(0.6))
8649 .pl_2()
8650 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8651 .cursor_default()
8652 .hoverable_tooltip(move |_window, cx| {
8653 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8654 })
8655 })
8656 .children(keybind)
8657 .child(
8658 Label::new(label)
8659 .size(LabelSize::Small)
8660 .when(!has_keybind, |el| {
8661 el.color(cx.theme().status().error.into()).strikethrough()
8662 }),
8663 )
8664 .when(!has_keybind, |el| {
8665 el.child(
8666 h_flex().ml_1().child(
8667 Icon::new(IconName::Info)
8668 .size(IconSize::Small)
8669 .color(cx.theme().status().error.into()),
8670 ),
8671 )
8672 })
8673 .when_some(icon, |element, icon| {
8674 element.child(
8675 div()
8676 .mt(px(1.5))
8677 .child(Icon::new(icon).size(IconSize::Small)),
8678 )
8679 });
8680
8681 Some(result)
8682 }
8683
8684 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8685 let accent_color = cx.theme().colors().text_accent;
8686 let editor_bg_color = cx.theme().colors().editor_background;
8687 editor_bg_color.blend(accent_color.opacity(0.1))
8688 }
8689
8690 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8691 let accent_color = cx.theme().colors().text_accent;
8692 let editor_bg_color = cx.theme().colors().editor_background;
8693 editor_bg_color.blend(accent_color.opacity(0.6))
8694 }
8695
8696 fn render_edit_prediction_cursor_popover(
8697 &self,
8698 min_width: Pixels,
8699 max_width: Pixels,
8700 cursor_point: Point,
8701 style: &EditorStyle,
8702 accept_keystroke: Option<&gpui::Keystroke>,
8703 _window: &Window,
8704 cx: &mut Context<Editor>,
8705 ) -> Option<AnyElement> {
8706 let provider = self.edit_prediction_provider.as_ref()?;
8707
8708 if provider.provider.needs_terms_acceptance(cx) {
8709 return Some(
8710 h_flex()
8711 .min_w(min_width)
8712 .flex_1()
8713 .px_2()
8714 .py_1()
8715 .gap_3()
8716 .elevation_2(cx)
8717 .hover(|style| style.bg(cx.theme().colors().element_hover))
8718 .id("accept-terms")
8719 .cursor_pointer()
8720 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8721 .on_click(cx.listener(|this, _event, window, cx| {
8722 cx.stop_propagation();
8723 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8724 window.dispatch_action(
8725 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8726 cx,
8727 );
8728 }))
8729 .child(
8730 h_flex()
8731 .flex_1()
8732 .gap_2()
8733 .child(Icon::new(IconName::ZedPredict))
8734 .child(Label::new("Accept Terms of Service"))
8735 .child(div().w_full())
8736 .child(
8737 Icon::new(IconName::ArrowUpRight)
8738 .color(Color::Muted)
8739 .size(IconSize::Small),
8740 )
8741 .into_any_element(),
8742 )
8743 .into_any(),
8744 );
8745 }
8746
8747 let is_refreshing = provider.provider.is_refreshing(cx);
8748
8749 fn pending_completion_container() -> Div {
8750 h_flex()
8751 .h_full()
8752 .flex_1()
8753 .gap_2()
8754 .child(Icon::new(IconName::ZedPredict))
8755 }
8756
8757 let completion = match &self.active_inline_completion {
8758 Some(prediction) => {
8759 if !self.has_visible_completions_menu() {
8760 const RADIUS: Pixels = px(6.);
8761 const BORDER_WIDTH: Pixels = px(1.);
8762
8763 return Some(
8764 h_flex()
8765 .elevation_2(cx)
8766 .border(BORDER_WIDTH)
8767 .border_color(cx.theme().colors().border)
8768 .when(accept_keystroke.is_none(), |el| {
8769 el.border_color(cx.theme().status().error)
8770 })
8771 .rounded(RADIUS)
8772 .rounded_tl(px(0.))
8773 .overflow_hidden()
8774 .child(div().px_1p5().child(match &prediction.completion {
8775 InlineCompletion::Move { target, snapshot } => {
8776 use text::ToPoint as _;
8777 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8778 {
8779 Icon::new(IconName::ZedPredictDown)
8780 } else {
8781 Icon::new(IconName::ZedPredictUp)
8782 }
8783 }
8784 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8785 }))
8786 .child(
8787 h_flex()
8788 .gap_1()
8789 .py_1()
8790 .px_2()
8791 .rounded_r(RADIUS - BORDER_WIDTH)
8792 .border_l_1()
8793 .border_color(cx.theme().colors().border)
8794 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8795 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8796 el.child(
8797 Label::new("Hold")
8798 .size(LabelSize::Small)
8799 .when(accept_keystroke.is_none(), |el| {
8800 el.strikethrough()
8801 })
8802 .line_height_style(LineHeightStyle::UiLabel),
8803 )
8804 })
8805 .id("edit_prediction_cursor_popover_keybind")
8806 .when(accept_keystroke.is_none(), |el| {
8807 let status_colors = cx.theme().status();
8808
8809 el.bg(status_colors.error_background)
8810 .border_color(status_colors.error.opacity(0.6))
8811 .child(Icon::new(IconName::Info).color(Color::Error))
8812 .cursor_default()
8813 .hoverable_tooltip(move |_window, cx| {
8814 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8815 .into()
8816 })
8817 })
8818 .when_some(
8819 accept_keystroke.as_ref(),
8820 |el, accept_keystroke| {
8821 el.child(h_flex().children(ui::render_modifiers(
8822 &accept_keystroke.modifiers,
8823 PlatformStyle::platform(),
8824 Some(Color::Default),
8825 Some(IconSize::XSmall.rems().into()),
8826 false,
8827 )))
8828 },
8829 ),
8830 )
8831 .into_any(),
8832 );
8833 }
8834
8835 self.render_edit_prediction_cursor_popover_preview(
8836 prediction,
8837 cursor_point,
8838 style,
8839 cx,
8840 )?
8841 }
8842
8843 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8844 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8845 stale_completion,
8846 cursor_point,
8847 style,
8848 cx,
8849 )?,
8850
8851 None => {
8852 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8853 }
8854 },
8855
8856 None => pending_completion_container().child(Label::new("No Prediction")),
8857 };
8858
8859 let completion = if is_refreshing {
8860 completion
8861 .with_animation(
8862 "loading-completion",
8863 Animation::new(Duration::from_secs(2))
8864 .repeat()
8865 .with_easing(pulsating_between(0.4, 0.8)),
8866 |label, delta| label.opacity(delta),
8867 )
8868 .into_any_element()
8869 } else {
8870 completion.into_any_element()
8871 };
8872
8873 let has_completion = self.active_inline_completion.is_some();
8874
8875 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8876 Some(
8877 h_flex()
8878 .min_w(min_width)
8879 .max_w(max_width)
8880 .flex_1()
8881 .elevation_2(cx)
8882 .border_color(cx.theme().colors().border)
8883 .child(
8884 div()
8885 .flex_1()
8886 .py_1()
8887 .px_2()
8888 .overflow_hidden()
8889 .child(completion),
8890 )
8891 .when_some(accept_keystroke, |el, accept_keystroke| {
8892 if !accept_keystroke.modifiers.modified() {
8893 return el;
8894 }
8895
8896 el.child(
8897 h_flex()
8898 .h_full()
8899 .border_l_1()
8900 .rounded_r_lg()
8901 .border_color(cx.theme().colors().border)
8902 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8903 .gap_1()
8904 .py_1()
8905 .px_2()
8906 .child(
8907 h_flex()
8908 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8909 .when(is_platform_style_mac, |parent| parent.gap_1())
8910 .child(h_flex().children(ui::render_modifiers(
8911 &accept_keystroke.modifiers,
8912 PlatformStyle::platform(),
8913 Some(if !has_completion {
8914 Color::Muted
8915 } else {
8916 Color::Default
8917 }),
8918 None,
8919 false,
8920 ))),
8921 )
8922 .child(Label::new("Preview").into_any_element())
8923 .opacity(if has_completion { 1.0 } else { 0.4 }),
8924 )
8925 })
8926 .into_any(),
8927 )
8928 }
8929
8930 fn render_edit_prediction_cursor_popover_preview(
8931 &self,
8932 completion: &InlineCompletionState,
8933 cursor_point: Point,
8934 style: &EditorStyle,
8935 cx: &mut Context<Editor>,
8936 ) -> Option<Div> {
8937 use text::ToPoint as _;
8938
8939 fn render_relative_row_jump(
8940 prefix: impl Into<String>,
8941 current_row: u32,
8942 target_row: u32,
8943 ) -> Div {
8944 let (row_diff, arrow) = if target_row < current_row {
8945 (current_row - target_row, IconName::ArrowUp)
8946 } else {
8947 (target_row - current_row, IconName::ArrowDown)
8948 };
8949
8950 h_flex()
8951 .child(
8952 Label::new(format!("{}{}", prefix.into(), row_diff))
8953 .color(Color::Muted)
8954 .size(LabelSize::Small),
8955 )
8956 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8957 }
8958
8959 match &completion.completion {
8960 InlineCompletion::Move {
8961 target, snapshot, ..
8962 } => Some(
8963 h_flex()
8964 .px_2()
8965 .gap_2()
8966 .flex_1()
8967 .child(
8968 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8969 Icon::new(IconName::ZedPredictDown)
8970 } else {
8971 Icon::new(IconName::ZedPredictUp)
8972 },
8973 )
8974 .child(Label::new("Jump to Edit")),
8975 ),
8976
8977 InlineCompletion::Edit {
8978 edits,
8979 edit_preview,
8980 snapshot,
8981 display_mode: _,
8982 } => {
8983 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8984
8985 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8986 &snapshot,
8987 &edits,
8988 edit_preview.as_ref()?,
8989 true,
8990 cx,
8991 )
8992 .first_line_preview();
8993
8994 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8995 .with_default_highlights(&style.text, highlighted_edits.highlights);
8996
8997 let preview = h_flex()
8998 .gap_1()
8999 .min_w_16()
9000 .child(styled_text)
9001 .when(has_more_lines, |parent| parent.child("…"));
9002
9003 let left = if first_edit_row != cursor_point.row {
9004 render_relative_row_jump("", cursor_point.row, first_edit_row)
9005 .into_any_element()
9006 } else {
9007 Icon::new(IconName::ZedPredict).into_any_element()
9008 };
9009
9010 Some(
9011 h_flex()
9012 .h_full()
9013 .flex_1()
9014 .gap_2()
9015 .pr_1()
9016 .overflow_x_hidden()
9017 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9018 .child(left)
9019 .child(preview),
9020 )
9021 }
9022 }
9023 }
9024
9025 pub fn render_context_menu(
9026 &self,
9027 style: &EditorStyle,
9028 max_height_in_lines: u32,
9029 window: &mut Window,
9030 cx: &mut Context<Editor>,
9031 ) -> Option<AnyElement> {
9032 let menu = self.context_menu.borrow();
9033 let menu = menu.as_ref()?;
9034 if !menu.visible() {
9035 return None;
9036 };
9037 Some(menu.render(style, max_height_in_lines, window, cx))
9038 }
9039
9040 fn render_context_menu_aside(
9041 &mut self,
9042 max_size: Size<Pixels>,
9043 window: &mut Window,
9044 cx: &mut Context<Editor>,
9045 ) -> Option<AnyElement> {
9046 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9047 if menu.visible() {
9048 menu.render_aside(max_size, window, cx)
9049 } else {
9050 None
9051 }
9052 })
9053 }
9054
9055 fn hide_context_menu(
9056 &mut self,
9057 window: &mut Window,
9058 cx: &mut Context<Self>,
9059 ) -> Option<CodeContextMenu> {
9060 cx.notify();
9061 self.completion_tasks.clear();
9062 let context_menu = self.context_menu.borrow_mut().take();
9063 self.stale_inline_completion_in_menu.take();
9064 self.update_visible_inline_completion(window, cx);
9065 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9066 if let Some(completion_provider) = &self.completion_provider {
9067 completion_provider.selection_changed(None, window, cx);
9068 }
9069 }
9070 context_menu
9071 }
9072
9073 fn show_snippet_choices(
9074 &mut self,
9075 choices: &Vec<String>,
9076 selection: Range<Anchor>,
9077 cx: &mut Context<Self>,
9078 ) {
9079 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9080 (Some(a), Some(b)) if a == b => a,
9081 _ => {
9082 log::error!("expected anchor range to have matching buffer IDs");
9083 return;
9084 }
9085 };
9086 let multi_buffer = self.buffer().read(cx);
9087 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9088 return;
9089 };
9090
9091 let id = post_inc(&mut self.next_completion_id);
9092 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9093 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9094 CompletionsMenu::new_snippet_choices(
9095 id,
9096 true,
9097 choices,
9098 selection,
9099 buffer,
9100 snippet_sort_order,
9101 ),
9102 ));
9103 }
9104
9105 pub fn insert_snippet(
9106 &mut self,
9107 insertion_ranges: &[Range<usize>],
9108 snippet: Snippet,
9109 window: &mut Window,
9110 cx: &mut Context<Self>,
9111 ) -> Result<()> {
9112 struct Tabstop<T> {
9113 is_end_tabstop: bool,
9114 ranges: Vec<Range<T>>,
9115 choices: Option<Vec<String>>,
9116 }
9117
9118 let tabstops = self.buffer.update(cx, |buffer, cx| {
9119 let snippet_text: Arc<str> = snippet.text.clone().into();
9120 let edits = insertion_ranges
9121 .iter()
9122 .cloned()
9123 .map(|range| (range, snippet_text.clone()));
9124 let autoindent_mode = AutoindentMode::Block {
9125 original_indent_columns: Vec::new(),
9126 };
9127 buffer.edit(edits, Some(autoindent_mode), cx);
9128
9129 let snapshot = &*buffer.read(cx);
9130 let snippet = &snippet;
9131 snippet
9132 .tabstops
9133 .iter()
9134 .map(|tabstop| {
9135 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9136 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9137 });
9138 let mut tabstop_ranges = tabstop
9139 .ranges
9140 .iter()
9141 .flat_map(|tabstop_range| {
9142 let mut delta = 0_isize;
9143 insertion_ranges.iter().map(move |insertion_range| {
9144 let insertion_start = insertion_range.start as isize + delta;
9145 delta +=
9146 snippet.text.len() as isize - insertion_range.len() as isize;
9147
9148 let start = ((insertion_start + tabstop_range.start) as usize)
9149 .min(snapshot.len());
9150 let end = ((insertion_start + tabstop_range.end) as usize)
9151 .min(snapshot.len());
9152 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9153 })
9154 })
9155 .collect::<Vec<_>>();
9156 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9157
9158 Tabstop {
9159 is_end_tabstop,
9160 ranges: tabstop_ranges,
9161 choices: tabstop.choices.clone(),
9162 }
9163 })
9164 .collect::<Vec<_>>()
9165 });
9166 if let Some(tabstop) = tabstops.first() {
9167 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9168 // Reverse order so that the first range is the newest created selection.
9169 // Completions will use it and autoscroll will prioritize it.
9170 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9171 });
9172
9173 if let Some(choices) = &tabstop.choices {
9174 if let Some(selection) = tabstop.ranges.first() {
9175 self.show_snippet_choices(choices, selection.clone(), cx)
9176 }
9177 }
9178
9179 // If we're already at the last tabstop and it's at the end of the snippet,
9180 // we're done, we don't need to keep the state around.
9181 if !tabstop.is_end_tabstop {
9182 let choices = tabstops
9183 .iter()
9184 .map(|tabstop| tabstop.choices.clone())
9185 .collect();
9186
9187 let ranges = tabstops
9188 .into_iter()
9189 .map(|tabstop| tabstop.ranges)
9190 .collect::<Vec<_>>();
9191
9192 self.snippet_stack.push(SnippetState {
9193 active_index: 0,
9194 ranges,
9195 choices,
9196 });
9197 }
9198
9199 // Check whether the just-entered snippet ends with an auto-closable bracket.
9200 if self.autoclose_regions.is_empty() {
9201 let snapshot = self.buffer.read(cx).snapshot(cx);
9202 for selection in &mut self.selections.all::<Point>(cx) {
9203 let selection_head = selection.head();
9204 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9205 continue;
9206 };
9207
9208 let mut bracket_pair = None;
9209 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9210 let prev_chars = snapshot
9211 .reversed_chars_at(selection_head)
9212 .collect::<String>();
9213 for (pair, enabled) in scope.brackets() {
9214 if enabled
9215 && pair.close
9216 && prev_chars.starts_with(pair.start.as_str())
9217 && next_chars.starts_with(pair.end.as_str())
9218 {
9219 bracket_pair = Some(pair.clone());
9220 break;
9221 }
9222 }
9223 if let Some(pair) = bracket_pair {
9224 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9225 let autoclose_enabled =
9226 self.use_autoclose && snapshot_settings.use_autoclose;
9227 if autoclose_enabled {
9228 let start = snapshot.anchor_after(selection_head);
9229 let end = snapshot.anchor_after(selection_head);
9230 self.autoclose_regions.push(AutocloseRegion {
9231 selection_id: selection.id,
9232 range: start..end,
9233 pair,
9234 });
9235 }
9236 }
9237 }
9238 }
9239 }
9240 Ok(())
9241 }
9242
9243 pub fn move_to_next_snippet_tabstop(
9244 &mut self,
9245 window: &mut Window,
9246 cx: &mut Context<Self>,
9247 ) -> bool {
9248 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9249 }
9250
9251 pub fn move_to_prev_snippet_tabstop(
9252 &mut self,
9253 window: &mut Window,
9254 cx: &mut Context<Self>,
9255 ) -> bool {
9256 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9257 }
9258
9259 pub fn move_to_snippet_tabstop(
9260 &mut self,
9261 bias: Bias,
9262 window: &mut Window,
9263 cx: &mut Context<Self>,
9264 ) -> bool {
9265 if let Some(mut snippet) = self.snippet_stack.pop() {
9266 match bias {
9267 Bias::Left => {
9268 if snippet.active_index > 0 {
9269 snippet.active_index -= 1;
9270 } else {
9271 self.snippet_stack.push(snippet);
9272 return false;
9273 }
9274 }
9275 Bias::Right => {
9276 if snippet.active_index + 1 < snippet.ranges.len() {
9277 snippet.active_index += 1;
9278 } else {
9279 self.snippet_stack.push(snippet);
9280 return false;
9281 }
9282 }
9283 }
9284 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9285 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9286 // Reverse order so that the first range is the newest created selection.
9287 // Completions will use it and autoscroll will prioritize it.
9288 s.select_ranges(current_ranges.iter().rev().cloned())
9289 });
9290
9291 if let Some(choices) = &snippet.choices[snippet.active_index] {
9292 if let Some(selection) = current_ranges.first() {
9293 self.show_snippet_choices(&choices, selection.clone(), cx);
9294 }
9295 }
9296
9297 // If snippet state is not at the last tabstop, push it back on the stack
9298 if snippet.active_index + 1 < snippet.ranges.len() {
9299 self.snippet_stack.push(snippet);
9300 }
9301 return true;
9302 }
9303 }
9304
9305 false
9306 }
9307
9308 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9309 self.transact(window, cx, |this, window, cx| {
9310 this.select_all(&SelectAll, window, cx);
9311 this.insert("", window, cx);
9312 });
9313 }
9314
9315 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9316 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9317 self.transact(window, cx, |this, window, cx| {
9318 this.select_autoclose_pair(window, cx);
9319 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9320 if !this.linked_edit_ranges.is_empty() {
9321 let selections = this.selections.all::<MultiBufferPoint>(cx);
9322 let snapshot = this.buffer.read(cx).snapshot(cx);
9323
9324 for selection in selections.iter() {
9325 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9326 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9327 if selection_start.buffer_id != selection_end.buffer_id {
9328 continue;
9329 }
9330 if let Some(ranges) =
9331 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9332 {
9333 for (buffer, entries) in ranges {
9334 linked_ranges.entry(buffer).or_default().extend(entries);
9335 }
9336 }
9337 }
9338 }
9339
9340 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9341 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9342 for selection in &mut selections {
9343 if selection.is_empty() {
9344 let old_head = selection.head();
9345 let mut new_head =
9346 movement::left(&display_map, old_head.to_display_point(&display_map))
9347 .to_point(&display_map);
9348 if let Some((buffer, line_buffer_range)) = display_map
9349 .buffer_snapshot
9350 .buffer_line_for_row(MultiBufferRow(old_head.row))
9351 {
9352 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9353 let indent_len = match indent_size.kind {
9354 IndentKind::Space => {
9355 buffer.settings_at(line_buffer_range.start, cx).tab_size
9356 }
9357 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9358 };
9359 if old_head.column <= indent_size.len && old_head.column > 0 {
9360 let indent_len = indent_len.get();
9361 new_head = cmp::min(
9362 new_head,
9363 MultiBufferPoint::new(
9364 old_head.row,
9365 ((old_head.column - 1) / indent_len) * indent_len,
9366 ),
9367 );
9368 }
9369 }
9370
9371 selection.set_head(new_head, SelectionGoal::None);
9372 }
9373 }
9374
9375 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9376 s.select(selections)
9377 });
9378 this.insert("", window, cx);
9379 let empty_str: Arc<str> = Arc::from("");
9380 for (buffer, edits) in linked_ranges {
9381 let snapshot = buffer.read(cx).snapshot();
9382 use text::ToPoint as TP;
9383
9384 let edits = edits
9385 .into_iter()
9386 .map(|range| {
9387 let end_point = TP::to_point(&range.end, &snapshot);
9388 let mut start_point = TP::to_point(&range.start, &snapshot);
9389
9390 if end_point == start_point {
9391 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9392 .saturating_sub(1);
9393 start_point =
9394 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9395 };
9396
9397 (start_point..end_point, empty_str.clone())
9398 })
9399 .sorted_by_key(|(range, _)| range.start)
9400 .collect::<Vec<_>>();
9401 buffer.update(cx, |this, cx| {
9402 this.edit(edits, None, cx);
9403 })
9404 }
9405 this.refresh_inline_completion(true, false, window, cx);
9406 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9407 });
9408 }
9409
9410 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9411 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9412 self.transact(window, cx, |this, window, cx| {
9413 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9414 s.move_with(|map, selection| {
9415 if selection.is_empty() {
9416 let cursor = movement::right(map, selection.head());
9417 selection.end = cursor;
9418 selection.reversed = true;
9419 selection.goal = SelectionGoal::None;
9420 }
9421 })
9422 });
9423 this.insert("", window, cx);
9424 this.refresh_inline_completion(true, false, window, cx);
9425 });
9426 }
9427
9428 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9429 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9430 if self.move_to_prev_snippet_tabstop(window, cx) {
9431 return;
9432 }
9433 self.outdent(&Outdent, window, cx);
9434 }
9435
9436 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9437 if self.move_to_next_snippet_tabstop(window, cx) {
9438 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9439 return;
9440 }
9441 if self.read_only(cx) {
9442 return;
9443 }
9444 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9445 let mut selections = self.selections.all_adjusted(cx);
9446 let buffer = self.buffer.read(cx);
9447 let snapshot = buffer.snapshot(cx);
9448 let rows_iter = selections.iter().map(|s| s.head().row);
9449 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9450
9451 let has_some_cursor_in_whitespace = selections
9452 .iter()
9453 .filter(|selection| selection.is_empty())
9454 .any(|selection| {
9455 let cursor = selection.head();
9456 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9457 cursor.column < current_indent.len
9458 });
9459
9460 let mut edits = Vec::new();
9461 let mut prev_edited_row = 0;
9462 let mut row_delta = 0;
9463 for selection in &mut selections {
9464 if selection.start.row != prev_edited_row {
9465 row_delta = 0;
9466 }
9467 prev_edited_row = selection.end.row;
9468
9469 // If the selection is non-empty, then increase the indentation of the selected lines.
9470 if !selection.is_empty() {
9471 row_delta =
9472 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9473 continue;
9474 }
9475
9476 let cursor = selection.head();
9477 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9478 if let Some(suggested_indent) =
9479 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9480 {
9481 // Don't do anything if already at suggested indent
9482 // and there is any other cursor which is not
9483 if has_some_cursor_in_whitespace
9484 && cursor.column == current_indent.len
9485 && current_indent.len == suggested_indent.len
9486 {
9487 continue;
9488 }
9489
9490 // Adjust line and move cursor to suggested indent
9491 // if cursor is not at suggested indent
9492 if cursor.column < suggested_indent.len
9493 && cursor.column <= current_indent.len
9494 && current_indent.len <= suggested_indent.len
9495 {
9496 selection.start = Point::new(cursor.row, suggested_indent.len);
9497 selection.end = selection.start;
9498 if row_delta == 0 {
9499 edits.extend(Buffer::edit_for_indent_size_adjustment(
9500 cursor.row,
9501 current_indent,
9502 suggested_indent,
9503 ));
9504 row_delta = suggested_indent.len - current_indent.len;
9505 }
9506 continue;
9507 }
9508
9509 // If current indent is more than suggested indent
9510 // only move cursor to current indent and skip indent
9511 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9512 selection.start = Point::new(cursor.row, current_indent.len);
9513 selection.end = selection.start;
9514 continue;
9515 }
9516 }
9517
9518 // Otherwise, insert a hard or soft tab.
9519 let settings = buffer.language_settings_at(cursor, cx);
9520 let tab_size = if settings.hard_tabs {
9521 IndentSize::tab()
9522 } else {
9523 let tab_size = settings.tab_size.get();
9524 let indent_remainder = snapshot
9525 .text_for_range(Point::new(cursor.row, 0)..cursor)
9526 .flat_map(str::chars)
9527 .fold(row_delta % tab_size, |counter: u32, c| {
9528 if c == '\t' {
9529 0
9530 } else {
9531 (counter + 1) % tab_size
9532 }
9533 });
9534
9535 let chars_to_next_tab_stop = tab_size - indent_remainder;
9536 IndentSize::spaces(chars_to_next_tab_stop)
9537 };
9538 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9539 selection.end = selection.start;
9540 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9541 row_delta += tab_size.len;
9542 }
9543
9544 self.transact(window, cx, |this, window, cx| {
9545 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9546 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9547 s.select(selections)
9548 });
9549 this.refresh_inline_completion(true, false, window, cx);
9550 });
9551 }
9552
9553 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9554 if self.read_only(cx) {
9555 return;
9556 }
9557 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9558 let mut selections = self.selections.all::<Point>(cx);
9559 let mut prev_edited_row = 0;
9560 let mut row_delta = 0;
9561 let mut edits = Vec::new();
9562 let buffer = self.buffer.read(cx);
9563 let snapshot = buffer.snapshot(cx);
9564 for selection in &mut selections {
9565 if selection.start.row != prev_edited_row {
9566 row_delta = 0;
9567 }
9568 prev_edited_row = selection.end.row;
9569
9570 row_delta =
9571 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9572 }
9573
9574 self.transact(window, cx, |this, window, cx| {
9575 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9576 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9577 s.select(selections)
9578 });
9579 });
9580 }
9581
9582 fn indent_selection(
9583 buffer: &MultiBuffer,
9584 snapshot: &MultiBufferSnapshot,
9585 selection: &mut Selection<Point>,
9586 edits: &mut Vec<(Range<Point>, String)>,
9587 delta_for_start_row: u32,
9588 cx: &App,
9589 ) -> u32 {
9590 let settings = buffer.language_settings_at(selection.start, cx);
9591 let tab_size = settings.tab_size.get();
9592 let indent_kind = if settings.hard_tabs {
9593 IndentKind::Tab
9594 } else {
9595 IndentKind::Space
9596 };
9597 let mut start_row = selection.start.row;
9598 let mut end_row = selection.end.row + 1;
9599
9600 // If a selection ends at the beginning of a line, don't indent
9601 // that last line.
9602 if selection.end.column == 0 && selection.end.row > selection.start.row {
9603 end_row -= 1;
9604 }
9605
9606 // Avoid re-indenting a row that has already been indented by a
9607 // previous selection, but still update this selection's column
9608 // to reflect that indentation.
9609 if delta_for_start_row > 0 {
9610 start_row += 1;
9611 selection.start.column += delta_for_start_row;
9612 if selection.end.row == selection.start.row {
9613 selection.end.column += delta_for_start_row;
9614 }
9615 }
9616
9617 let mut delta_for_end_row = 0;
9618 let has_multiple_rows = start_row + 1 != end_row;
9619 for row in start_row..end_row {
9620 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9621 let indent_delta = match (current_indent.kind, indent_kind) {
9622 (IndentKind::Space, IndentKind::Space) => {
9623 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9624 IndentSize::spaces(columns_to_next_tab_stop)
9625 }
9626 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9627 (_, IndentKind::Tab) => IndentSize::tab(),
9628 };
9629
9630 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9631 0
9632 } else {
9633 selection.start.column
9634 };
9635 let row_start = Point::new(row, start);
9636 edits.push((
9637 row_start..row_start,
9638 indent_delta.chars().collect::<String>(),
9639 ));
9640
9641 // Update this selection's endpoints to reflect the indentation.
9642 if row == selection.start.row {
9643 selection.start.column += indent_delta.len;
9644 }
9645 if row == selection.end.row {
9646 selection.end.column += indent_delta.len;
9647 delta_for_end_row = indent_delta.len;
9648 }
9649 }
9650
9651 if selection.start.row == selection.end.row {
9652 delta_for_start_row + delta_for_end_row
9653 } else {
9654 delta_for_end_row
9655 }
9656 }
9657
9658 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9659 if self.read_only(cx) {
9660 return;
9661 }
9662 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9663 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9664 let selections = self.selections.all::<Point>(cx);
9665 let mut deletion_ranges = Vec::new();
9666 let mut last_outdent = None;
9667 {
9668 let buffer = self.buffer.read(cx);
9669 let snapshot = buffer.snapshot(cx);
9670 for selection in &selections {
9671 let settings = buffer.language_settings_at(selection.start, cx);
9672 let tab_size = settings.tab_size.get();
9673 let mut rows = selection.spanned_rows(false, &display_map);
9674
9675 // Avoid re-outdenting a row that has already been outdented by a
9676 // previous selection.
9677 if let Some(last_row) = last_outdent {
9678 if last_row == rows.start {
9679 rows.start = rows.start.next_row();
9680 }
9681 }
9682 let has_multiple_rows = rows.len() > 1;
9683 for row in rows.iter_rows() {
9684 let indent_size = snapshot.indent_size_for_line(row);
9685 if indent_size.len > 0 {
9686 let deletion_len = match indent_size.kind {
9687 IndentKind::Space => {
9688 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9689 if columns_to_prev_tab_stop == 0 {
9690 tab_size
9691 } else {
9692 columns_to_prev_tab_stop
9693 }
9694 }
9695 IndentKind::Tab => 1,
9696 };
9697 let start = if has_multiple_rows
9698 || deletion_len > selection.start.column
9699 || indent_size.len < selection.start.column
9700 {
9701 0
9702 } else {
9703 selection.start.column - deletion_len
9704 };
9705 deletion_ranges.push(
9706 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9707 );
9708 last_outdent = Some(row);
9709 }
9710 }
9711 }
9712 }
9713
9714 self.transact(window, cx, |this, window, cx| {
9715 this.buffer.update(cx, |buffer, cx| {
9716 let empty_str: Arc<str> = Arc::default();
9717 buffer.edit(
9718 deletion_ranges
9719 .into_iter()
9720 .map(|range| (range, empty_str.clone())),
9721 None,
9722 cx,
9723 );
9724 });
9725 let selections = this.selections.all::<usize>(cx);
9726 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9727 s.select(selections)
9728 });
9729 });
9730 }
9731
9732 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9733 if self.read_only(cx) {
9734 return;
9735 }
9736 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9737 let selections = self
9738 .selections
9739 .all::<usize>(cx)
9740 .into_iter()
9741 .map(|s| s.range());
9742
9743 self.transact(window, cx, |this, window, cx| {
9744 this.buffer.update(cx, |buffer, cx| {
9745 buffer.autoindent_ranges(selections, cx);
9746 });
9747 let selections = this.selections.all::<usize>(cx);
9748 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9749 s.select(selections)
9750 });
9751 });
9752 }
9753
9754 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9755 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9756 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9757 let selections = self.selections.all::<Point>(cx);
9758
9759 let mut new_cursors = Vec::new();
9760 let mut edit_ranges = Vec::new();
9761 let mut selections = selections.iter().peekable();
9762 while let Some(selection) = selections.next() {
9763 let mut rows = selection.spanned_rows(false, &display_map);
9764 let goal_display_column = selection.head().to_display_point(&display_map).column();
9765
9766 // Accumulate contiguous regions of rows that we want to delete.
9767 while let Some(next_selection) = selections.peek() {
9768 let next_rows = next_selection.spanned_rows(false, &display_map);
9769 if next_rows.start <= rows.end {
9770 rows.end = next_rows.end;
9771 selections.next().unwrap();
9772 } else {
9773 break;
9774 }
9775 }
9776
9777 let buffer = &display_map.buffer_snapshot;
9778 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9779 let edit_end;
9780 let cursor_buffer_row;
9781 if buffer.max_point().row >= rows.end.0 {
9782 // If there's a line after the range, delete the \n from the end of the row range
9783 // and position the cursor on the next line.
9784 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9785 cursor_buffer_row = rows.end;
9786 } else {
9787 // If there isn't a line after the range, delete the \n from the line before the
9788 // start of the row range and position the cursor there.
9789 edit_start = edit_start.saturating_sub(1);
9790 edit_end = buffer.len();
9791 cursor_buffer_row = rows.start.previous_row();
9792 }
9793
9794 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9795 *cursor.column_mut() =
9796 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9797
9798 new_cursors.push((
9799 selection.id,
9800 buffer.anchor_after(cursor.to_point(&display_map)),
9801 ));
9802 edit_ranges.push(edit_start..edit_end);
9803 }
9804
9805 self.transact(window, cx, |this, window, cx| {
9806 let buffer = this.buffer.update(cx, |buffer, cx| {
9807 let empty_str: Arc<str> = Arc::default();
9808 buffer.edit(
9809 edit_ranges
9810 .into_iter()
9811 .map(|range| (range, empty_str.clone())),
9812 None,
9813 cx,
9814 );
9815 buffer.snapshot(cx)
9816 });
9817 let new_selections = new_cursors
9818 .into_iter()
9819 .map(|(id, cursor)| {
9820 let cursor = cursor.to_point(&buffer);
9821 Selection {
9822 id,
9823 start: cursor,
9824 end: cursor,
9825 reversed: false,
9826 goal: SelectionGoal::None,
9827 }
9828 })
9829 .collect();
9830
9831 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9832 s.select(new_selections);
9833 });
9834 });
9835 }
9836
9837 pub fn join_lines_impl(
9838 &mut self,
9839 insert_whitespace: bool,
9840 window: &mut Window,
9841 cx: &mut Context<Self>,
9842 ) {
9843 if self.read_only(cx) {
9844 return;
9845 }
9846 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9847 for selection in self.selections.all::<Point>(cx) {
9848 let start = MultiBufferRow(selection.start.row);
9849 // Treat single line selections as if they include the next line. Otherwise this action
9850 // would do nothing for single line selections individual cursors.
9851 let end = if selection.start.row == selection.end.row {
9852 MultiBufferRow(selection.start.row + 1)
9853 } else {
9854 MultiBufferRow(selection.end.row)
9855 };
9856
9857 if let Some(last_row_range) = row_ranges.last_mut() {
9858 if start <= last_row_range.end {
9859 last_row_range.end = end;
9860 continue;
9861 }
9862 }
9863 row_ranges.push(start..end);
9864 }
9865
9866 let snapshot = self.buffer.read(cx).snapshot(cx);
9867 let mut cursor_positions = Vec::new();
9868 for row_range in &row_ranges {
9869 let anchor = snapshot.anchor_before(Point::new(
9870 row_range.end.previous_row().0,
9871 snapshot.line_len(row_range.end.previous_row()),
9872 ));
9873 cursor_positions.push(anchor..anchor);
9874 }
9875
9876 self.transact(window, cx, |this, window, cx| {
9877 for row_range in row_ranges.into_iter().rev() {
9878 for row in row_range.iter_rows().rev() {
9879 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9880 let next_line_row = row.next_row();
9881 let indent = snapshot.indent_size_for_line(next_line_row);
9882 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9883
9884 let replace =
9885 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9886 " "
9887 } else {
9888 ""
9889 };
9890
9891 this.buffer.update(cx, |buffer, cx| {
9892 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9893 });
9894 }
9895 }
9896
9897 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9898 s.select_anchor_ranges(cursor_positions)
9899 });
9900 });
9901 }
9902
9903 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9904 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9905 self.join_lines_impl(true, window, cx);
9906 }
9907
9908 pub fn sort_lines_case_sensitive(
9909 &mut self,
9910 _: &SortLinesCaseSensitive,
9911 window: &mut Window,
9912 cx: &mut Context<Self>,
9913 ) {
9914 self.manipulate_lines(window, cx, |lines| lines.sort())
9915 }
9916
9917 pub fn sort_lines_case_insensitive(
9918 &mut self,
9919 _: &SortLinesCaseInsensitive,
9920 window: &mut Window,
9921 cx: &mut Context<Self>,
9922 ) {
9923 self.manipulate_lines(window, cx, |lines| {
9924 lines.sort_by_key(|line| line.to_lowercase())
9925 })
9926 }
9927
9928 pub fn unique_lines_case_insensitive(
9929 &mut self,
9930 _: &UniqueLinesCaseInsensitive,
9931 window: &mut Window,
9932 cx: &mut Context<Self>,
9933 ) {
9934 self.manipulate_lines(window, cx, |lines| {
9935 let mut seen = HashSet::default();
9936 lines.retain(|line| seen.insert(line.to_lowercase()));
9937 })
9938 }
9939
9940 pub fn unique_lines_case_sensitive(
9941 &mut self,
9942 _: &UniqueLinesCaseSensitive,
9943 window: &mut Window,
9944 cx: &mut Context<Self>,
9945 ) {
9946 self.manipulate_lines(window, cx, |lines| {
9947 let mut seen = HashSet::default();
9948 lines.retain(|line| seen.insert(*line));
9949 })
9950 }
9951
9952 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9953 let Some(project) = self.project.clone() else {
9954 return;
9955 };
9956 self.reload(project, window, cx)
9957 .detach_and_notify_err(window, cx);
9958 }
9959
9960 pub fn restore_file(
9961 &mut self,
9962 _: &::git::RestoreFile,
9963 window: &mut Window,
9964 cx: &mut Context<Self>,
9965 ) {
9966 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9967 let mut buffer_ids = HashSet::default();
9968 let snapshot = self.buffer().read(cx).snapshot(cx);
9969 for selection in self.selections.all::<usize>(cx) {
9970 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9971 }
9972
9973 let buffer = self.buffer().read(cx);
9974 let ranges = buffer_ids
9975 .into_iter()
9976 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9977 .collect::<Vec<_>>();
9978
9979 self.restore_hunks_in_ranges(ranges, window, cx);
9980 }
9981
9982 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9983 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9984 let selections = self
9985 .selections
9986 .all(cx)
9987 .into_iter()
9988 .map(|s| s.range())
9989 .collect();
9990 self.restore_hunks_in_ranges(selections, window, cx);
9991 }
9992
9993 pub fn restore_hunks_in_ranges(
9994 &mut self,
9995 ranges: Vec<Range<Point>>,
9996 window: &mut Window,
9997 cx: &mut Context<Editor>,
9998 ) {
9999 let mut revert_changes = HashMap::default();
10000 let chunk_by = self
10001 .snapshot(window, cx)
10002 .hunks_for_ranges(ranges)
10003 .into_iter()
10004 .chunk_by(|hunk| hunk.buffer_id);
10005 for (buffer_id, hunks) in &chunk_by {
10006 let hunks = hunks.collect::<Vec<_>>();
10007 for hunk in &hunks {
10008 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10009 }
10010 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10011 }
10012 drop(chunk_by);
10013 if !revert_changes.is_empty() {
10014 self.transact(window, cx, |editor, window, cx| {
10015 editor.restore(revert_changes, window, cx);
10016 });
10017 }
10018 }
10019
10020 pub fn open_active_item_in_terminal(
10021 &mut self,
10022 _: &OpenInTerminal,
10023 window: &mut Window,
10024 cx: &mut Context<Self>,
10025 ) {
10026 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10027 let project_path = buffer.read(cx).project_path(cx)?;
10028 let project = self.project.as_ref()?.read(cx);
10029 let entry = project.entry_for_path(&project_path, cx)?;
10030 let parent = match &entry.canonical_path {
10031 Some(canonical_path) => canonical_path.to_path_buf(),
10032 None => project.absolute_path(&project_path, cx)?,
10033 }
10034 .parent()?
10035 .to_path_buf();
10036 Some(parent)
10037 }) {
10038 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10039 }
10040 }
10041
10042 fn set_breakpoint_context_menu(
10043 &mut self,
10044 display_row: DisplayRow,
10045 position: Option<Anchor>,
10046 clicked_point: gpui::Point<Pixels>,
10047 window: &mut Window,
10048 cx: &mut Context<Self>,
10049 ) {
10050 if !cx.has_flag::<DebuggerFeatureFlag>() {
10051 return;
10052 }
10053 let source = self
10054 .buffer
10055 .read(cx)
10056 .snapshot(cx)
10057 .anchor_before(Point::new(display_row.0, 0u32));
10058
10059 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10060
10061 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10062 self,
10063 source,
10064 clicked_point,
10065 context_menu,
10066 window,
10067 cx,
10068 );
10069 }
10070
10071 fn add_edit_breakpoint_block(
10072 &mut self,
10073 anchor: Anchor,
10074 breakpoint: &Breakpoint,
10075 edit_action: BreakpointPromptEditAction,
10076 window: &mut Window,
10077 cx: &mut Context<Self>,
10078 ) {
10079 let weak_editor = cx.weak_entity();
10080 let bp_prompt = cx.new(|cx| {
10081 BreakpointPromptEditor::new(
10082 weak_editor,
10083 anchor,
10084 breakpoint.clone(),
10085 edit_action,
10086 window,
10087 cx,
10088 )
10089 });
10090
10091 let height = bp_prompt.update(cx, |this, cx| {
10092 this.prompt
10093 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10094 });
10095 let cloned_prompt = bp_prompt.clone();
10096 let blocks = vec![BlockProperties {
10097 style: BlockStyle::Sticky,
10098 placement: BlockPlacement::Above(anchor),
10099 height: Some(height),
10100 render: Arc::new(move |cx| {
10101 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10102 cloned_prompt.clone().into_any_element()
10103 }),
10104 priority: 0,
10105 render_in_minimap: true,
10106 }];
10107
10108 let focus_handle = bp_prompt.focus_handle(cx);
10109 window.focus(&focus_handle);
10110
10111 let block_ids = self.insert_blocks(blocks, None, cx);
10112 bp_prompt.update(cx, |prompt, _| {
10113 prompt.add_block_ids(block_ids);
10114 });
10115 }
10116
10117 pub(crate) fn breakpoint_at_row(
10118 &self,
10119 row: u32,
10120 window: &mut Window,
10121 cx: &mut Context<Self>,
10122 ) -> Option<(Anchor, Breakpoint)> {
10123 let snapshot = self.snapshot(window, cx);
10124 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10125
10126 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10127 }
10128
10129 pub(crate) fn breakpoint_at_anchor(
10130 &self,
10131 breakpoint_position: Anchor,
10132 snapshot: &EditorSnapshot,
10133 cx: &mut Context<Self>,
10134 ) -> Option<(Anchor, Breakpoint)> {
10135 let project = self.project.clone()?;
10136
10137 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10138 snapshot
10139 .buffer_snapshot
10140 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10141 })?;
10142
10143 let enclosing_excerpt = breakpoint_position.excerpt_id;
10144 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10145 let buffer_snapshot = buffer.read(cx).snapshot();
10146
10147 let row = buffer_snapshot
10148 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10149 .row;
10150
10151 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10152 let anchor_end = snapshot
10153 .buffer_snapshot
10154 .anchor_after(Point::new(row, line_len));
10155
10156 let bp = self
10157 .breakpoint_store
10158 .as_ref()?
10159 .read_with(cx, |breakpoint_store, cx| {
10160 breakpoint_store
10161 .breakpoints(
10162 &buffer,
10163 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10164 &buffer_snapshot,
10165 cx,
10166 )
10167 .next()
10168 .and_then(|(bp, _)| {
10169 let breakpoint_row = buffer_snapshot
10170 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10171 .row;
10172
10173 if breakpoint_row == row {
10174 snapshot
10175 .buffer_snapshot
10176 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10177 .map(|position| (position, bp.bp.clone()))
10178 } else {
10179 None
10180 }
10181 })
10182 });
10183 bp
10184 }
10185
10186 pub fn edit_log_breakpoint(
10187 &mut self,
10188 _: &EditLogBreakpoint,
10189 window: &mut Window,
10190 cx: &mut Context<Self>,
10191 ) {
10192 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10193 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10194 message: None,
10195 state: BreakpointState::Enabled,
10196 condition: None,
10197 hit_condition: None,
10198 });
10199
10200 self.add_edit_breakpoint_block(
10201 anchor,
10202 &breakpoint,
10203 BreakpointPromptEditAction::Log,
10204 window,
10205 cx,
10206 );
10207 }
10208 }
10209
10210 fn breakpoints_at_cursors(
10211 &self,
10212 window: &mut Window,
10213 cx: &mut Context<Self>,
10214 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10215 let snapshot = self.snapshot(window, cx);
10216 let cursors = self
10217 .selections
10218 .disjoint_anchors()
10219 .into_iter()
10220 .map(|selection| {
10221 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10222
10223 let breakpoint_position = self
10224 .breakpoint_at_row(cursor_position.row, window, cx)
10225 .map(|bp| bp.0)
10226 .unwrap_or_else(|| {
10227 snapshot
10228 .display_snapshot
10229 .buffer_snapshot
10230 .anchor_after(Point::new(cursor_position.row, 0))
10231 });
10232
10233 let breakpoint = self
10234 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10235 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10236
10237 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10238 })
10239 // 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.
10240 .collect::<HashMap<Anchor, _>>();
10241
10242 cursors.into_iter().collect()
10243 }
10244
10245 pub fn enable_breakpoint(
10246 &mut self,
10247 _: &crate::actions::EnableBreakpoint,
10248 window: &mut Window,
10249 cx: &mut Context<Self>,
10250 ) {
10251 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10252 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10253 continue;
10254 };
10255 self.edit_breakpoint_at_anchor(
10256 anchor,
10257 breakpoint,
10258 BreakpointEditAction::InvertState,
10259 cx,
10260 );
10261 }
10262 }
10263
10264 pub fn disable_breakpoint(
10265 &mut self,
10266 _: &crate::actions::DisableBreakpoint,
10267 window: &mut Window,
10268 cx: &mut Context<Self>,
10269 ) {
10270 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10271 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10272 continue;
10273 };
10274 self.edit_breakpoint_at_anchor(
10275 anchor,
10276 breakpoint,
10277 BreakpointEditAction::InvertState,
10278 cx,
10279 );
10280 }
10281 }
10282
10283 pub fn toggle_breakpoint(
10284 &mut self,
10285 _: &crate::actions::ToggleBreakpoint,
10286 window: &mut Window,
10287 cx: &mut Context<Self>,
10288 ) {
10289 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10290 if let Some(breakpoint) = breakpoint {
10291 self.edit_breakpoint_at_anchor(
10292 anchor,
10293 breakpoint,
10294 BreakpointEditAction::Toggle,
10295 cx,
10296 );
10297 } else {
10298 self.edit_breakpoint_at_anchor(
10299 anchor,
10300 Breakpoint::new_standard(),
10301 BreakpointEditAction::Toggle,
10302 cx,
10303 );
10304 }
10305 }
10306 }
10307
10308 pub fn edit_breakpoint_at_anchor(
10309 &mut self,
10310 breakpoint_position: Anchor,
10311 breakpoint: Breakpoint,
10312 edit_action: BreakpointEditAction,
10313 cx: &mut Context<Self>,
10314 ) {
10315 let Some(breakpoint_store) = &self.breakpoint_store else {
10316 return;
10317 };
10318
10319 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10320 if breakpoint_position == Anchor::min() {
10321 self.buffer()
10322 .read(cx)
10323 .excerpt_buffer_ids()
10324 .into_iter()
10325 .next()
10326 } else {
10327 None
10328 }
10329 }) else {
10330 return;
10331 };
10332
10333 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10334 return;
10335 };
10336
10337 breakpoint_store.update(cx, |breakpoint_store, cx| {
10338 breakpoint_store.toggle_breakpoint(
10339 buffer,
10340 BreakpointWithPosition {
10341 position: breakpoint_position.text_anchor,
10342 bp: breakpoint,
10343 },
10344 edit_action,
10345 cx,
10346 );
10347 });
10348
10349 cx.notify();
10350 }
10351
10352 #[cfg(any(test, feature = "test-support"))]
10353 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10354 self.breakpoint_store.clone()
10355 }
10356
10357 pub fn prepare_restore_change(
10358 &self,
10359 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10360 hunk: &MultiBufferDiffHunk,
10361 cx: &mut App,
10362 ) -> Option<()> {
10363 if hunk.is_created_file() {
10364 return None;
10365 }
10366 let buffer = self.buffer.read(cx);
10367 let diff = buffer.diff_for(hunk.buffer_id)?;
10368 let buffer = buffer.buffer(hunk.buffer_id)?;
10369 let buffer = buffer.read(cx);
10370 let original_text = diff
10371 .read(cx)
10372 .base_text()
10373 .as_rope()
10374 .slice(hunk.diff_base_byte_range.clone());
10375 let buffer_snapshot = buffer.snapshot();
10376 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10377 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10378 probe
10379 .0
10380 .start
10381 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10382 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10383 }) {
10384 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10385 Some(())
10386 } else {
10387 None
10388 }
10389 }
10390
10391 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10392 self.manipulate_lines(window, cx, |lines| lines.reverse())
10393 }
10394
10395 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10396 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10397 }
10398
10399 fn manipulate_lines<Fn>(
10400 &mut self,
10401 window: &mut Window,
10402 cx: &mut Context<Self>,
10403 mut callback: Fn,
10404 ) where
10405 Fn: FnMut(&mut Vec<&str>),
10406 {
10407 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10408
10409 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10410 let buffer = self.buffer.read(cx).snapshot(cx);
10411
10412 let mut edits = Vec::new();
10413
10414 let selections = self.selections.all::<Point>(cx);
10415 let mut selections = selections.iter().peekable();
10416 let mut contiguous_row_selections = Vec::new();
10417 let mut new_selections = Vec::new();
10418 let mut added_lines = 0;
10419 let mut removed_lines = 0;
10420
10421 while let Some(selection) = selections.next() {
10422 let (start_row, end_row) = consume_contiguous_rows(
10423 &mut contiguous_row_selections,
10424 selection,
10425 &display_map,
10426 &mut selections,
10427 );
10428
10429 let start_point = Point::new(start_row.0, 0);
10430 let end_point = Point::new(
10431 end_row.previous_row().0,
10432 buffer.line_len(end_row.previous_row()),
10433 );
10434 let text = buffer
10435 .text_for_range(start_point..end_point)
10436 .collect::<String>();
10437
10438 let mut lines = text.split('\n').collect_vec();
10439
10440 let lines_before = lines.len();
10441 callback(&mut lines);
10442 let lines_after = lines.len();
10443
10444 edits.push((start_point..end_point, lines.join("\n")));
10445
10446 // Selections must change based on added and removed line count
10447 let start_row =
10448 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10449 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10450 new_selections.push(Selection {
10451 id: selection.id,
10452 start: start_row,
10453 end: end_row,
10454 goal: SelectionGoal::None,
10455 reversed: selection.reversed,
10456 });
10457
10458 if lines_after > lines_before {
10459 added_lines += lines_after - lines_before;
10460 } else if lines_before > lines_after {
10461 removed_lines += lines_before - lines_after;
10462 }
10463 }
10464
10465 self.transact(window, cx, |this, window, cx| {
10466 let buffer = this.buffer.update(cx, |buffer, cx| {
10467 buffer.edit(edits, None, cx);
10468 buffer.snapshot(cx)
10469 });
10470
10471 // Recalculate offsets on newly edited buffer
10472 let new_selections = new_selections
10473 .iter()
10474 .map(|s| {
10475 let start_point = Point::new(s.start.0, 0);
10476 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10477 Selection {
10478 id: s.id,
10479 start: buffer.point_to_offset(start_point),
10480 end: buffer.point_to_offset(end_point),
10481 goal: s.goal,
10482 reversed: s.reversed,
10483 }
10484 })
10485 .collect();
10486
10487 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10488 s.select(new_selections);
10489 });
10490
10491 this.request_autoscroll(Autoscroll::fit(), cx);
10492 });
10493 }
10494
10495 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10496 self.manipulate_text(window, cx, |text| {
10497 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10498 if has_upper_case_characters {
10499 text.to_lowercase()
10500 } else {
10501 text.to_uppercase()
10502 }
10503 })
10504 }
10505
10506 pub fn convert_to_upper_case(
10507 &mut self,
10508 _: &ConvertToUpperCase,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.manipulate_text(window, cx, |text| text.to_uppercase())
10513 }
10514
10515 pub fn convert_to_lower_case(
10516 &mut self,
10517 _: &ConvertToLowerCase,
10518 window: &mut Window,
10519 cx: &mut Context<Self>,
10520 ) {
10521 self.manipulate_text(window, cx, |text| text.to_lowercase())
10522 }
10523
10524 pub fn convert_to_title_case(
10525 &mut self,
10526 _: &ConvertToTitleCase,
10527 window: &mut Window,
10528 cx: &mut Context<Self>,
10529 ) {
10530 self.manipulate_text(window, cx, |text| {
10531 text.split('\n')
10532 .map(|line| line.to_case(Case::Title))
10533 .join("\n")
10534 })
10535 }
10536
10537 pub fn convert_to_snake_case(
10538 &mut self,
10539 _: &ConvertToSnakeCase,
10540 window: &mut Window,
10541 cx: &mut Context<Self>,
10542 ) {
10543 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10544 }
10545
10546 pub fn convert_to_kebab_case(
10547 &mut self,
10548 _: &ConvertToKebabCase,
10549 window: &mut Window,
10550 cx: &mut Context<Self>,
10551 ) {
10552 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10553 }
10554
10555 pub fn convert_to_upper_camel_case(
10556 &mut self,
10557 _: &ConvertToUpperCamelCase,
10558 window: &mut Window,
10559 cx: &mut Context<Self>,
10560 ) {
10561 self.manipulate_text(window, cx, |text| {
10562 text.split('\n')
10563 .map(|line| line.to_case(Case::UpperCamel))
10564 .join("\n")
10565 })
10566 }
10567
10568 pub fn convert_to_lower_camel_case(
10569 &mut self,
10570 _: &ConvertToLowerCamelCase,
10571 window: &mut Window,
10572 cx: &mut Context<Self>,
10573 ) {
10574 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10575 }
10576
10577 pub fn convert_to_opposite_case(
10578 &mut self,
10579 _: &ConvertToOppositeCase,
10580 window: &mut Window,
10581 cx: &mut Context<Self>,
10582 ) {
10583 self.manipulate_text(window, cx, |text| {
10584 text.chars()
10585 .fold(String::with_capacity(text.len()), |mut t, c| {
10586 if c.is_uppercase() {
10587 t.extend(c.to_lowercase());
10588 } else {
10589 t.extend(c.to_uppercase());
10590 }
10591 t
10592 })
10593 })
10594 }
10595
10596 pub fn convert_to_rot13(
10597 &mut self,
10598 _: &ConvertToRot13,
10599 window: &mut Window,
10600 cx: &mut Context<Self>,
10601 ) {
10602 self.manipulate_text(window, cx, |text| {
10603 text.chars()
10604 .map(|c| match c {
10605 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10606 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10607 _ => c,
10608 })
10609 .collect()
10610 })
10611 }
10612
10613 pub fn convert_to_rot47(
10614 &mut self,
10615 _: &ConvertToRot47,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) {
10619 self.manipulate_text(window, cx, |text| {
10620 text.chars()
10621 .map(|c| {
10622 let code_point = c as u32;
10623 if code_point >= 33 && code_point <= 126 {
10624 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10625 }
10626 c
10627 })
10628 .collect()
10629 })
10630 }
10631
10632 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10633 where
10634 Fn: FnMut(&str) -> String,
10635 {
10636 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10637 let buffer = self.buffer.read(cx).snapshot(cx);
10638
10639 let mut new_selections = Vec::new();
10640 let mut edits = Vec::new();
10641 let mut selection_adjustment = 0i32;
10642
10643 for selection in self.selections.all::<usize>(cx) {
10644 let selection_is_empty = selection.is_empty();
10645
10646 let (start, end) = if selection_is_empty {
10647 let word_range = movement::surrounding_word(
10648 &display_map,
10649 selection.start.to_display_point(&display_map),
10650 );
10651 let start = word_range.start.to_offset(&display_map, Bias::Left);
10652 let end = word_range.end.to_offset(&display_map, Bias::Left);
10653 (start, end)
10654 } else {
10655 (selection.start, selection.end)
10656 };
10657
10658 let text = buffer.text_for_range(start..end).collect::<String>();
10659 let old_length = text.len() as i32;
10660 let text = callback(&text);
10661
10662 new_selections.push(Selection {
10663 start: (start as i32 - selection_adjustment) as usize,
10664 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10665 goal: SelectionGoal::None,
10666 ..selection
10667 });
10668
10669 selection_adjustment += old_length - text.len() as i32;
10670
10671 edits.push((start..end, text));
10672 }
10673
10674 self.transact(window, cx, |this, window, cx| {
10675 this.buffer.update(cx, |buffer, cx| {
10676 buffer.edit(edits, None, cx);
10677 });
10678
10679 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10680 s.select(new_selections);
10681 });
10682
10683 this.request_autoscroll(Autoscroll::fit(), cx);
10684 });
10685 }
10686
10687 pub fn move_selection_on_drop(
10688 &mut self,
10689 selection: &Selection<Anchor>,
10690 target: DisplayPoint,
10691 is_cut: bool,
10692 window: &mut Window,
10693 cx: &mut Context<Self>,
10694 ) {
10695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10696 let buffer = &display_map.buffer_snapshot;
10697 let mut edits = Vec::new();
10698 let insert_point = display_map
10699 .clip_point(target, Bias::Left)
10700 .to_point(&display_map);
10701 let text = buffer
10702 .text_for_range(selection.start..selection.end)
10703 .collect::<String>();
10704 if is_cut {
10705 edits.push(((selection.start..selection.end), String::new()));
10706 }
10707 let insert_anchor = buffer.anchor_before(insert_point);
10708 edits.push(((insert_anchor..insert_anchor), text));
10709 let last_edit_start = insert_anchor.bias_left(buffer);
10710 let last_edit_end = insert_anchor.bias_right(buffer);
10711 self.transact(window, cx, |this, window, cx| {
10712 this.buffer.update(cx, |buffer, cx| {
10713 buffer.edit(edits, None, cx);
10714 });
10715 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10716 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10717 });
10718 });
10719 }
10720
10721 pub fn clear_selection_drag_state(&mut self) {
10722 self.selection_drag_state = SelectionDragState::None;
10723 }
10724
10725 pub fn duplicate(
10726 &mut self,
10727 upwards: bool,
10728 whole_lines: bool,
10729 window: &mut Window,
10730 cx: &mut Context<Self>,
10731 ) {
10732 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10733
10734 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10735 let buffer = &display_map.buffer_snapshot;
10736 let selections = self.selections.all::<Point>(cx);
10737
10738 let mut edits = Vec::new();
10739 let mut selections_iter = selections.iter().peekable();
10740 while let Some(selection) = selections_iter.next() {
10741 let mut rows = selection.spanned_rows(false, &display_map);
10742 // duplicate line-wise
10743 if whole_lines || selection.start == selection.end {
10744 // Avoid duplicating the same lines twice.
10745 while let Some(next_selection) = selections_iter.peek() {
10746 let next_rows = next_selection.spanned_rows(false, &display_map);
10747 if next_rows.start < rows.end {
10748 rows.end = next_rows.end;
10749 selections_iter.next().unwrap();
10750 } else {
10751 break;
10752 }
10753 }
10754
10755 // Copy the text from the selected row region and splice it either at the start
10756 // or end of the region.
10757 let start = Point::new(rows.start.0, 0);
10758 let end = Point::new(
10759 rows.end.previous_row().0,
10760 buffer.line_len(rows.end.previous_row()),
10761 );
10762 let text = buffer
10763 .text_for_range(start..end)
10764 .chain(Some("\n"))
10765 .collect::<String>();
10766 let insert_location = if upwards {
10767 Point::new(rows.end.0, 0)
10768 } else {
10769 start
10770 };
10771 edits.push((insert_location..insert_location, text));
10772 } else {
10773 // duplicate character-wise
10774 let start = selection.start;
10775 let end = selection.end;
10776 let text = buffer.text_for_range(start..end).collect::<String>();
10777 edits.push((selection.end..selection.end, text));
10778 }
10779 }
10780
10781 self.transact(window, cx, |this, _, cx| {
10782 this.buffer.update(cx, |buffer, cx| {
10783 buffer.edit(edits, None, cx);
10784 });
10785
10786 this.request_autoscroll(Autoscroll::fit(), cx);
10787 });
10788 }
10789
10790 pub fn duplicate_line_up(
10791 &mut self,
10792 _: &DuplicateLineUp,
10793 window: &mut Window,
10794 cx: &mut Context<Self>,
10795 ) {
10796 self.duplicate(true, true, window, cx);
10797 }
10798
10799 pub fn duplicate_line_down(
10800 &mut self,
10801 _: &DuplicateLineDown,
10802 window: &mut Window,
10803 cx: &mut Context<Self>,
10804 ) {
10805 self.duplicate(false, true, window, cx);
10806 }
10807
10808 pub fn duplicate_selection(
10809 &mut self,
10810 _: &DuplicateSelection,
10811 window: &mut Window,
10812 cx: &mut Context<Self>,
10813 ) {
10814 self.duplicate(false, false, window, cx);
10815 }
10816
10817 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10818 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10819
10820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10821 let buffer = self.buffer.read(cx).snapshot(cx);
10822
10823 let mut edits = Vec::new();
10824 let mut unfold_ranges = Vec::new();
10825 let mut refold_creases = Vec::new();
10826
10827 let selections = self.selections.all::<Point>(cx);
10828 let mut selections = selections.iter().peekable();
10829 let mut contiguous_row_selections = Vec::new();
10830 let mut new_selections = Vec::new();
10831
10832 while let Some(selection) = selections.next() {
10833 // Find all the selections that span a contiguous row range
10834 let (start_row, end_row) = consume_contiguous_rows(
10835 &mut contiguous_row_selections,
10836 selection,
10837 &display_map,
10838 &mut selections,
10839 );
10840
10841 // Move the text spanned by the row range to be before the line preceding the row range
10842 if start_row.0 > 0 {
10843 let range_to_move = Point::new(
10844 start_row.previous_row().0,
10845 buffer.line_len(start_row.previous_row()),
10846 )
10847 ..Point::new(
10848 end_row.previous_row().0,
10849 buffer.line_len(end_row.previous_row()),
10850 );
10851 let insertion_point = display_map
10852 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10853 .0;
10854
10855 // Don't move lines across excerpts
10856 if buffer
10857 .excerpt_containing(insertion_point..range_to_move.end)
10858 .is_some()
10859 {
10860 let text = buffer
10861 .text_for_range(range_to_move.clone())
10862 .flat_map(|s| s.chars())
10863 .skip(1)
10864 .chain(['\n'])
10865 .collect::<String>();
10866
10867 edits.push((
10868 buffer.anchor_after(range_to_move.start)
10869 ..buffer.anchor_before(range_to_move.end),
10870 String::new(),
10871 ));
10872 let insertion_anchor = buffer.anchor_after(insertion_point);
10873 edits.push((insertion_anchor..insertion_anchor, text));
10874
10875 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10876
10877 // Move selections up
10878 new_selections.extend(contiguous_row_selections.drain(..).map(
10879 |mut selection| {
10880 selection.start.row -= row_delta;
10881 selection.end.row -= row_delta;
10882 selection
10883 },
10884 ));
10885
10886 // Move folds up
10887 unfold_ranges.push(range_to_move.clone());
10888 for fold in display_map.folds_in_range(
10889 buffer.anchor_before(range_to_move.start)
10890 ..buffer.anchor_after(range_to_move.end),
10891 ) {
10892 let mut start = fold.range.start.to_point(&buffer);
10893 let mut end = fold.range.end.to_point(&buffer);
10894 start.row -= row_delta;
10895 end.row -= row_delta;
10896 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10897 }
10898 }
10899 }
10900
10901 // If we didn't move line(s), preserve the existing selections
10902 new_selections.append(&mut contiguous_row_selections);
10903 }
10904
10905 self.transact(window, cx, |this, window, cx| {
10906 this.unfold_ranges(&unfold_ranges, true, true, cx);
10907 this.buffer.update(cx, |buffer, cx| {
10908 for (range, text) in edits {
10909 buffer.edit([(range, text)], None, cx);
10910 }
10911 });
10912 this.fold_creases(refold_creases, true, window, cx);
10913 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10914 s.select(new_selections);
10915 })
10916 });
10917 }
10918
10919 pub fn move_line_down(
10920 &mut self,
10921 _: &MoveLineDown,
10922 window: &mut Window,
10923 cx: &mut Context<Self>,
10924 ) {
10925 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10926
10927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10928 let buffer = self.buffer.read(cx).snapshot(cx);
10929
10930 let mut edits = Vec::new();
10931 let mut unfold_ranges = Vec::new();
10932 let mut refold_creases = Vec::new();
10933
10934 let selections = self.selections.all::<Point>(cx);
10935 let mut selections = selections.iter().peekable();
10936 let mut contiguous_row_selections = Vec::new();
10937 let mut new_selections = Vec::new();
10938
10939 while let Some(selection) = selections.next() {
10940 // Find all the selections that span a contiguous row range
10941 let (start_row, end_row) = consume_contiguous_rows(
10942 &mut contiguous_row_selections,
10943 selection,
10944 &display_map,
10945 &mut selections,
10946 );
10947
10948 // Move the text spanned by the row range to be after the last line of the row range
10949 if end_row.0 <= buffer.max_point().row {
10950 let range_to_move =
10951 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10952 let insertion_point = display_map
10953 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10954 .0;
10955
10956 // Don't move lines across excerpt boundaries
10957 if buffer
10958 .excerpt_containing(range_to_move.start..insertion_point)
10959 .is_some()
10960 {
10961 let mut text = String::from("\n");
10962 text.extend(buffer.text_for_range(range_to_move.clone()));
10963 text.pop(); // Drop trailing newline
10964 edits.push((
10965 buffer.anchor_after(range_to_move.start)
10966 ..buffer.anchor_before(range_to_move.end),
10967 String::new(),
10968 ));
10969 let insertion_anchor = buffer.anchor_after(insertion_point);
10970 edits.push((insertion_anchor..insertion_anchor, text));
10971
10972 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10973
10974 // Move selections down
10975 new_selections.extend(contiguous_row_selections.drain(..).map(
10976 |mut selection| {
10977 selection.start.row += row_delta;
10978 selection.end.row += row_delta;
10979 selection
10980 },
10981 ));
10982
10983 // Move folds down
10984 unfold_ranges.push(range_to_move.clone());
10985 for fold in display_map.folds_in_range(
10986 buffer.anchor_before(range_to_move.start)
10987 ..buffer.anchor_after(range_to_move.end),
10988 ) {
10989 let mut start = fold.range.start.to_point(&buffer);
10990 let mut end = fold.range.end.to_point(&buffer);
10991 start.row += row_delta;
10992 end.row += row_delta;
10993 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10994 }
10995 }
10996 }
10997
10998 // If we didn't move line(s), preserve the existing selections
10999 new_selections.append(&mut contiguous_row_selections);
11000 }
11001
11002 self.transact(window, cx, |this, window, cx| {
11003 this.unfold_ranges(&unfold_ranges, true, true, cx);
11004 this.buffer.update(cx, |buffer, cx| {
11005 for (range, text) in edits {
11006 buffer.edit([(range, text)], None, cx);
11007 }
11008 });
11009 this.fold_creases(refold_creases, true, window, cx);
11010 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11011 s.select(new_selections)
11012 });
11013 });
11014 }
11015
11016 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11017 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11018 let text_layout_details = &self.text_layout_details(window);
11019 self.transact(window, cx, |this, window, cx| {
11020 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11021 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11022 s.move_with(|display_map, selection| {
11023 if !selection.is_empty() {
11024 return;
11025 }
11026
11027 let mut head = selection.head();
11028 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11029 if head.column() == display_map.line_len(head.row()) {
11030 transpose_offset = display_map
11031 .buffer_snapshot
11032 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11033 }
11034
11035 if transpose_offset == 0 {
11036 return;
11037 }
11038
11039 *head.column_mut() += 1;
11040 head = display_map.clip_point(head, Bias::Right);
11041 let goal = SelectionGoal::HorizontalPosition(
11042 display_map
11043 .x_for_display_point(head, text_layout_details)
11044 .into(),
11045 );
11046 selection.collapse_to(head, goal);
11047
11048 let transpose_start = display_map
11049 .buffer_snapshot
11050 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11051 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11052 let transpose_end = display_map
11053 .buffer_snapshot
11054 .clip_offset(transpose_offset + 1, Bias::Right);
11055 if let Some(ch) =
11056 display_map.buffer_snapshot.chars_at(transpose_start).next()
11057 {
11058 edits.push((transpose_start..transpose_offset, String::new()));
11059 edits.push((transpose_end..transpose_end, ch.to_string()));
11060 }
11061 }
11062 });
11063 edits
11064 });
11065 this.buffer
11066 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11067 let selections = this.selections.all::<usize>(cx);
11068 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11069 s.select(selections);
11070 });
11071 });
11072 }
11073
11074 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11075 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11076 self.rewrap_impl(RewrapOptions::default(), cx)
11077 }
11078
11079 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11080 let buffer = self.buffer.read(cx).snapshot(cx);
11081 let selections = self.selections.all::<Point>(cx);
11082
11083 // Shrink and split selections to respect paragraph boundaries.
11084 let ranges = selections.into_iter().flat_map(|selection| {
11085 let language_settings = buffer.language_settings_at(selection.head(), cx);
11086 let language_scope = buffer.language_scope_at(selection.head());
11087
11088 let Some(start_row) = (selection.start.row..=selection.end.row)
11089 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11090 else {
11091 return vec![];
11092 };
11093 let Some(end_row) = (selection.start.row..=selection.end.row)
11094 .rev()
11095 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11096 else {
11097 return vec![];
11098 };
11099
11100 let mut row = start_row;
11101 let mut ranges = Vec::new();
11102 while let Some(blank_row) =
11103 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11104 {
11105 let next_paragraph_start = (blank_row + 1..=end_row)
11106 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11107 .unwrap();
11108 ranges.push((
11109 language_settings.clone(),
11110 language_scope.clone(),
11111 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11112 ));
11113 row = next_paragraph_start;
11114 }
11115 ranges.push((
11116 language_settings.clone(),
11117 language_scope.clone(),
11118 Point::new(row, 0)..Point::new(end_row, 0),
11119 ));
11120
11121 ranges
11122 });
11123
11124 let mut edits = Vec::new();
11125 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11126
11127 for (language_settings, language_scope, range) in ranges {
11128 let mut start_row = range.start.row;
11129 let mut end_row = range.end.row;
11130
11131 // Skip selections that overlap with a range that has already been rewrapped.
11132 let selection_range = start_row..end_row;
11133 if rewrapped_row_ranges
11134 .iter()
11135 .any(|range| range.overlaps(&selection_range))
11136 {
11137 continue;
11138 }
11139
11140 let tab_size = language_settings.tab_size;
11141
11142 // Since not all lines in the selection may be at the same indent
11143 // level, choose the indent size that is the most common between all
11144 // of the lines.
11145 //
11146 // If there is a tie, we use the deepest indent.
11147 let (indent_size, indent_end) = {
11148 let mut indent_size_occurrences = HashMap::default();
11149 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11150
11151 for row in start_row..=end_row {
11152 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11153 rows_by_indent_size.entry(indent).or_default().push(row);
11154 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11155 }
11156
11157 let indent_size = indent_size_occurrences
11158 .into_iter()
11159 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11160 .map(|(indent, _)| indent)
11161 .unwrap_or_default();
11162 let row = rows_by_indent_size[&indent_size][0];
11163 let indent_end = Point::new(row, indent_size.len);
11164
11165 (indent_size, indent_end)
11166 };
11167
11168 let mut line_prefix = indent_size.chars().collect::<String>();
11169
11170 let mut inside_comment = false;
11171 if let Some(comment_prefix) = language_scope.and_then(|language| {
11172 language
11173 .line_comment_prefixes()
11174 .iter()
11175 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11176 .cloned()
11177 }) {
11178 line_prefix.push_str(&comment_prefix);
11179 inside_comment = true;
11180 }
11181
11182 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11183 RewrapBehavior::InComments => inside_comment,
11184 RewrapBehavior::InSelections => !range.is_empty(),
11185 RewrapBehavior::Anywhere => true,
11186 };
11187
11188 let should_rewrap = options.override_language_settings
11189 || allow_rewrap_based_on_language
11190 || self.hard_wrap.is_some();
11191 if !should_rewrap {
11192 continue;
11193 }
11194
11195 if range.is_empty() {
11196 'expand_upwards: while start_row > 0 {
11197 let prev_row = start_row - 1;
11198 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11199 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11200 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11201 {
11202 start_row = prev_row;
11203 } else {
11204 break 'expand_upwards;
11205 }
11206 }
11207
11208 'expand_downwards: while end_row < buffer.max_point().row {
11209 let next_row = end_row + 1;
11210 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11211 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11212 && !buffer.is_line_blank(MultiBufferRow(next_row))
11213 {
11214 end_row = next_row;
11215 } else {
11216 break 'expand_downwards;
11217 }
11218 }
11219 }
11220
11221 let start = Point::new(start_row, 0);
11222 let start_offset = start.to_offset(&buffer);
11223 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11224 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11225 let Some(lines_without_prefixes) = selection_text
11226 .lines()
11227 .map(|line| {
11228 line.strip_prefix(&line_prefix)
11229 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11230 .with_context(|| {
11231 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11232 })
11233 })
11234 .collect::<Result<Vec<_>, _>>()
11235 .log_err()
11236 else {
11237 continue;
11238 };
11239
11240 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11241 buffer
11242 .language_settings_at(Point::new(start_row, 0), cx)
11243 .preferred_line_length as usize
11244 });
11245 let wrapped_text = wrap_with_prefix(
11246 line_prefix,
11247 lines_without_prefixes.join("\n"),
11248 wrap_column,
11249 tab_size,
11250 options.preserve_existing_whitespace,
11251 );
11252
11253 // TODO: should always use char-based diff while still supporting cursor behavior that
11254 // matches vim.
11255 let mut diff_options = DiffOptions::default();
11256 if options.override_language_settings {
11257 diff_options.max_word_diff_len = 0;
11258 diff_options.max_word_diff_line_count = 0;
11259 } else {
11260 diff_options.max_word_diff_len = usize::MAX;
11261 diff_options.max_word_diff_line_count = usize::MAX;
11262 }
11263
11264 for (old_range, new_text) in
11265 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11266 {
11267 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11268 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11269 edits.push((edit_start..edit_end, new_text));
11270 }
11271
11272 rewrapped_row_ranges.push(start_row..=end_row);
11273 }
11274
11275 self.buffer
11276 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11277 }
11278
11279 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11280 let mut text = String::new();
11281 let buffer = self.buffer.read(cx).snapshot(cx);
11282 let mut selections = self.selections.all::<Point>(cx);
11283 let mut clipboard_selections = Vec::with_capacity(selections.len());
11284 {
11285 let max_point = buffer.max_point();
11286 let mut is_first = true;
11287 for selection in &mut selections {
11288 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11289 if is_entire_line {
11290 selection.start = Point::new(selection.start.row, 0);
11291 if !selection.is_empty() && selection.end.column == 0 {
11292 selection.end = cmp::min(max_point, selection.end);
11293 } else {
11294 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11295 }
11296 selection.goal = SelectionGoal::None;
11297 }
11298 if is_first {
11299 is_first = false;
11300 } else {
11301 text += "\n";
11302 }
11303 let mut len = 0;
11304 for chunk in buffer.text_for_range(selection.start..selection.end) {
11305 text.push_str(chunk);
11306 len += chunk.len();
11307 }
11308 clipboard_selections.push(ClipboardSelection {
11309 len,
11310 is_entire_line,
11311 first_line_indent: buffer
11312 .indent_size_for_line(MultiBufferRow(selection.start.row))
11313 .len,
11314 });
11315 }
11316 }
11317
11318 self.transact(window, cx, |this, window, cx| {
11319 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11320 s.select(selections);
11321 });
11322 this.insert("", window, cx);
11323 });
11324 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11325 }
11326
11327 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11328 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11329 let item = self.cut_common(window, cx);
11330 cx.write_to_clipboard(item);
11331 }
11332
11333 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11334 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11335 self.change_selections(None, window, cx, |s| {
11336 s.move_with(|snapshot, sel| {
11337 if sel.is_empty() {
11338 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11339 }
11340 });
11341 });
11342 let item = self.cut_common(window, cx);
11343 cx.set_global(KillRing(item))
11344 }
11345
11346 pub fn kill_ring_yank(
11347 &mut self,
11348 _: &KillRingYank,
11349 window: &mut Window,
11350 cx: &mut Context<Self>,
11351 ) {
11352 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11353 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11354 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11355 (kill_ring.text().to_string(), kill_ring.metadata_json())
11356 } else {
11357 return;
11358 }
11359 } else {
11360 return;
11361 };
11362 self.do_paste(&text, metadata, false, window, cx);
11363 }
11364
11365 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11366 self.do_copy(true, cx);
11367 }
11368
11369 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11370 self.do_copy(false, cx);
11371 }
11372
11373 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11374 let selections = self.selections.all::<Point>(cx);
11375 let buffer = self.buffer.read(cx).read(cx);
11376 let mut text = String::new();
11377
11378 let mut clipboard_selections = Vec::with_capacity(selections.len());
11379 {
11380 let max_point = buffer.max_point();
11381 let mut is_first = true;
11382 for selection in &selections {
11383 let mut start = selection.start;
11384 let mut end = selection.end;
11385 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11386 if is_entire_line {
11387 start = Point::new(start.row, 0);
11388 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11389 }
11390
11391 let mut trimmed_selections = Vec::new();
11392 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11393 let row = MultiBufferRow(start.row);
11394 let first_indent = buffer.indent_size_for_line(row);
11395 if first_indent.len == 0 || start.column > first_indent.len {
11396 trimmed_selections.push(start..end);
11397 } else {
11398 trimmed_selections.push(
11399 Point::new(row.0, first_indent.len)
11400 ..Point::new(row.0, buffer.line_len(row)),
11401 );
11402 for row in start.row + 1..=end.row {
11403 let mut line_len = buffer.line_len(MultiBufferRow(row));
11404 if row == end.row {
11405 line_len = end.column;
11406 }
11407 if line_len == 0 {
11408 trimmed_selections
11409 .push(Point::new(row, 0)..Point::new(row, line_len));
11410 continue;
11411 }
11412 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11413 if row_indent_size.len >= first_indent.len {
11414 trimmed_selections.push(
11415 Point::new(row, first_indent.len)..Point::new(row, line_len),
11416 );
11417 } else {
11418 trimmed_selections.clear();
11419 trimmed_selections.push(start..end);
11420 break;
11421 }
11422 }
11423 }
11424 } else {
11425 trimmed_selections.push(start..end);
11426 }
11427
11428 for trimmed_range in trimmed_selections {
11429 if is_first {
11430 is_first = false;
11431 } else {
11432 text += "\n";
11433 }
11434 let mut len = 0;
11435 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11436 text.push_str(chunk);
11437 len += chunk.len();
11438 }
11439 clipboard_selections.push(ClipboardSelection {
11440 len,
11441 is_entire_line,
11442 first_line_indent: buffer
11443 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11444 .len,
11445 });
11446 }
11447 }
11448 }
11449
11450 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11451 text,
11452 clipboard_selections,
11453 ));
11454 }
11455
11456 pub fn do_paste(
11457 &mut self,
11458 text: &String,
11459 clipboard_selections: Option<Vec<ClipboardSelection>>,
11460 handle_entire_lines: bool,
11461 window: &mut Window,
11462 cx: &mut Context<Self>,
11463 ) {
11464 if self.read_only(cx) {
11465 return;
11466 }
11467
11468 let clipboard_text = Cow::Borrowed(text);
11469
11470 self.transact(window, cx, |this, window, cx| {
11471 if let Some(mut clipboard_selections) = clipboard_selections {
11472 let old_selections = this.selections.all::<usize>(cx);
11473 let all_selections_were_entire_line =
11474 clipboard_selections.iter().all(|s| s.is_entire_line);
11475 let first_selection_indent_column =
11476 clipboard_selections.first().map(|s| s.first_line_indent);
11477 if clipboard_selections.len() != old_selections.len() {
11478 clipboard_selections.drain(..);
11479 }
11480 let cursor_offset = this.selections.last::<usize>(cx).head();
11481 let mut auto_indent_on_paste = true;
11482
11483 this.buffer.update(cx, |buffer, cx| {
11484 let snapshot = buffer.read(cx);
11485 auto_indent_on_paste = snapshot
11486 .language_settings_at(cursor_offset, cx)
11487 .auto_indent_on_paste;
11488
11489 let mut start_offset = 0;
11490 let mut edits = Vec::new();
11491 let mut original_indent_columns = Vec::new();
11492 for (ix, selection) in old_selections.iter().enumerate() {
11493 let to_insert;
11494 let entire_line;
11495 let original_indent_column;
11496 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11497 let end_offset = start_offset + clipboard_selection.len;
11498 to_insert = &clipboard_text[start_offset..end_offset];
11499 entire_line = clipboard_selection.is_entire_line;
11500 start_offset = end_offset + 1;
11501 original_indent_column = Some(clipboard_selection.first_line_indent);
11502 } else {
11503 to_insert = clipboard_text.as_str();
11504 entire_line = all_selections_were_entire_line;
11505 original_indent_column = first_selection_indent_column
11506 }
11507
11508 // If the corresponding selection was empty when this slice of the
11509 // clipboard text was written, then the entire line containing the
11510 // selection was copied. If this selection is also currently empty,
11511 // then paste the line before the current line of the buffer.
11512 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11513 let column = selection.start.to_point(&snapshot).column as usize;
11514 let line_start = selection.start - column;
11515 line_start..line_start
11516 } else {
11517 selection.range()
11518 };
11519
11520 edits.push((range, to_insert));
11521 original_indent_columns.push(original_indent_column);
11522 }
11523 drop(snapshot);
11524
11525 buffer.edit(
11526 edits,
11527 if auto_indent_on_paste {
11528 Some(AutoindentMode::Block {
11529 original_indent_columns,
11530 })
11531 } else {
11532 None
11533 },
11534 cx,
11535 );
11536 });
11537
11538 let selections = this.selections.all::<usize>(cx);
11539 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11540 s.select(selections)
11541 });
11542 } else {
11543 this.insert(&clipboard_text, window, cx);
11544 }
11545 });
11546 }
11547
11548 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11549 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11550 if let Some(item) = cx.read_from_clipboard() {
11551 let entries = item.entries();
11552
11553 match entries.first() {
11554 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11555 // of all the pasted entries.
11556 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11557 .do_paste(
11558 clipboard_string.text(),
11559 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11560 true,
11561 window,
11562 cx,
11563 ),
11564 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11565 }
11566 }
11567 }
11568
11569 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11570 if self.read_only(cx) {
11571 return;
11572 }
11573
11574 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11575
11576 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11577 if let Some((selections, _)) =
11578 self.selection_history.transaction(transaction_id).cloned()
11579 {
11580 self.change_selections(None, window, cx, |s| {
11581 s.select_anchors(selections.to_vec());
11582 });
11583 } else {
11584 log::error!(
11585 "No entry in selection_history found for undo. \
11586 This may correspond to a bug where undo does not update the selection. \
11587 If this is occurring, please add details to \
11588 https://github.com/zed-industries/zed/issues/22692"
11589 );
11590 }
11591 self.request_autoscroll(Autoscroll::fit(), cx);
11592 self.unmark_text(window, cx);
11593 self.refresh_inline_completion(true, false, window, cx);
11594 cx.emit(EditorEvent::Edited { transaction_id });
11595 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11596 }
11597 }
11598
11599 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11600 if self.read_only(cx) {
11601 return;
11602 }
11603
11604 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11605
11606 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11607 if let Some((_, Some(selections))) =
11608 self.selection_history.transaction(transaction_id).cloned()
11609 {
11610 self.change_selections(None, window, cx, |s| {
11611 s.select_anchors(selections.to_vec());
11612 });
11613 } else {
11614 log::error!(
11615 "No entry in selection_history found for redo. \
11616 This may correspond to a bug where undo does not update the selection. \
11617 If this is occurring, please add details to \
11618 https://github.com/zed-industries/zed/issues/22692"
11619 );
11620 }
11621 self.request_autoscroll(Autoscroll::fit(), cx);
11622 self.unmark_text(window, cx);
11623 self.refresh_inline_completion(true, false, window, cx);
11624 cx.emit(EditorEvent::Edited { transaction_id });
11625 }
11626 }
11627
11628 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11629 self.buffer
11630 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11631 }
11632
11633 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11634 self.buffer
11635 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11636 }
11637
11638 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11639 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11640 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11641 s.move_with(|map, selection| {
11642 let cursor = if selection.is_empty() {
11643 movement::left(map, selection.start)
11644 } else {
11645 selection.start
11646 };
11647 selection.collapse_to(cursor, SelectionGoal::None);
11648 });
11649 })
11650 }
11651
11652 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11653 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11654 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11655 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11656 })
11657 }
11658
11659 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11660 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11662 s.move_with(|map, selection| {
11663 let cursor = if selection.is_empty() {
11664 movement::right(map, selection.end)
11665 } else {
11666 selection.end
11667 };
11668 selection.collapse_to(cursor, SelectionGoal::None)
11669 });
11670 })
11671 }
11672
11673 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11674 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11675 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11676 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11677 })
11678 }
11679
11680 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11681 if self.take_rename(true, window, cx).is_some() {
11682 return;
11683 }
11684
11685 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11686 cx.propagate();
11687 return;
11688 }
11689
11690 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11691
11692 let text_layout_details = &self.text_layout_details(window);
11693 let selection_count = self.selections.count();
11694 let first_selection = self.selections.first_anchor();
11695
11696 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11697 s.move_with(|map, selection| {
11698 if !selection.is_empty() {
11699 selection.goal = SelectionGoal::None;
11700 }
11701 let (cursor, goal) = movement::up(
11702 map,
11703 selection.start,
11704 selection.goal,
11705 false,
11706 text_layout_details,
11707 );
11708 selection.collapse_to(cursor, goal);
11709 });
11710 });
11711
11712 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11713 {
11714 cx.propagate();
11715 }
11716 }
11717
11718 pub fn move_up_by_lines(
11719 &mut self,
11720 action: &MoveUpByLines,
11721 window: &mut Window,
11722 cx: &mut Context<Self>,
11723 ) {
11724 if self.take_rename(true, window, cx).is_some() {
11725 return;
11726 }
11727
11728 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11729 cx.propagate();
11730 return;
11731 }
11732
11733 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11734
11735 let text_layout_details = &self.text_layout_details(window);
11736
11737 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11738 s.move_with(|map, selection| {
11739 if !selection.is_empty() {
11740 selection.goal = SelectionGoal::None;
11741 }
11742 let (cursor, goal) = movement::up_by_rows(
11743 map,
11744 selection.start,
11745 action.lines,
11746 selection.goal,
11747 false,
11748 text_layout_details,
11749 );
11750 selection.collapse_to(cursor, goal);
11751 });
11752 })
11753 }
11754
11755 pub fn move_down_by_lines(
11756 &mut self,
11757 action: &MoveDownByLines,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 if self.take_rename(true, window, cx).is_some() {
11762 return;
11763 }
11764
11765 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11766 cx.propagate();
11767 return;
11768 }
11769
11770 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11771
11772 let text_layout_details = &self.text_layout_details(window);
11773
11774 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11775 s.move_with(|map, selection| {
11776 if !selection.is_empty() {
11777 selection.goal = SelectionGoal::None;
11778 }
11779 let (cursor, goal) = movement::down_by_rows(
11780 map,
11781 selection.start,
11782 action.lines,
11783 selection.goal,
11784 false,
11785 text_layout_details,
11786 );
11787 selection.collapse_to(cursor, goal);
11788 });
11789 })
11790 }
11791
11792 pub fn select_down_by_lines(
11793 &mut self,
11794 action: &SelectDownByLines,
11795 window: &mut Window,
11796 cx: &mut Context<Self>,
11797 ) {
11798 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11799 let text_layout_details = &self.text_layout_details(window);
11800 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11801 s.move_heads_with(|map, head, goal| {
11802 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11803 })
11804 })
11805 }
11806
11807 pub fn select_up_by_lines(
11808 &mut self,
11809 action: &SelectUpByLines,
11810 window: &mut Window,
11811 cx: &mut Context<Self>,
11812 ) {
11813 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11814 let text_layout_details = &self.text_layout_details(window);
11815 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11816 s.move_heads_with(|map, head, goal| {
11817 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11818 })
11819 })
11820 }
11821
11822 pub fn select_page_up(
11823 &mut self,
11824 _: &SelectPageUp,
11825 window: &mut Window,
11826 cx: &mut Context<Self>,
11827 ) {
11828 let Some(row_count) = self.visible_row_count() else {
11829 return;
11830 };
11831
11832 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11833
11834 let text_layout_details = &self.text_layout_details(window);
11835
11836 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11837 s.move_heads_with(|map, head, goal| {
11838 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11839 })
11840 })
11841 }
11842
11843 pub fn move_page_up(
11844 &mut self,
11845 action: &MovePageUp,
11846 window: &mut Window,
11847 cx: &mut Context<Self>,
11848 ) {
11849 if self.take_rename(true, window, cx).is_some() {
11850 return;
11851 }
11852
11853 if self
11854 .context_menu
11855 .borrow_mut()
11856 .as_mut()
11857 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11858 .unwrap_or(false)
11859 {
11860 return;
11861 }
11862
11863 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11864 cx.propagate();
11865 return;
11866 }
11867
11868 let Some(row_count) = self.visible_row_count() else {
11869 return;
11870 };
11871
11872 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11873
11874 let autoscroll = if action.center_cursor {
11875 Autoscroll::center()
11876 } else {
11877 Autoscroll::fit()
11878 };
11879
11880 let text_layout_details = &self.text_layout_details(window);
11881
11882 self.change_selections(Some(autoscroll), window, cx, |s| {
11883 s.move_with(|map, selection| {
11884 if !selection.is_empty() {
11885 selection.goal = SelectionGoal::None;
11886 }
11887 let (cursor, goal) = movement::up_by_rows(
11888 map,
11889 selection.end,
11890 row_count,
11891 selection.goal,
11892 false,
11893 text_layout_details,
11894 );
11895 selection.collapse_to(cursor, goal);
11896 });
11897 });
11898 }
11899
11900 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11901 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11902 let text_layout_details = &self.text_layout_details(window);
11903 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11904 s.move_heads_with(|map, head, goal| {
11905 movement::up(map, head, goal, false, text_layout_details)
11906 })
11907 })
11908 }
11909
11910 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11911 self.take_rename(true, window, cx);
11912
11913 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11914 cx.propagate();
11915 return;
11916 }
11917
11918 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11919
11920 let text_layout_details = &self.text_layout_details(window);
11921 let selection_count = self.selections.count();
11922 let first_selection = self.selections.first_anchor();
11923
11924 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11925 s.move_with(|map, selection| {
11926 if !selection.is_empty() {
11927 selection.goal = SelectionGoal::None;
11928 }
11929 let (cursor, goal) = movement::down(
11930 map,
11931 selection.end,
11932 selection.goal,
11933 false,
11934 text_layout_details,
11935 );
11936 selection.collapse_to(cursor, goal);
11937 });
11938 });
11939
11940 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11941 {
11942 cx.propagate();
11943 }
11944 }
11945
11946 pub fn select_page_down(
11947 &mut self,
11948 _: &SelectPageDown,
11949 window: &mut Window,
11950 cx: &mut Context<Self>,
11951 ) {
11952 let Some(row_count) = self.visible_row_count() else {
11953 return;
11954 };
11955
11956 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11957
11958 let text_layout_details = &self.text_layout_details(window);
11959
11960 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11961 s.move_heads_with(|map, head, goal| {
11962 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11963 })
11964 })
11965 }
11966
11967 pub fn move_page_down(
11968 &mut self,
11969 action: &MovePageDown,
11970 window: &mut Window,
11971 cx: &mut Context<Self>,
11972 ) {
11973 if self.take_rename(true, window, cx).is_some() {
11974 return;
11975 }
11976
11977 if self
11978 .context_menu
11979 .borrow_mut()
11980 .as_mut()
11981 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11982 .unwrap_or(false)
11983 {
11984 return;
11985 }
11986
11987 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11988 cx.propagate();
11989 return;
11990 }
11991
11992 let Some(row_count) = self.visible_row_count() else {
11993 return;
11994 };
11995
11996 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11997
11998 let autoscroll = if action.center_cursor {
11999 Autoscroll::center()
12000 } else {
12001 Autoscroll::fit()
12002 };
12003
12004 let text_layout_details = &self.text_layout_details(window);
12005 self.change_selections(Some(autoscroll), window, cx, |s| {
12006 s.move_with(|map, selection| {
12007 if !selection.is_empty() {
12008 selection.goal = SelectionGoal::None;
12009 }
12010 let (cursor, goal) = movement::down_by_rows(
12011 map,
12012 selection.end,
12013 row_count,
12014 selection.goal,
12015 false,
12016 text_layout_details,
12017 );
12018 selection.collapse_to(cursor, goal);
12019 });
12020 });
12021 }
12022
12023 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12024 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12025 let text_layout_details = &self.text_layout_details(window);
12026 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12027 s.move_heads_with(|map, head, goal| {
12028 movement::down(map, head, goal, false, text_layout_details)
12029 })
12030 });
12031 }
12032
12033 pub fn context_menu_first(
12034 &mut self,
12035 _: &ContextMenuFirst,
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_first(self.completion_provider.as_deref(), window, cx);
12041 }
12042 }
12043
12044 pub fn context_menu_prev(
12045 &mut self,
12046 _: &ContextMenuPrevious,
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_prev(self.completion_provider.as_deref(), window, cx);
12052 }
12053 }
12054
12055 pub fn context_menu_next(
12056 &mut self,
12057 _: &ContextMenuNext,
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_next(self.completion_provider.as_deref(), window, cx);
12063 }
12064 }
12065
12066 pub fn context_menu_last(
12067 &mut self,
12068 _: &ContextMenuLast,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12073 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12074 }
12075 }
12076
12077 pub fn move_to_previous_word_start(
12078 &mut self,
12079 _: &MoveToPreviousWordStart,
12080 window: &mut Window,
12081 cx: &mut Context<Self>,
12082 ) {
12083 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12084 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12085 s.move_cursors_with(|map, head, _| {
12086 (
12087 movement::previous_word_start(map, head),
12088 SelectionGoal::None,
12089 )
12090 });
12091 })
12092 }
12093
12094 pub fn move_to_previous_subword_start(
12095 &mut self,
12096 _: &MoveToPreviousSubwordStart,
12097 window: &mut Window,
12098 cx: &mut Context<Self>,
12099 ) {
12100 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12101 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12102 s.move_cursors_with(|map, head, _| {
12103 (
12104 movement::previous_subword_start(map, head),
12105 SelectionGoal::None,
12106 )
12107 });
12108 })
12109 }
12110
12111 pub fn select_to_previous_word_start(
12112 &mut self,
12113 _: &SelectToPreviousWordStart,
12114 window: &mut Window,
12115 cx: &mut Context<Self>,
12116 ) {
12117 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12118 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12119 s.move_heads_with(|map, head, _| {
12120 (
12121 movement::previous_word_start(map, head),
12122 SelectionGoal::None,
12123 )
12124 });
12125 })
12126 }
12127
12128 pub fn select_to_previous_subword_start(
12129 &mut self,
12130 _: &SelectToPreviousSubwordStart,
12131 window: &mut Window,
12132 cx: &mut Context<Self>,
12133 ) {
12134 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12135 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12136 s.move_heads_with(|map, head, _| {
12137 (
12138 movement::previous_subword_start(map, head),
12139 SelectionGoal::None,
12140 )
12141 });
12142 })
12143 }
12144
12145 pub fn delete_to_previous_word_start(
12146 &mut self,
12147 action: &DeleteToPreviousWordStart,
12148 window: &mut Window,
12149 cx: &mut Context<Self>,
12150 ) {
12151 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12152 self.transact(window, cx, |this, window, cx| {
12153 this.select_autoclose_pair(window, cx);
12154 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12155 s.move_with(|map, selection| {
12156 if selection.is_empty() {
12157 let cursor = if action.ignore_newlines {
12158 movement::previous_word_start(map, selection.head())
12159 } else {
12160 movement::previous_word_start_or_newline(map, selection.head())
12161 };
12162 selection.set_head(cursor, SelectionGoal::None);
12163 }
12164 });
12165 });
12166 this.insert("", window, cx);
12167 });
12168 }
12169
12170 pub fn delete_to_previous_subword_start(
12171 &mut self,
12172 _: &DeleteToPreviousSubwordStart,
12173 window: &mut Window,
12174 cx: &mut Context<Self>,
12175 ) {
12176 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12177 self.transact(window, cx, |this, window, cx| {
12178 this.select_autoclose_pair(window, cx);
12179 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12180 s.move_with(|map, selection| {
12181 if selection.is_empty() {
12182 let cursor = movement::previous_subword_start(map, selection.head());
12183 selection.set_head(cursor, SelectionGoal::None);
12184 }
12185 });
12186 });
12187 this.insert("", window, cx);
12188 });
12189 }
12190
12191 pub fn move_to_next_word_end(
12192 &mut self,
12193 _: &MoveToNextWordEnd,
12194 window: &mut Window,
12195 cx: &mut Context<Self>,
12196 ) {
12197 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12198 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12199 s.move_cursors_with(|map, head, _| {
12200 (movement::next_word_end(map, head), SelectionGoal::None)
12201 });
12202 })
12203 }
12204
12205 pub fn move_to_next_subword_end(
12206 &mut self,
12207 _: &MoveToNextSubwordEnd,
12208 window: &mut Window,
12209 cx: &mut Context<Self>,
12210 ) {
12211 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12212 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12213 s.move_cursors_with(|map, head, _| {
12214 (movement::next_subword_end(map, head), SelectionGoal::None)
12215 });
12216 })
12217 }
12218
12219 pub fn select_to_next_word_end(
12220 &mut self,
12221 _: &SelectToNextWordEnd,
12222 window: &mut Window,
12223 cx: &mut Context<Self>,
12224 ) {
12225 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12227 s.move_heads_with(|map, head, _| {
12228 (movement::next_word_end(map, head), SelectionGoal::None)
12229 });
12230 })
12231 }
12232
12233 pub fn select_to_next_subword_end(
12234 &mut self,
12235 _: &SelectToNextSubwordEnd,
12236 window: &mut Window,
12237 cx: &mut Context<Self>,
12238 ) {
12239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12240 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12241 s.move_heads_with(|map, head, _| {
12242 (movement::next_subword_end(map, head), SelectionGoal::None)
12243 });
12244 })
12245 }
12246
12247 pub fn delete_to_next_word_end(
12248 &mut self,
12249 action: &DeleteToNextWordEnd,
12250 window: &mut Window,
12251 cx: &mut Context<Self>,
12252 ) {
12253 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12254 self.transact(window, cx, |this, window, cx| {
12255 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12256 s.move_with(|map, selection| {
12257 if selection.is_empty() {
12258 let cursor = if action.ignore_newlines {
12259 movement::next_word_end(map, selection.head())
12260 } else {
12261 movement::next_word_end_or_newline(map, selection.head())
12262 };
12263 selection.set_head(cursor, SelectionGoal::None);
12264 }
12265 });
12266 });
12267 this.insert("", window, cx);
12268 });
12269 }
12270
12271 pub fn delete_to_next_subword_end(
12272 &mut self,
12273 _: &DeleteToNextSubwordEnd,
12274 window: &mut Window,
12275 cx: &mut Context<Self>,
12276 ) {
12277 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12278 self.transact(window, cx, |this, window, cx| {
12279 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12280 s.move_with(|map, selection| {
12281 if selection.is_empty() {
12282 let cursor = movement::next_subword_end(map, selection.head());
12283 selection.set_head(cursor, SelectionGoal::None);
12284 }
12285 });
12286 });
12287 this.insert("", window, cx);
12288 });
12289 }
12290
12291 pub fn move_to_beginning_of_line(
12292 &mut self,
12293 action: &MoveToBeginningOfLine,
12294 window: &mut Window,
12295 cx: &mut Context<Self>,
12296 ) {
12297 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12298 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12299 s.move_cursors_with(|map, head, _| {
12300 (
12301 movement::indented_line_beginning(
12302 map,
12303 head,
12304 action.stop_at_soft_wraps,
12305 action.stop_at_indent,
12306 ),
12307 SelectionGoal::None,
12308 )
12309 });
12310 })
12311 }
12312
12313 pub fn select_to_beginning_of_line(
12314 &mut self,
12315 action: &SelectToBeginningOfLine,
12316 window: &mut Window,
12317 cx: &mut Context<Self>,
12318 ) {
12319 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12320 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12321 s.move_heads_with(|map, head, _| {
12322 (
12323 movement::indented_line_beginning(
12324 map,
12325 head,
12326 action.stop_at_soft_wraps,
12327 action.stop_at_indent,
12328 ),
12329 SelectionGoal::None,
12330 )
12331 });
12332 });
12333 }
12334
12335 pub fn delete_to_beginning_of_line(
12336 &mut self,
12337 action: &DeleteToBeginningOfLine,
12338 window: &mut Window,
12339 cx: &mut Context<Self>,
12340 ) {
12341 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12342 self.transact(window, cx, |this, window, cx| {
12343 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12344 s.move_with(|_, selection| {
12345 selection.reversed = true;
12346 });
12347 });
12348
12349 this.select_to_beginning_of_line(
12350 &SelectToBeginningOfLine {
12351 stop_at_soft_wraps: false,
12352 stop_at_indent: action.stop_at_indent,
12353 },
12354 window,
12355 cx,
12356 );
12357 this.backspace(&Backspace, window, cx);
12358 });
12359 }
12360
12361 pub fn move_to_end_of_line(
12362 &mut self,
12363 action: &MoveToEndOfLine,
12364 window: &mut Window,
12365 cx: &mut Context<Self>,
12366 ) {
12367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12369 s.move_cursors_with(|map, head, _| {
12370 (
12371 movement::line_end(map, head, action.stop_at_soft_wraps),
12372 SelectionGoal::None,
12373 )
12374 });
12375 })
12376 }
12377
12378 pub fn select_to_end_of_line(
12379 &mut self,
12380 action: &SelectToEndOfLine,
12381 window: &mut Window,
12382 cx: &mut Context<Self>,
12383 ) {
12384 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12385 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12386 s.move_heads_with(|map, head, _| {
12387 (
12388 movement::line_end(map, head, action.stop_at_soft_wraps),
12389 SelectionGoal::None,
12390 )
12391 });
12392 })
12393 }
12394
12395 pub fn delete_to_end_of_line(
12396 &mut self,
12397 _: &DeleteToEndOfLine,
12398 window: &mut Window,
12399 cx: &mut Context<Self>,
12400 ) {
12401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12402 self.transact(window, cx, |this, window, cx| {
12403 this.select_to_end_of_line(
12404 &SelectToEndOfLine {
12405 stop_at_soft_wraps: false,
12406 },
12407 window,
12408 cx,
12409 );
12410 this.delete(&Delete, window, cx);
12411 });
12412 }
12413
12414 pub fn cut_to_end_of_line(
12415 &mut self,
12416 _: &CutToEndOfLine,
12417 window: &mut Window,
12418 cx: &mut Context<Self>,
12419 ) {
12420 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12421 self.transact(window, cx, |this, window, cx| {
12422 this.select_to_end_of_line(
12423 &SelectToEndOfLine {
12424 stop_at_soft_wraps: false,
12425 },
12426 window,
12427 cx,
12428 );
12429 this.cut(&Cut, window, cx);
12430 });
12431 }
12432
12433 pub fn move_to_start_of_paragraph(
12434 &mut self,
12435 _: &MoveToStartOfParagraph,
12436 window: &mut Window,
12437 cx: &mut Context<Self>,
12438 ) {
12439 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12440 cx.propagate();
12441 return;
12442 }
12443 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12444 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12445 s.move_with(|map, selection| {
12446 selection.collapse_to(
12447 movement::start_of_paragraph(map, selection.head(), 1),
12448 SelectionGoal::None,
12449 )
12450 });
12451 })
12452 }
12453
12454 pub fn move_to_end_of_paragraph(
12455 &mut self,
12456 _: &MoveToEndOfParagraph,
12457 window: &mut Window,
12458 cx: &mut Context<Self>,
12459 ) {
12460 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12461 cx.propagate();
12462 return;
12463 }
12464 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12465 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12466 s.move_with(|map, selection| {
12467 selection.collapse_to(
12468 movement::end_of_paragraph(map, selection.head(), 1),
12469 SelectionGoal::None,
12470 )
12471 });
12472 })
12473 }
12474
12475 pub fn select_to_start_of_paragraph(
12476 &mut self,
12477 _: &SelectToStartOfParagraph,
12478 window: &mut Window,
12479 cx: &mut Context<Self>,
12480 ) {
12481 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12482 cx.propagate();
12483 return;
12484 }
12485 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12486 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12487 s.move_heads_with(|map, head, _| {
12488 (
12489 movement::start_of_paragraph(map, head, 1),
12490 SelectionGoal::None,
12491 )
12492 });
12493 })
12494 }
12495
12496 pub fn select_to_end_of_paragraph(
12497 &mut self,
12498 _: &SelectToEndOfParagraph,
12499 window: &mut Window,
12500 cx: &mut Context<Self>,
12501 ) {
12502 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12503 cx.propagate();
12504 return;
12505 }
12506 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12507 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12508 s.move_heads_with(|map, head, _| {
12509 (
12510 movement::end_of_paragraph(map, head, 1),
12511 SelectionGoal::None,
12512 )
12513 });
12514 })
12515 }
12516
12517 pub fn move_to_start_of_excerpt(
12518 &mut self,
12519 _: &MoveToStartOfExcerpt,
12520 window: &mut Window,
12521 cx: &mut Context<Self>,
12522 ) {
12523 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12524 cx.propagate();
12525 return;
12526 }
12527 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12528 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12529 s.move_with(|map, selection| {
12530 selection.collapse_to(
12531 movement::start_of_excerpt(
12532 map,
12533 selection.head(),
12534 workspace::searchable::Direction::Prev,
12535 ),
12536 SelectionGoal::None,
12537 )
12538 });
12539 })
12540 }
12541
12542 pub fn move_to_start_of_next_excerpt(
12543 &mut self,
12544 _: &MoveToStartOfNextExcerpt,
12545 window: &mut Window,
12546 cx: &mut Context<Self>,
12547 ) {
12548 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12549 cx.propagate();
12550 return;
12551 }
12552
12553 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12554 s.move_with(|map, selection| {
12555 selection.collapse_to(
12556 movement::start_of_excerpt(
12557 map,
12558 selection.head(),
12559 workspace::searchable::Direction::Next,
12560 ),
12561 SelectionGoal::None,
12562 )
12563 });
12564 })
12565 }
12566
12567 pub fn move_to_end_of_excerpt(
12568 &mut self,
12569 _: &MoveToEndOfExcerpt,
12570 window: &mut Window,
12571 cx: &mut Context<Self>,
12572 ) {
12573 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12574 cx.propagate();
12575 return;
12576 }
12577 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12578 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12579 s.move_with(|map, selection| {
12580 selection.collapse_to(
12581 movement::end_of_excerpt(
12582 map,
12583 selection.head(),
12584 workspace::searchable::Direction::Next,
12585 ),
12586 SelectionGoal::None,
12587 )
12588 });
12589 })
12590 }
12591
12592 pub fn move_to_end_of_previous_excerpt(
12593 &mut self,
12594 _: &MoveToEndOfPreviousExcerpt,
12595 window: &mut Window,
12596 cx: &mut Context<Self>,
12597 ) {
12598 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12599 cx.propagate();
12600 return;
12601 }
12602 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12603 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12604 s.move_with(|map, selection| {
12605 selection.collapse_to(
12606 movement::end_of_excerpt(
12607 map,
12608 selection.head(),
12609 workspace::searchable::Direction::Prev,
12610 ),
12611 SelectionGoal::None,
12612 )
12613 });
12614 })
12615 }
12616
12617 pub fn select_to_start_of_excerpt(
12618 &mut self,
12619 _: &SelectToStartOfExcerpt,
12620 window: &mut Window,
12621 cx: &mut Context<Self>,
12622 ) {
12623 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12624 cx.propagate();
12625 return;
12626 }
12627 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12628 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12629 s.move_heads_with(|map, head, _| {
12630 (
12631 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12632 SelectionGoal::None,
12633 )
12634 });
12635 })
12636 }
12637
12638 pub fn select_to_start_of_next_excerpt(
12639 &mut self,
12640 _: &SelectToStartOfNextExcerpt,
12641 window: &mut Window,
12642 cx: &mut Context<Self>,
12643 ) {
12644 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12645 cx.propagate();
12646 return;
12647 }
12648 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12649 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12650 s.move_heads_with(|map, head, _| {
12651 (
12652 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12653 SelectionGoal::None,
12654 )
12655 });
12656 })
12657 }
12658
12659 pub fn select_to_end_of_excerpt(
12660 &mut self,
12661 _: &SelectToEndOfExcerpt,
12662 window: &mut Window,
12663 cx: &mut Context<Self>,
12664 ) {
12665 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12666 cx.propagate();
12667 return;
12668 }
12669 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12670 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12671 s.move_heads_with(|map, head, _| {
12672 (
12673 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12674 SelectionGoal::None,
12675 )
12676 });
12677 })
12678 }
12679
12680 pub fn select_to_end_of_previous_excerpt(
12681 &mut self,
12682 _: &SelectToEndOfPreviousExcerpt,
12683 window: &mut Window,
12684 cx: &mut Context<Self>,
12685 ) {
12686 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12687 cx.propagate();
12688 return;
12689 }
12690 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12691 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12692 s.move_heads_with(|map, head, _| {
12693 (
12694 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12695 SelectionGoal::None,
12696 )
12697 });
12698 })
12699 }
12700
12701 pub fn move_to_beginning(
12702 &mut self,
12703 _: &MoveToBeginning,
12704 window: &mut Window,
12705 cx: &mut Context<Self>,
12706 ) {
12707 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12708 cx.propagate();
12709 return;
12710 }
12711 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12712 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12713 s.select_ranges(vec![0..0]);
12714 });
12715 }
12716
12717 pub fn select_to_beginning(
12718 &mut self,
12719 _: &SelectToBeginning,
12720 window: &mut Window,
12721 cx: &mut Context<Self>,
12722 ) {
12723 let mut selection = self.selections.last::<Point>(cx);
12724 selection.set_head(Point::zero(), SelectionGoal::None);
12725 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12726 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12727 s.select(vec![selection]);
12728 });
12729 }
12730
12731 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12732 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12733 cx.propagate();
12734 return;
12735 }
12736 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12737 let cursor = self.buffer.read(cx).read(cx).len();
12738 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12739 s.select_ranges(vec![cursor..cursor])
12740 });
12741 }
12742
12743 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12744 self.nav_history = nav_history;
12745 }
12746
12747 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12748 self.nav_history.as_ref()
12749 }
12750
12751 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12752 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12753 }
12754
12755 fn push_to_nav_history(
12756 &mut self,
12757 cursor_anchor: Anchor,
12758 new_position: Option<Point>,
12759 is_deactivate: bool,
12760 cx: &mut Context<Self>,
12761 ) {
12762 if let Some(nav_history) = self.nav_history.as_mut() {
12763 let buffer = self.buffer.read(cx).read(cx);
12764 let cursor_position = cursor_anchor.to_point(&buffer);
12765 let scroll_state = self.scroll_manager.anchor();
12766 let scroll_top_row = scroll_state.top_row(&buffer);
12767 drop(buffer);
12768
12769 if let Some(new_position) = new_position {
12770 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12771 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12772 return;
12773 }
12774 }
12775
12776 nav_history.push(
12777 Some(NavigationData {
12778 cursor_anchor,
12779 cursor_position,
12780 scroll_anchor: scroll_state,
12781 scroll_top_row,
12782 }),
12783 cx,
12784 );
12785 cx.emit(EditorEvent::PushedToNavHistory {
12786 anchor: cursor_anchor,
12787 is_deactivate,
12788 })
12789 }
12790 }
12791
12792 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12793 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12794 let buffer = self.buffer.read(cx).snapshot(cx);
12795 let mut selection = self.selections.first::<usize>(cx);
12796 selection.set_head(buffer.len(), SelectionGoal::None);
12797 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12798 s.select(vec![selection]);
12799 });
12800 }
12801
12802 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12803 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12804 let end = self.buffer.read(cx).read(cx).len();
12805 self.change_selections(None, window, cx, |s| {
12806 s.select_ranges(vec![0..end]);
12807 });
12808 }
12809
12810 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12811 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12813 let mut selections = self.selections.all::<Point>(cx);
12814 let max_point = display_map.buffer_snapshot.max_point();
12815 for selection in &mut selections {
12816 let rows = selection.spanned_rows(true, &display_map);
12817 selection.start = Point::new(rows.start.0, 0);
12818 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12819 selection.reversed = false;
12820 }
12821 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12822 s.select(selections);
12823 });
12824 }
12825
12826 pub fn split_selection_into_lines(
12827 &mut self,
12828 _: &SplitSelectionIntoLines,
12829 window: &mut Window,
12830 cx: &mut Context<Self>,
12831 ) {
12832 let selections = self
12833 .selections
12834 .all::<Point>(cx)
12835 .into_iter()
12836 .map(|selection| selection.start..selection.end)
12837 .collect::<Vec<_>>();
12838 self.unfold_ranges(&selections, true, true, cx);
12839
12840 let mut new_selection_ranges = Vec::new();
12841 {
12842 let buffer = self.buffer.read(cx).read(cx);
12843 for selection in selections {
12844 for row in selection.start.row..selection.end.row {
12845 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12846 new_selection_ranges.push(cursor..cursor);
12847 }
12848
12849 let is_multiline_selection = selection.start.row != selection.end.row;
12850 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12851 // so this action feels more ergonomic when paired with other selection operations
12852 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12853 if !should_skip_last {
12854 new_selection_ranges.push(selection.end..selection.end);
12855 }
12856 }
12857 }
12858 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12859 s.select_ranges(new_selection_ranges);
12860 });
12861 }
12862
12863 pub fn add_selection_above(
12864 &mut self,
12865 _: &AddSelectionAbove,
12866 window: &mut Window,
12867 cx: &mut Context<Self>,
12868 ) {
12869 self.add_selection(true, window, cx);
12870 }
12871
12872 pub fn add_selection_below(
12873 &mut self,
12874 _: &AddSelectionBelow,
12875 window: &mut Window,
12876 cx: &mut Context<Self>,
12877 ) {
12878 self.add_selection(false, window, cx);
12879 }
12880
12881 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12882 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12883
12884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12885 let all_selections = self.selections.all::<Point>(cx);
12886 let text_layout_details = self.text_layout_details(window);
12887
12888 let (mut columnar_selections, new_selections_to_columnarize) = {
12889 if let Some(state) = self.add_selections_state.as_ref() {
12890 let columnar_selection_ids: HashSet<_> = state
12891 .groups
12892 .iter()
12893 .flat_map(|group| group.stack.iter())
12894 .copied()
12895 .collect();
12896
12897 all_selections
12898 .into_iter()
12899 .partition(|s| columnar_selection_ids.contains(&s.id))
12900 } else {
12901 (Vec::new(), all_selections)
12902 }
12903 };
12904
12905 let mut state = self
12906 .add_selections_state
12907 .take()
12908 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12909
12910 for selection in new_selections_to_columnarize {
12911 let range = selection.display_range(&display_map).sorted();
12912 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12913 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12914 let positions = start_x.min(end_x)..start_x.max(end_x);
12915 let mut stack = Vec::new();
12916 for row in range.start.row().0..=range.end.row().0 {
12917 if let Some(selection) = self.selections.build_columnar_selection(
12918 &display_map,
12919 DisplayRow(row),
12920 &positions,
12921 selection.reversed,
12922 &text_layout_details,
12923 ) {
12924 stack.push(selection.id);
12925 columnar_selections.push(selection);
12926 }
12927 }
12928 if !stack.is_empty() {
12929 if above {
12930 stack.reverse();
12931 }
12932 state.groups.push(AddSelectionsGroup { above, stack });
12933 }
12934 }
12935
12936 let mut final_selections = Vec::new();
12937 let end_row = if above {
12938 DisplayRow(0)
12939 } else {
12940 display_map.max_point().row()
12941 };
12942
12943 let mut last_added_item_per_group = HashMap::default();
12944 for group in state.groups.iter_mut() {
12945 if let Some(last_id) = group.stack.last() {
12946 last_added_item_per_group.insert(*last_id, group);
12947 }
12948 }
12949
12950 for selection in columnar_selections {
12951 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12952 if above == group.above {
12953 let range = selection.display_range(&display_map).sorted();
12954 debug_assert_eq!(range.start.row(), range.end.row());
12955 let mut row = range.start.row();
12956 let positions =
12957 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12958 px(start)..px(end)
12959 } else {
12960 let start_x =
12961 display_map.x_for_display_point(range.start, &text_layout_details);
12962 let end_x =
12963 display_map.x_for_display_point(range.end, &text_layout_details);
12964 start_x.min(end_x)..start_x.max(end_x)
12965 };
12966
12967 let mut maybe_new_selection = None;
12968 while row != end_row {
12969 if above {
12970 row.0 -= 1;
12971 } else {
12972 row.0 += 1;
12973 }
12974 if let Some(new_selection) = self.selections.build_columnar_selection(
12975 &display_map,
12976 row,
12977 &positions,
12978 selection.reversed,
12979 &text_layout_details,
12980 ) {
12981 maybe_new_selection = Some(new_selection);
12982 break;
12983 }
12984 }
12985
12986 if let Some(new_selection) = maybe_new_selection {
12987 group.stack.push(new_selection.id);
12988 if above {
12989 final_selections.push(new_selection);
12990 final_selections.push(selection);
12991 } else {
12992 final_selections.push(selection);
12993 final_selections.push(new_selection);
12994 }
12995 } else {
12996 final_selections.push(selection);
12997 }
12998 } else {
12999 group.stack.pop();
13000 }
13001 } else {
13002 final_selections.push(selection);
13003 }
13004 }
13005
13006 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13007 s.select(final_selections);
13008 });
13009
13010 let final_selection_ids: HashSet<_> = self
13011 .selections
13012 .all::<Point>(cx)
13013 .iter()
13014 .map(|s| s.id)
13015 .collect();
13016 state.groups.retain_mut(|group| {
13017 // selections might get merged above so we remove invalid items from stacks
13018 group.stack.retain(|id| final_selection_ids.contains(id));
13019
13020 // single selection in stack can be treated as initial state
13021 group.stack.len() > 1
13022 });
13023
13024 if !state.groups.is_empty() {
13025 self.add_selections_state = Some(state);
13026 }
13027 }
13028
13029 fn select_match_ranges(
13030 &mut self,
13031 range: Range<usize>,
13032 reversed: bool,
13033 replace_newest: bool,
13034 auto_scroll: Option<Autoscroll>,
13035 window: &mut Window,
13036 cx: &mut Context<Editor>,
13037 ) {
13038 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13039 self.change_selections(auto_scroll, window, cx, |s| {
13040 if replace_newest {
13041 s.delete(s.newest_anchor().id);
13042 }
13043 if reversed {
13044 s.insert_range(range.end..range.start);
13045 } else {
13046 s.insert_range(range);
13047 }
13048 });
13049 }
13050
13051 pub fn select_next_match_internal(
13052 &mut self,
13053 display_map: &DisplaySnapshot,
13054 replace_newest: bool,
13055 autoscroll: Option<Autoscroll>,
13056 window: &mut Window,
13057 cx: &mut Context<Self>,
13058 ) -> Result<()> {
13059 let buffer = &display_map.buffer_snapshot;
13060 let mut selections = self.selections.all::<usize>(cx);
13061 if let Some(mut select_next_state) = self.select_next_state.take() {
13062 let query = &select_next_state.query;
13063 if !select_next_state.done {
13064 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13065 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13066 let mut next_selected_range = None;
13067
13068 let bytes_after_last_selection =
13069 buffer.bytes_in_range(last_selection.end..buffer.len());
13070 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13071 let query_matches = query
13072 .stream_find_iter(bytes_after_last_selection)
13073 .map(|result| (last_selection.end, result))
13074 .chain(
13075 query
13076 .stream_find_iter(bytes_before_first_selection)
13077 .map(|result| (0, result)),
13078 );
13079
13080 for (start_offset, query_match) in query_matches {
13081 let query_match = query_match.unwrap(); // can only fail due to I/O
13082 let offset_range =
13083 start_offset + query_match.start()..start_offset + query_match.end();
13084 let display_range = offset_range.start.to_display_point(display_map)
13085 ..offset_range.end.to_display_point(display_map);
13086
13087 if !select_next_state.wordwise
13088 || (!movement::is_inside_word(display_map, display_range.start)
13089 && !movement::is_inside_word(display_map, display_range.end))
13090 {
13091 // TODO: This is n^2, because we might check all the selections
13092 if !selections
13093 .iter()
13094 .any(|selection| selection.range().overlaps(&offset_range))
13095 {
13096 next_selected_range = Some(offset_range);
13097 break;
13098 }
13099 }
13100 }
13101
13102 if let Some(next_selected_range) = next_selected_range {
13103 self.select_match_ranges(
13104 next_selected_range,
13105 last_selection.reversed,
13106 replace_newest,
13107 autoscroll,
13108 window,
13109 cx,
13110 );
13111 } else {
13112 select_next_state.done = true;
13113 }
13114 }
13115
13116 self.select_next_state = Some(select_next_state);
13117 } else {
13118 let mut only_carets = true;
13119 let mut same_text_selected = true;
13120 let mut selected_text = None;
13121
13122 let mut selections_iter = selections.iter().peekable();
13123 while let Some(selection) = selections_iter.next() {
13124 if selection.start != selection.end {
13125 only_carets = false;
13126 }
13127
13128 if same_text_selected {
13129 if selected_text.is_none() {
13130 selected_text =
13131 Some(buffer.text_for_range(selection.range()).collect::<String>());
13132 }
13133
13134 if let Some(next_selection) = selections_iter.peek() {
13135 if next_selection.range().len() == selection.range().len() {
13136 let next_selected_text = buffer
13137 .text_for_range(next_selection.range())
13138 .collect::<String>();
13139 if Some(next_selected_text) != selected_text {
13140 same_text_selected = false;
13141 selected_text = None;
13142 }
13143 } else {
13144 same_text_selected = false;
13145 selected_text = None;
13146 }
13147 }
13148 }
13149 }
13150
13151 if only_carets {
13152 for selection in &mut selections {
13153 let word_range = movement::surrounding_word(
13154 display_map,
13155 selection.start.to_display_point(display_map),
13156 );
13157 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13158 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13159 selection.goal = SelectionGoal::None;
13160 selection.reversed = false;
13161 self.select_match_ranges(
13162 selection.start..selection.end,
13163 selection.reversed,
13164 replace_newest,
13165 autoscroll,
13166 window,
13167 cx,
13168 );
13169 }
13170
13171 if selections.len() == 1 {
13172 let selection = selections
13173 .last()
13174 .expect("ensured that there's only one selection");
13175 let query = buffer
13176 .text_for_range(selection.start..selection.end)
13177 .collect::<String>();
13178 let is_empty = query.is_empty();
13179 let select_state = SelectNextState {
13180 query: AhoCorasick::new(&[query])?,
13181 wordwise: true,
13182 done: is_empty,
13183 };
13184 self.select_next_state = Some(select_state);
13185 } else {
13186 self.select_next_state = None;
13187 }
13188 } else if let Some(selected_text) = selected_text {
13189 self.select_next_state = Some(SelectNextState {
13190 query: AhoCorasick::new(&[selected_text])?,
13191 wordwise: false,
13192 done: false,
13193 });
13194 self.select_next_match_internal(
13195 display_map,
13196 replace_newest,
13197 autoscroll,
13198 window,
13199 cx,
13200 )?;
13201 }
13202 }
13203 Ok(())
13204 }
13205
13206 pub fn select_all_matches(
13207 &mut self,
13208 _action: &SelectAllMatches,
13209 window: &mut Window,
13210 cx: &mut Context<Self>,
13211 ) -> Result<()> {
13212 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13213
13214 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13215
13216 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13217 let Some(select_next_state) = self.select_next_state.as_mut() else {
13218 return Ok(());
13219 };
13220 if select_next_state.done {
13221 return Ok(());
13222 }
13223
13224 let mut new_selections = Vec::new();
13225
13226 let reversed = self.selections.oldest::<usize>(cx).reversed;
13227 let buffer = &display_map.buffer_snapshot;
13228 let query_matches = select_next_state
13229 .query
13230 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13231
13232 for query_match in query_matches.into_iter() {
13233 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13234 let offset_range = if reversed {
13235 query_match.end()..query_match.start()
13236 } else {
13237 query_match.start()..query_match.end()
13238 };
13239 let display_range = offset_range.start.to_display_point(&display_map)
13240 ..offset_range.end.to_display_point(&display_map);
13241
13242 if !select_next_state.wordwise
13243 || (!movement::is_inside_word(&display_map, display_range.start)
13244 && !movement::is_inside_word(&display_map, display_range.end))
13245 {
13246 new_selections.push(offset_range.start..offset_range.end);
13247 }
13248 }
13249
13250 select_next_state.done = true;
13251 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13252 self.change_selections(None, window, cx, |selections| {
13253 selections.select_ranges(new_selections)
13254 });
13255
13256 Ok(())
13257 }
13258
13259 pub fn select_next(
13260 &mut self,
13261 action: &SelectNext,
13262 window: &mut Window,
13263 cx: &mut Context<Self>,
13264 ) -> Result<()> {
13265 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13267 self.select_next_match_internal(
13268 &display_map,
13269 action.replace_newest,
13270 Some(Autoscroll::newest()),
13271 window,
13272 cx,
13273 )?;
13274 Ok(())
13275 }
13276
13277 pub fn select_previous(
13278 &mut self,
13279 action: &SelectPrevious,
13280 window: &mut Window,
13281 cx: &mut Context<Self>,
13282 ) -> Result<()> {
13283 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13284 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13285 let buffer = &display_map.buffer_snapshot;
13286 let mut selections = self.selections.all::<usize>(cx);
13287 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13288 let query = &select_prev_state.query;
13289 if !select_prev_state.done {
13290 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13291 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13292 let mut next_selected_range = None;
13293 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13294 let bytes_before_last_selection =
13295 buffer.reversed_bytes_in_range(0..last_selection.start);
13296 let bytes_after_first_selection =
13297 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13298 let query_matches = query
13299 .stream_find_iter(bytes_before_last_selection)
13300 .map(|result| (last_selection.start, result))
13301 .chain(
13302 query
13303 .stream_find_iter(bytes_after_first_selection)
13304 .map(|result| (buffer.len(), result)),
13305 );
13306 for (end_offset, query_match) in query_matches {
13307 let query_match = query_match.unwrap(); // can only fail due to I/O
13308 let offset_range =
13309 end_offset - query_match.end()..end_offset - query_match.start();
13310 let display_range = offset_range.start.to_display_point(&display_map)
13311 ..offset_range.end.to_display_point(&display_map);
13312
13313 if !select_prev_state.wordwise
13314 || (!movement::is_inside_word(&display_map, display_range.start)
13315 && !movement::is_inside_word(&display_map, display_range.end))
13316 {
13317 next_selected_range = Some(offset_range);
13318 break;
13319 }
13320 }
13321
13322 if let Some(next_selected_range) = next_selected_range {
13323 self.select_match_ranges(
13324 next_selected_range,
13325 last_selection.reversed,
13326 action.replace_newest,
13327 Some(Autoscroll::newest()),
13328 window,
13329 cx,
13330 );
13331 } else {
13332 select_prev_state.done = true;
13333 }
13334 }
13335
13336 self.select_prev_state = Some(select_prev_state);
13337 } else {
13338 let mut only_carets = true;
13339 let mut same_text_selected = true;
13340 let mut selected_text = None;
13341
13342 let mut selections_iter = selections.iter().peekable();
13343 while let Some(selection) = selections_iter.next() {
13344 if selection.start != selection.end {
13345 only_carets = false;
13346 }
13347
13348 if same_text_selected {
13349 if selected_text.is_none() {
13350 selected_text =
13351 Some(buffer.text_for_range(selection.range()).collect::<String>());
13352 }
13353
13354 if let Some(next_selection) = selections_iter.peek() {
13355 if next_selection.range().len() == selection.range().len() {
13356 let next_selected_text = buffer
13357 .text_for_range(next_selection.range())
13358 .collect::<String>();
13359 if Some(next_selected_text) != selected_text {
13360 same_text_selected = false;
13361 selected_text = None;
13362 }
13363 } else {
13364 same_text_selected = false;
13365 selected_text = None;
13366 }
13367 }
13368 }
13369 }
13370
13371 if only_carets {
13372 for selection in &mut selections {
13373 let word_range = movement::surrounding_word(
13374 &display_map,
13375 selection.start.to_display_point(&display_map),
13376 );
13377 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13378 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13379 selection.goal = SelectionGoal::None;
13380 selection.reversed = false;
13381 self.select_match_ranges(
13382 selection.start..selection.end,
13383 selection.reversed,
13384 action.replace_newest,
13385 Some(Autoscroll::newest()),
13386 window,
13387 cx,
13388 );
13389 }
13390 if selections.len() == 1 {
13391 let selection = selections
13392 .last()
13393 .expect("ensured that there's only one selection");
13394 let query = buffer
13395 .text_for_range(selection.start..selection.end)
13396 .collect::<String>();
13397 let is_empty = query.is_empty();
13398 let select_state = SelectNextState {
13399 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13400 wordwise: true,
13401 done: is_empty,
13402 };
13403 self.select_prev_state = Some(select_state);
13404 } else {
13405 self.select_prev_state = None;
13406 }
13407 } else if let Some(selected_text) = selected_text {
13408 self.select_prev_state = Some(SelectNextState {
13409 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13410 wordwise: false,
13411 done: false,
13412 });
13413 self.select_previous(action, window, cx)?;
13414 }
13415 }
13416 Ok(())
13417 }
13418
13419 pub fn find_next_match(
13420 &mut self,
13421 _: &FindNextMatch,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) -> Result<()> {
13425 let selections = self.selections.disjoint_anchors();
13426 match selections.first() {
13427 Some(first) if selections.len() >= 2 => {
13428 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13429 s.select_ranges([first.range()]);
13430 });
13431 }
13432 _ => self.select_next(
13433 &SelectNext {
13434 replace_newest: true,
13435 },
13436 window,
13437 cx,
13438 )?,
13439 }
13440 Ok(())
13441 }
13442
13443 pub fn find_previous_match(
13444 &mut self,
13445 _: &FindPreviousMatch,
13446 window: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) -> Result<()> {
13449 let selections = self.selections.disjoint_anchors();
13450 match selections.last() {
13451 Some(last) if selections.len() >= 2 => {
13452 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13453 s.select_ranges([last.range()]);
13454 });
13455 }
13456 _ => self.select_previous(
13457 &SelectPrevious {
13458 replace_newest: true,
13459 },
13460 window,
13461 cx,
13462 )?,
13463 }
13464 Ok(())
13465 }
13466
13467 pub fn toggle_comments(
13468 &mut self,
13469 action: &ToggleComments,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) {
13473 if self.read_only(cx) {
13474 return;
13475 }
13476 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13477 let text_layout_details = &self.text_layout_details(window);
13478 self.transact(window, cx, |this, window, cx| {
13479 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13480 let mut edits = Vec::new();
13481 let mut selection_edit_ranges = Vec::new();
13482 let mut last_toggled_row = None;
13483 let snapshot = this.buffer.read(cx).read(cx);
13484 let empty_str: Arc<str> = Arc::default();
13485 let mut suffixes_inserted = Vec::new();
13486 let ignore_indent = action.ignore_indent;
13487
13488 fn comment_prefix_range(
13489 snapshot: &MultiBufferSnapshot,
13490 row: MultiBufferRow,
13491 comment_prefix: &str,
13492 comment_prefix_whitespace: &str,
13493 ignore_indent: bool,
13494 ) -> Range<Point> {
13495 let indent_size = if ignore_indent {
13496 0
13497 } else {
13498 snapshot.indent_size_for_line(row).len
13499 };
13500
13501 let start = Point::new(row.0, indent_size);
13502
13503 let mut line_bytes = snapshot
13504 .bytes_in_range(start..snapshot.max_point())
13505 .flatten()
13506 .copied();
13507
13508 // If this line currently begins with the line comment prefix, then record
13509 // the range containing the prefix.
13510 if line_bytes
13511 .by_ref()
13512 .take(comment_prefix.len())
13513 .eq(comment_prefix.bytes())
13514 {
13515 // Include any whitespace that matches the comment prefix.
13516 let matching_whitespace_len = line_bytes
13517 .zip(comment_prefix_whitespace.bytes())
13518 .take_while(|(a, b)| a == b)
13519 .count() as u32;
13520 let end = Point::new(
13521 start.row,
13522 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13523 );
13524 start..end
13525 } else {
13526 start..start
13527 }
13528 }
13529
13530 fn comment_suffix_range(
13531 snapshot: &MultiBufferSnapshot,
13532 row: MultiBufferRow,
13533 comment_suffix: &str,
13534 comment_suffix_has_leading_space: bool,
13535 ) -> Range<Point> {
13536 let end = Point::new(row.0, snapshot.line_len(row));
13537 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13538
13539 let mut line_end_bytes = snapshot
13540 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13541 .flatten()
13542 .copied();
13543
13544 let leading_space_len = if suffix_start_column > 0
13545 && line_end_bytes.next() == Some(b' ')
13546 && comment_suffix_has_leading_space
13547 {
13548 1
13549 } else {
13550 0
13551 };
13552
13553 // If this line currently begins with the line comment prefix, then record
13554 // the range containing the prefix.
13555 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13556 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13557 start..end
13558 } else {
13559 end..end
13560 }
13561 }
13562
13563 // TODO: Handle selections that cross excerpts
13564 for selection in &mut selections {
13565 let start_column = snapshot
13566 .indent_size_for_line(MultiBufferRow(selection.start.row))
13567 .len;
13568 let language = if let Some(language) =
13569 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13570 {
13571 language
13572 } else {
13573 continue;
13574 };
13575
13576 selection_edit_ranges.clear();
13577
13578 // If multiple selections contain a given row, avoid processing that
13579 // row more than once.
13580 let mut start_row = MultiBufferRow(selection.start.row);
13581 if last_toggled_row == Some(start_row) {
13582 start_row = start_row.next_row();
13583 }
13584 let end_row =
13585 if selection.end.row > selection.start.row && selection.end.column == 0 {
13586 MultiBufferRow(selection.end.row - 1)
13587 } else {
13588 MultiBufferRow(selection.end.row)
13589 };
13590 last_toggled_row = Some(end_row);
13591
13592 if start_row > end_row {
13593 continue;
13594 }
13595
13596 // If the language has line comments, toggle those.
13597 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13598
13599 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13600 if ignore_indent {
13601 full_comment_prefixes = full_comment_prefixes
13602 .into_iter()
13603 .map(|s| Arc::from(s.trim_end()))
13604 .collect();
13605 }
13606
13607 if !full_comment_prefixes.is_empty() {
13608 let first_prefix = full_comment_prefixes
13609 .first()
13610 .expect("prefixes is non-empty");
13611 let prefix_trimmed_lengths = full_comment_prefixes
13612 .iter()
13613 .map(|p| p.trim_end_matches(' ').len())
13614 .collect::<SmallVec<[usize; 4]>>();
13615
13616 let mut all_selection_lines_are_comments = true;
13617
13618 for row in start_row.0..=end_row.0 {
13619 let row = MultiBufferRow(row);
13620 if start_row < end_row && snapshot.is_line_blank(row) {
13621 continue;
13622 }
13623
13624 let prefix_range = full_comment_prefixes
13625 .iter()
13626 .zip(prefix_trimmed_lengths.iter().copied())
13627 .map(|(prefix, trimmed_prefix_len)| {
13628 comment_prefix_range(
13629 snapshot.deref(),
13630 row,
13631 &prefix[..trimmed_prefix_len],
13632 &prefix[trimmed_prefix_len..],
13633 ignore_indent,
13634 )
13635 })
13636 .max_by_key(|range| range.end.column - range.start.column)
13637 .expect("prefixes is non-empty");
13638
13639 if prefix_range.is_empty() {
13640 all_selection_lines_are_comments = false;
13641 }
13642
13643 selection_edit_ranges.push(prefix_range);
13644 }
13645
13646 if all_selection_lines_are_comments {
13647 edits.extend(
13648 selection_edit_ranges
13649 .iter()
13650 .cloned()
13651 .map(|range| (range, empty_str.clone())),
13652 );
13653 } else {
13654 let min_column = selection_edit_ranges
13655 .iter()
13656 .map(|range| range.start.column)
13657 .min()
13658 .unwrap_or(0);
13659 edits.extend(selection_edit_ranges.iter().map(|range| {
13660 let position = Point::new(range.start.row, min_column);
13661 (position..position, first_prefix.clone())
13662 }));
13663 }
13664 } else if let Some((full_comment_prefix, comment_suffix)) =
13665 language.block_comment_delimiters()
13666 {
13667 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13668 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13669 let prefix_range = comment_prefix_range(
13670 snapshot.deref(),
13671 start_row,
13672 comment_prefix,
13673 comment_prefix_whitespace,
13674 ignore_indent,
13675 );
13676 let suffix_range = comment_suffix_range(
13677 snapshot.deref(),
13678 end_row,
13679 comment_suffix.trim_start_matches(' '),
13680 comment_suffix.starts_with(' '),
13681 );
13682
13683 if prefix_range.is_empty() || suffix_range.is_empty() {
13684 edits.push((
13685 prefix_range.start..prefix_range.start,
13686 full_comment_prefix.clone(),
13687 ));
13688 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13689 suffixes_inserted.push((end_row, comment_suffix.len()));
13690 } else {
13691 edits.push((prefix_range, empty_str.clone()));
13692 edits.push((suffix_range, empty_str.clone()));
13693 }
13694 } else {
13695 continue;
13696 }
13697 }
13698
13699 drop(snapshot);
13700 this.buffer.update(cx, |buffer, cx| {
13701 buffer.edit(edits, None, cx);
13702 });
13703
13704 // Adjust selections so that they end before any comment suffixes that
13705 // were inserted.
13706 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13707 let mut selections = this.selections.all::<Point>(cx);
13708 let snapshot = this.buffer.read(cx).read(cx);
13709 for selection in &mut selections {
13710 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13711 match row.cmp(&MultiBufferRow(selection.end.row)) {
13712 Ordering::Less => {
13713 suffixes_inserted.next();
13714 continue;
13715 }
13716 Ordering::Greater => break,
13717 Ordering::Equal => {
13718 if selection.end.column == snapshot.line_len(row) {
13719 if selection.is_empty() {
13720 selection.start.column -= suffix_len as u32;
13721 }
13722 selection.end.column -= suffix_len as u32;
13723 }
13724 break;
13725 }
13726 }
13727 }
13728 }
13729
13730 drop(snapshot);
13731 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13732 s.select(selections)
13733 });
13734
13735 let selections = this.selections.all::<Point>(cx);
13736 let selections_on_single_row = selections.windows(2).all(|selections| {
13737 selections[0].start.row == selections[1].start.row
13738 && selections[0].end.row == selections[1].end.row
13739 && selections[0].start.row == selections[0].end.row
13740 });
13741 let selections_selecting = selections
13742 .iter()
13743 .any(|selection| selection.start != selection.end);
13744 let advance_downwards = action.advance_downwards
13745 && selections_on_single_row
13746 && !selections_selecting
13747 && !matches!(this.mode, EditorMode::SingleLine { .. });
13748
13749 if advance_downwards {
13750 let snapshot = this.buffer.read(cx).snapshot(cx);
13751
13752 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13753 s.move_cursors_with(|display_snapshot, display_point, _| {
13754 let mut point = display_point.to_point(display_snapshot);
13755 point.row += 1;
13756 point = snapshot.clip_point(point, Bias::Left);
13757 let display_point = point.to_display_point(display_snapshot);
13758 let goal = SelectionGoal::HorizontalPosition(
13759 display_snapshot
13760 .x_for_display_point(display_point, text_layout_details)
13761 .into(),
13762 );
13763 (display_point, goal)
13764 })
13765 });
13766 }
13767 });
13768 }
13769
13770 pub fn select_enclosing_symbol(
13771 &mut self,
13772 _: &SelectEnclosingSymbol,
13773 window: &mut Window,
13774 cx: &mut Context<Self>,
13775 ) {
13776 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13777
13778 let buffer = self.buffer.read(cx).snapshot(cx);
13779 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13780
13781 fn update_selection(
13782 selection: &Selection<usize>,
13783 buffer_snap: &MultiBufferSnapshot,
13784 ) -> Option<Selection<usize>> {
13785 let cursor = selection.head();
13786 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13787 for symbol in symbols.iter().rev() {
13788 let start = symbol.range.start.to_offset(buffer_snap);
13789 let end = symbol.range.end.to_offset(buffer_snap);
13790 let new_range = start..end;
13791 if start < selection.start || end > selection.end {
13792 return Some(Selection {
13793 id: selection.id,
13794 start: new_range.start,
13795 end: new_range.end,
13796 goal: SelectionGoal::None,
13797 reversed: selection.reversed,
13798 });
13799 }
13800 }
13801 None
13802 }
13803
13804 let mut selected_larger_symbol = false;
13805 let new_selections = old_selections
13806 .iter()
13807 .map(|selection| match update_selection(selection, &buffer) {
13808 Some(new_selection) => {
13809 if new_selection.range() != selection.range() {
13810 selected_larger_symbol = true;
13811 }
13812 new_selection
13813 }
13814 None => selection.clone(),
13815 })
13816 .collect::<Vec<_>>();
13817
13818 if selected_larger_symbol {
13819 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13820 s.select(new_selections);
13821 });
13822 }
13823 }
13824
13825 pub fn select_larger_syntax_node(
13826 &mut self,
13827 _: &SelectLargerSyntaxNode,
13828 window: &mut Window,
13829 cx: &mut Context<Self>,
13830 ) {
13831 let Some(visible_row_count) = self.visible_row_count() else {
13832 return;
13833 };
13834 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13835 if old_selections.is_empty() {
13836 return;
13837 }
13838
13839 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13840
13841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13842 let buffer = self.buffer.read(cx).snapshot(cx);
13843
13844 let mut selected_larger_node = false;
13845 let mut new_selections = old_selections
13846 .iter()
13847 .map(|selection| {
13848 let old_range = selection.start..selection.end;
13849
13850 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13851 // manually select word at selection
13852 if ["string_content", "inline"].contains(&node.kind()) {
13853 let word_range = {
13854 let display_point = buffer
13855 .offset_to_point(old_range.start)
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 // ignore if word is already selected
13863 if !word_range.is_empty() && old_range != word_range {
13864 let last_word_range = {
13865 let display_point = buffer
13866 .offset_to_point(old_range.end)
13867 .to_display_point(&display_map);
13868 let Range { start, end } =
13869 movement::surrounding_word(&display_map, display_point);
13870 start.to_point(&display_map).to_offset(&buffer)
13871 ..end.to_point(&display_map).to_offset(&buffer)
13872 };
13873 // only select word if start and end point belongs to same word
13874 if word_range == last_word_range {
13875 selected_larger_node = true;
13876 return Selection {
13877 id: selection.id,
13878 start: word_range.start,
13879 end: word_range.end,
13880 goal: SelectionGoal::None,
13881 reversed: selection.reversed,
13882 };
13883 }
13884 }
13885 }
13886 }
13887
13888 let mut new_range = old_range.clone();
13889 while let Some((_node, containing_range)) =
13890 buffer.syntax_ancestor(new_range.clone())
13891 {
13892 new_range = match containing_range {
13893 MultiOrSingleBufferOffsetRange::Single(_) => break,
13894 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13895 };
13896 if !display_map.intersects_fold(new_range.start)
13897 && !display_map.intersects_fold(new_range.end)
13898 {
13899 break;
13900 }
13901 }
13902
13903 selected_larger_node |= new_range != old_range;
13904 Selection {
13905 id: selection.id,
13906 start: new_range.start,
13907 end: new_range.end,
13908 goal: SelectionGoal::None,
13909 reversed: selection.reversed,
13910 }
13911 })
13912 .collect::<Vec<_>>();
13913
13914 if !selected_larger_node {
13915 return; // don't put this call in the history
13916 }
13917
13918 // scroll based on transformation done to the last selection created by the user
13919 let (last_old, last_new) = old_selections
13920 .last()
13921 .zip(new_selections.last().cloned())
13922 .expect("old_selections isn't empty");
13923
13924 // revert selection
13925 let is_selection_reversed = {
13926 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13927 new_selections.last_mut().expect("checked above").reversed =
13928 should_newest_selection_be_reversed;
13929 should_newest_selection_be_reversed
13930 };
13931
13932 if selected_larger_node {
13933 self.select_syntax_node_history.disable_clearing = true;
13934 self.change_selections(None, window, cx, |s| {
13935 s.select(new_selections.clone());
13936 });
13937 self.select_syntax_node_history.disable_clearing = false;
13938 }
13939
13940 let start_row = last_new.start.to_display_point(&display_map).row().0;
13941 let end_row = last_new.end.to_display_point(&display_map).row().0;
13942 let selection_height = end_row - start_row + 1;
13943 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13944
13945 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13946 let scroll_behavior = if fits_on_the_screen {
13947 self.request_autoscroll(Autoscroll::fit(), cx);
13948 SelectSyntaxNodeScrollBehavior::FitSelection
13949 } else if is_selection_reversed {
13950 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13951 SelectSyntaxNodeScrollBehavior::CursorTop
13952 } else {
13953 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13954 SelectSyntaxNodeScrollBehavior::CursorBottom
13955 };
13956
13957 self.select_syntax_node_history.push((
13958 old_selections,
13959 scroll_behavior,
13960 is_selection_reversed,
13961 ));
13962 }
13963
13964 pub fn select_smaller_syntax_node(
13965 &mut self,
13966 _: &SelectSmallerSyntaxNode,
13967 window: &mut Window,
13968 cx: &mut Context<Self>,
13969 ) {
13970 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13971
13972 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13973 self.select_syntax_node_history.pop()
13974 {
13975 if let Some(selection) = selections.last_mut() {
13976 selection.reversed = is_selection_reversed;
13977 }
13978
13979 self.select_syntax_node_history.disable_clearing = true;
13980 self.change_selections(None, window, cx, |s| {
13981 s.select(selections.to_vec());
13982 });
13983 self.select_syntax_node_history.disable_clearing = false;
13984
13985 match scroll_behavior {
13986 SelectSyntaxNodeScrollBehavior::CursorTop => {
13987 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13988 }
13989 SelectSyntaxNodeScrollBehavior::FitSelection => {
13990 self.request_autoscroll(Autoscroll::fit(), cx);
13991 }
13992 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13993 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13994 }
13995 }
13996 }
13997 }
13998
13999 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14000 if !EditorSettings::get_global(cx).gutter.runnables {
14001 self.clear_tasks();
14002 return Task::ready(());
14003 }
14004 let project = self.project.as_ref().map(Entity::downgrade);
14005 let task_sources = self.lsp_task_sources(cx);
14006 let multi_buffer = self.buffer.downgrade();
14007 cx.spawn_in(window, async move |editor, cx| {
14008 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14009 let Some(project) = project.and_then(|p| p.upgrade()) else {
14010 return;
14011 };
14012 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14013 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14014 }) else {
14015 return;
14016 };
14017
14018 let hide_runnables = project
14019 .update(cx, |project, cx| {
14020 // Do not display any test indicators in non-dev server remote projects.
14021 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14022 })
14023 .unwrap_or(true);
14024 if hide_runnables {
14025 return;
14026 }
14027 let new_rows =
14028 cx.background_spawn({
14029 let snapshot = display_snapshot.clone();
14030 async move {
14031 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14032 }
14033 })
14034 .await;
14035 let Ok(lsp_tasks) =
14036 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14037 else {
14038 return;
14039 };
14040 let lsp_tasks = lsp_tasks.await;
14041
14042 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14043 lsp_tasks
14044 .into_iter()
14045 .flat_map(|(kind, tasks)| {
14046 tasks.into_iter().filter_map(move |(location, task)| {
14047 Some((kind.clone(), location?, task))
14048 })
14049 })
14050 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14051 let buffer = location.target.buffer;
14052 let buffer_snapshot = buffer.read(cx).snapshot();
14053 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14054 |(excerpt_id, snapshot, _)| {
14055 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14056 display_snapshot
14057 .buffer_snapshot
14058 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14059 } else {
14060 None
14061 }
14062 },
14063 );
14064 if let Some(offset) = offset {
14065 let task_buffer_range =
14066 location.target.range.to_point(&buffer_snapshot);
14067 let context_buffer_range =
14068 task_buffer_range.to_offset(&buffer_snapshot);
14069 let context_range = BufferOffset(context_buffer_range.start)
14070 ..BufferOffset(context_buffer_range.end);
14071
14072 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14073 .or_insert_with(|| RunnableTasks {
14074 templates: Vec::new(),
14075 offset,
14076 column: task_buffer_range.start.column,
14077 extra_variables: HashMap::default(),
14078 context_range,
14079 })
14080 .templates
14081 .push((kind, task.original_task().clone()));
14082 }
14083
14084 acc
14085 })
14086 }) else {
14087 return;
14088 };
14089
14090 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14091 buffer.language_settings(cx).tasks.prefer_lsp
14092 }) else {
14093 return;
14094 };
14095
14096 let rows = Self::runnable_rows(
14097 project,
14098 display_snapshot,
14099 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14100 new_rows,
14101 cx.clone(),
14102 )
14103 .await;
14104 editor
14105 .update(cx, |editor, _| {
14106 editor.clear_tasks();
14107 for (key, mut value) in rows {
14108 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14109 value.templates.extend(lsp_tasks.templates);
14110 }
14111
14112 editor.insert_tasks(key, value);
14113 }
14114 for (key, value) in lsp_tasks_by_rows {
14115 editor.insert_tasks(key, value);
14116 }
14117 })
14118 .ok();
14119 })
14120 }
14121 fn fetch_runnable_ranges(
14122 snapshot: &DisplaySnapshot,
14123 range: Range<Anchor>,
14124 ) -> Vec<language::RunnableRange> {
14125 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14126 }
14127
14128 fn runnable_rows(
14129 project: Entity<Project>,
14130 snapshot: DisplaySnapshot,
14131 prefer_lsp: bool,
14132 runnable_ranges: Vec<RunnableRange>,
14133 cx: AsyncWindowContext,
14134 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14135 cx.spawn(async move |cx| {
14136 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14137 for mut runnable in runnable_ranges {
14138 let Some(tasks) = cx
14139 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14140 .ok()
14141 else {
14142 continue;
14143 };
14144 let mut tasks = tasks.await;
14145
14146 if prefer_lsp {
14147 tasks.retain(|(task_kind, _)| {
14148 !matches!(task_kind, TaskSourceKind::Language { .. })
14149 });
14150 }
14151 if tasks.is_empty() {
14152 continue;
14153 }
14154
14155 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14156 let Some(row) = snapshot
14157 .buffer_snapshot
14158 .buffer_line_for_row(MultiBufferRow(point.row))
14159 .map(|(_, range)| range.start.row)
14160 else {
14161 continue;
14162 };
14163
14164 let context_range =
14165 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14166 runnable_rows.push((
14167 (runnable.buffer_id, row),
14168 RunnableTasks {
14169 templates: tasks,
14170 offset: snapshot
14171 .buffer_snapshot
14172 .anchor_before(runnable.run_range.start),
14173 context_range,
14174 column: point.column,
14175 extra_variables: runnable.extra_captures,
14176 },
14177 ));
14178 }
14179 runnable_rows
14180 })
14181 }
14182
14183 fn templates_with_tags(
14184 project: &Entity<Project>,
14185 runnable: &mut Runnable,
14186 cx: &mut App,
14187 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14188 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14189 let (worktree_id, file) = project
14190 .buffer_for_id(runnable.buffer, cx)
14191 .and_then(|buffer| buffer.read(cx).file())
14192 .map(|file| (file.worktree_id(cx), file.clone()))
14193 .unzip();
14194
14195 (
14196 project.task_store().read(cx).task_inventory().cloned(),
14197 worktree_id,
14198 file,
14199 )
14200 });
14201
14202 let tags = mem::take(&mut runnable.tags);
14203 let language = runnable.language.clone();
14204 cx.spawn(async move |cx| {
14205 let mut templates_with_tags = Vec::new();
14206 if let Some(inventory) = inventory {
14207 for RunnableTag(tag) in tags {
14208 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14209 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14210 }) else {
14211 return templates_with_tags;
14212 };
14213 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14214 move |(_, template)| {
14215 template.tags.iter().any(|source_tag| source_tag == &tag)
14216 },
14217 ));
14218 }
14219 }
14220 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14221
14222 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14223 // Strongest source wins; if we have worktree tag binding, prefer that to
14224 // global and language bindings;
14225 // if we have a global binding, prefer that to language binding.
14226 let first_mismatch = templates_with_tags
14227 .iter()
14228 .position(|(tag_source, _)| tag_source != leading_tag_source);
14229 if let Some(index) = first_mismatch {
14230 templates_with_tags.truncate(index);
14231 }
14232 }
14233
14234 templates_with_tags
14235 })
14236 }
14237
14238 pub fn move_to_enclosing_bracket(
14239 &mut self,
14240 _: &MoveToEnclosingBracket,
14241 window: &mut Window,
14242 cx: &mut Context<Self>,
14243 ) {
14244 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14245 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14246 s.move_offsets_with(|snapshot, selection| {
14247 let Some(enclosing_bracket_ranges) =
14248 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14249 else {
14250 return;
14251 };
14252
14253 let mut best_length = usize::MAX;
14254 let mut best_inside = false;
14255 let mut best_in_bracket_range = false;
14256 let mut best_destination = None;
14257 for (open, close) in enclosing_bracket_ranges {
14258 let close = close.to_inclusive();
14259 let length = close.end() - open.start;
14260 let inside = selection.start >= open.end && selection.end <= *close.start();
14261 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14262 || close.contains(&selection.head());
14263
14264 // If best is next to a bracket and current isn't, skip
14265 if !in_bracket_range && best_in_bracket_range {
14266 continue;
14267 }
14268
14269 // Prefer smaller lengths unless best is inside and current isn't
14270 if length > best_length && (best_inside || !inside) {
14271 continue;
14272 }
14273
14274 best_length = length;
14275 best_inside = inside;
14276 best_in_bracket_range = in_bracket_range;
14277 best_destination = Some(
14278 if close.contains(&selection.start) && close.contains(&selection.end) {
14279 if inside { open.end } else { open.start }
14280 } else if inside {
14281 *close.start()
14282 } else {
14283 *close.end()
14284 },
14285 );
14286 }
14287
14288 if let Some(destination) = best_destination {
14289 selection.collapse_to(destination, SelectionGoal::None);
14290 }
14291 })
14292 });
14293 }
14294
14295 pub fn undo_selection(
14296 &mut self,
14297 _: &UndoSelection,
14298 window: &mut Window,
14299 cx: &mut Context<Self>,
14300 ) {
14301 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14302 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14303 self.selection_history.mode = SelectionHistoryMode::Undoing;
14304 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14305 this.end_selection(window, cx);
14306 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14307 s.select_anchors(entry.selections.to_vec())
14308 });
14309 });
14310 self.selection_history.mode = SelectionHistoryMode::Normal;
14311
14312 self.select_next_state = entry.select_next_state;
14313 self.select_prev_state = entry.select_prev_state;
14314 self.add_selections_state = entry.add_selections_state;
14315 }
14316 }
14317
14318 pub fn redo_selection(
14319 &mut self,
14320 _: &RedoSelection,
14321 window: &mut Window,
14322 cx: &mut Context<Self>,
14323 ) {
14324 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14325 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14326 self.selection_history.mode = SelectionHistoryMode::Redoing;
14327 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14328 this.end_selection(window, cx);
14329 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14330 s.select_anchors(entry.selections.to_vec())
14331 });
14332 });
14333 self.selection_history.mode = SelectionHistoryMode::Normal;
14334
14335 self.select_next_state = entry.select_next_state;
14336 self.select_prev_state = entry.select_prev_state;
14337 self.add_selections_state = entry.add_selections_state;
14338 }
14339 }
14340
14341 pub fn expand_excerpts(
14342 &mut self,
14343 action: &ExpandExcerpts,
14344 _: &mut Window,
14345 cx: &mut Context<Self>,
14346 ) {
14347 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14348 }
14349
14350 pub fn expand_excerpts_down(
14351 &mut self,
14352 action: &ExpandExcerptsDown,
14353 _: &mut Window,
14354 cx: &mut Context<Self>,
14355 ) {
14356 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14357 }
14358
14359 pub fn expand_excerpts_up(
14360 &mut self,
14361 action: &ExpandExcerptsUp,
14362 _: &mut Window,
14363 cx: &mut Context<Self>,
14364 ) {
14365 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14366 }
14367
14368 pub fn expand_excerpts_for_direction(
14369 &mut self,
14370 lines: u32,
14371 direction: ExpandExcerptDirection,
14372
14373 cx: &mut Context<Self>,
14374 ) {
14375 let selections = self.selections.disjoint_anchors();
14376
14377 let lines = if lines == 0 {
14378 EditorSettings::get_global(cx).expand_excerpt_lines
14379 } else {
14380 lines
14381 };
14382
14383 self.buffer.update(cx, |buffer, cx| {
14384 let snapshot = buffer.snapshot(cx);
14385 let mut excerpt_ids = selections
14386 .iter()
14387 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14388 .collect::<Vec<_>>();
14389 excerpt_ids.sort();
14390 excerpt_ids.dedup();
14391 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14392 })
14393 }
14394
14395 pub fn expand_excerpt(
14396 &mut self,
14397 excerpt: ExcerptId,
14398 direction: ExpandExcerptDirection,
14399 window: &mut Window,
14400 cx: &mut Context<Self>,
14401 ) {
14402 let current_scroll_position = self.scroll_position(cx);
14403 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14404 let mut should_scroll_up = false;
14405
14406 if direction == ExpandExcerptDirection::Down {
14407 let multi_buffer = self.buffer.read(cx);
14408 let snapshot = multi_buffer.snapshot(cx);
14409 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14410 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14411 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14412 let buffer_snapshot = buffer.read(cx).snapshot();
14413 let excerpt_end_row =
14414 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14415 let last_row = buffer_snapshot.max_point().row;
14416 let lines_below = last_row.saturating_sub(excerpt_end_row);
14417 should_scroll_up = lines_below >= lines_to_expand;
14418 }
14419 }
14420 }
14421 }
14422
14423 self.buffer.update(cx, |buffer, cx| {
14424 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14425 });
14426
14427 if should_scroll_up {
14428 let new_scroll_position =
14429 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14430 self.set_scroll_position(new_scroll_position, window, cx);
14431 }
14432 }
14433
14434 pub fn go_to_singleton_buffer_point(
14435 &mut self,
14436 point: Point,
14437 window: &mut Window,
14438 cx: &mut Context<Self>,
14439 ) {
14440 self.go_to_singleton_buffer_range(point..point, window, cx);
14441 }
14442
14443 pub fn go_to_singleton_buffer_range(
14444 &mut self,
14445 range: Range<Point>,
14446 window: &mut Window,
14447 cx: &mut Context<Self>,
14448 ) {
14449 let multibuffer = self.buffer().read(cx);
14450 let Some(buffer) = multibuffer.as_singleton() else {
14451 return;
14452 };
14453 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14454 return;
14455 };
14456 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14457 return;
14458 };
14459 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14460 s.select_anchor_ranges([start..end])
14461 });
14462 }
14463
14464 pub fn go_to_diagnostic(
14465 &mut self,
14466 _: &GoToDiagnostic,
14467 window: &mut Window,
14468 cx: &mut Context<Self>,
14469 ) {
14470 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14471 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14472 }
14473
14474 pub fn go_to_prev_diagnostic(
14475 &mut self,
14476 _: &GoToPreviousDiagnostic,
14477 window: &mut Window,
14478 cx: &mut Context<Self>,
14479 ) {
14480 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14481 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14482 }
14483
14484 pub fn go_to_diagnostic_impl(
14485 &mut self,
14486 direction: Direction,
14487 window: &mut Window,
14488 cx: &mut Context<Self>,
14489 ) {
14490 let buffer = self.buffer.read(cx).snapshot(cx);
14491 let selection = self.selections.newest::<usize>(cx);
14492
14493 let mut active_group_id = None;
14494 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14495 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14496 active_group_id = Some(active_group.group_id);
14497 }
14498 }
14499
14500 fn filtered(
14501 snapshot: EditorSnapshot,
14502 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14503 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14504 diagnostics
14505 .filter(|entry| entry.range.start != entry.range.end)
14506 .filter(|entry| !entry.diagnostic.is_unnecessary)
14507 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14508 }
14509
14510 let snapshot = self.snapshot(window, cx);
14511 let before = filtered(
14512 snapshot.clone(),
14513 buffer
14514 .diagnostics_in_range(0..selection.start)
14515 .filter(|entry| entry.range.start <= selection.start),
14516 );
14517 let after = filtered(
14518 snapshot,
14519 buffer
14520 .diagnostics_in_range(selection.start..buffer.len())
14521 .filter(|entry| entry.range.start >= selection.start),
14522 );
14523
14524 let mut found: Option<DiagnosticEntry<usize>> = None;
14525 if direction == Direction::Prev {
14526 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14527 {
14528 for diagnostic in prev_diagnostics.into_iter().rev() {
14529 if diagnostic.range.start != selection.start
14530 || active_group_id
14531 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14532 {
14533 found = Some(diagnostic);
14534 break 'outer;
14535 }
14536 }
14537 }
14538 } else {
14539 for diagnostic in after.chain(before) {
14540 if diagnostic.range.start != selection.start
14541 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14542 {
14543 found = Some(diagnostic);
14544 break;
14545 }
14546 }
14547 }
14548 let Some(next_diagnostic) = found else {
14549 return;
14550 };
14551
14552 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14553 return;
14554 };
14555 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14556 s.select_ranges(vec![
14557 next_diagnostic.range.start..next_diagnostic.range.start,
14558 ])
14559 });
14560 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14561 self.refresh_inline_completion(false, true, window, cx);
14562 }
14563
14564 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14565 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14566 let snapshot = self.snapshot(window, cx);
14567 let selection = self.selections.newest::<Point>(cx);
14568 self.go_to_hunk_before_or_after_position(
14569 &snapshot,
14570 selection.head(),
14571 Direction::Next,
14572 window,
14573 cx,
14574 );
14575 }
14576
14577 pub fn go_to_hunk_before_or_after_position(
14578 &mut self,
14579 snapshot: &EditorSnapshot,
14580 position: Point,
14581 direction: Direction,
14582 window: &mut Window,
14583 cx: &mut Context<Editor>,
14584 ) {
14585 let row = if direction == Direction::Next {
14586 self.hunk_after_position(snapshot, position)
14587 .map(|hunk| hunk.row_range.start)
14588 } else {
14589 self.hunk_before_position(snapshot, position)
14590 };
14591
14592 if let Some(row) = row {
14593 let destination = Point::new(row.0, 0);
14594 let autoscroll = Autoscroll::center();
14595
14596 self.unfold_ranges(&[destination..destination], false, false, cx);
14597 self.change_selections(Some(autoscroll), window, cx, |s| {
14598 s.select_ranges([destination..destination]);
14599 });
14600 }
14601 }
14602
14603 fn hunk_after_position(
14604 &mut self,
14605 snapshot: &EditorSnapshot,
14606 position: Point,
14607 ) -> Option<MultiBufferDiffHunk> {
14608 snapshot
14609 .buffer_snapshot
14610 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14611 .find(|hunk| hunk.row_range.start.0 > position.row)
14612 .or_else(|| {
14613 snapshot
14614 .buffer_snapshot
14615 .diff_hunks_in_range(Point::zero()..position)
14616 .find(|hunk| hunk.row_range.end.0 < position.row)
14617 })
14618 }
14619
14620 fn go_to_prev_hunk(
14621 &mut self,
14622 _: &GoToPreviousHunk,
14623 window: &mut Window,
14624 cx: &mut Context<Self>,
14625 ) {
14626 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14627 let snapshot = self.snapshot(window, cx);
14628 let selection = self.selections.newest::<Point>(cx);
14629 self.go_to_hunk_before_or_after_position(
14630 &snapshot,
14631 selection.head(),
14632 Direction::Prev,
14633 window,
14634 cx,
14635 );
14636 }
14637
14638 fn hunk_before_position(
14639 &mut self,
14640 snapshot: &EditorSnapshot,
14641 position: Point,
14642 ) -> Option<MultiBufferRow> {
14643 snapshot
14644 .buffer_snapshot
14645 .diff_hunk_before(position)
14646 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14647 }
14648
14649 fn go_to_next_change(
14650 &mut self,
14651 _: &GoToNextChange,
14652 window: &mut Window,
14653 cx: &mut Context<Self>,
14654 ) {
14655 if let Some(selections) = self
14656 .change_list
14657 .next_change(1, Direction::Next)
14658 .map(|s| s.to_vec())
14659 {
14660 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14661 let map = s.display_map();
14662 s.select_display_ranges(selections.iter().map(|a| {
14663 let point = a.to_display_point(&map);
14664 point..point
14665 }))
14666 })
14667 }
14668 }
14669
14670 fn go_to_previous_change(
14671 &mut self,
14672 _: &GoToPreviousChange,
14673 window: &mut Window,
14674 cx: &mut Context<Self>,
14675 ) {
14676 if let Some(selections) = self
14677 .change_list
14678 .next_change(1, Direction::Prev)
14679 .map(|s| s.to_vec())
14680 {
14681 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14682 let map = s.display_map();
14683 s.select_display_ranges(selections.iter().map(|a| {
14684 let point = a.to_display_point(&map);
14685 point..point
14686 }))
14687 })
14688 }
14689 }
14690
14691 fn go_to_line<T: 'static>(
14692 &mut self,
14693 position: Anchor,
14694 highlight_color: Option<Hsla>,
14695 window: &mut Window,
14696 cx: &mut Context<Self>,
14697 ) {
14698 let snapshot = self.snapshot(window, cx).display_snapshot;
14699 let position = position.to_point(&snapshot.buffer_snapshot);
14700 let start = snapshot
14701 .buffer_snapshot
14702 .clip_point(Point::new(position.row, 0), Bias::Left);
14703 let end = start + Point::new(1, 0);
14704 let start = snapshot.buffer_snapshot.anchor_before(start);
14705 let end = snapshot.buffer_snapshot.anchor_before(end);
14706
14707 self.highlight_rows::<T>(
14708 start..end,
14709 highlight_color
14710 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14711 Default::default(),
14712 cx,
14713 );
14714
14715 if self.buffer.read(cx).is_singleton() {
14716 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14717 }
14718 }
14719
14720 pub fn go_to_definition(
14721 &mut self,
14722 _: &GoToDefinition,
14723 window: &mut Window,
14724 cx: &mut Context<Self>,
14725 ) -> Task<Result<Navigated>> {
14726 let definition =
14727 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14728 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14729 cx.spawn_in(window, async move |editor, cx| {
14730 if definition.await? == Navigated::Yes {
14731 return Ok(Navigated::Yes);
14732 }
14733 match fallback_strategy {
14734 GoToDefinitionFallback::None => Ok(Navigated::No),
14735 GoToDefinitionFallback::FindAllReferences => {
14736 match editor.update_in(cx, |editor, window, cx| {
14737 editor.find_all_references(&FindAllReferences, window, cx)
14738 })? {
14739 Some(references) => references.await,
14740 None => Ok(Navigated::No),
14741 }
14742 }
14743 }
14744 })
14745 }
14746
14747 pub fn go_to_declaration(
14748 &mut self,
14749 _: &GoToDeclaration,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) -> Task<Result<Navigated>> {
14753 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14754 }
14755
14756 pub fn go_to_declaration_split(
14757 &mut self,
14758 _: &GoToDeclaration,
14759 window: &mut Window,
14760 cx: &mut Context<Self>,
14761 ) -> Task<Result<Navigated>> {
14762 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14763 }
14764
14765 pub fn go_to_implementation(
14766 &mut self,
14767 _: &GoToImplementation,
14768 window: &mut Window,
14769 cx: &mut Context<Self>,
14770 ) -> Task<Result<Navigated>> {
14771 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14772 }
14773
14774 pub fn go_to_implementation_split(
14775 &mut self,
14776 _: &GoToImplementationSplit,
14777 window: &mut Window,
14778 cx: &mut Context<Self>,
14779 ) -> Task<Result<Navigated>> {
14780 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14781 }
14782
14783 pub fn go_to_type_definition(
14784 &mut self,
14785 _: &GoToTypeDefinition,
14786 window: &mut Window,
14787 cx: &mut Context<Self>,
14788 ) -> Task<Result<Navigated>> {
14789 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14790 }
14791
14792 pub fn go_to_definition_split(
14793 &mut self,
14794 _: &GoToDefinitionSplit,
14795 window: &mut Window,
14796 cx: &mut Context<Self>,
14797 ) -> Task<Result<Navigated>> {
14798 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14799 }
14800
14801 pub fn go_to_type_definition_split(
14802 &mut self,
14803 _: &GoToTypeDefinitionSplit,
14804 window: &mut Window,
14805 cx: &mut Context<Self>,
14806 ) -> Task<Result<Navigated>> {
14807 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14808 }
14809
14810 fn go_to_definition_of_kind(
14811 &mut self,
14812 kind: GotoDefinitionKind,
14813 split: bool,
14814 window: &mut Window,
14815 cx: &mut Context<Self>,
14816 ) -> Task<Result<Navigated>> {
14817 let Some(provider) = self.semantics_provider.clone() else {
14818 return Task::ready(Ok(Navigated::No));
14819 };
14820 let head = self.selections.newest::<usize>(cx).head();
14821 let buffer = self.buffer.read(cx);
14822 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14823 text_anchor
14824 } else {
14825 return Task::ready(Ok(Navigated::No));
14826 };
14827
14828 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14829 return Task::ready(Ok(Navigated::No));
14830 };
14831
14832 cx.spawn_in(window, async move |editor, cx| {
14833 let definitions = definitions.await?;
14834 let navigated = editor
14835 .update_in(cx, |editor, window, cx| {
14836 editor.navigate_to_hover_links(
14837 Some(kind),
14838 definitions
14839 .into_iter()
14840 .filter(|location| {
14841 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14842 })
14843 .map(HoverLink::Text)
14844 .collect::<Vec<_>>(),
14845 split,
14846 window,
14847 cx,
14848 )
14849 })?
14850 .await?;
14851 anyhow::Ok(navigated)
14852 })
14853 }
14854
14855 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14856 let selection = self.selections.newest_anchor();
14857 let head = selection.head();
14858 let tail = selection.tail();
14859
14860 let Some((buffer, start_position)) =
14861 self.buffer.read(cx).text_anchor_for_position(head, cx)
14862 else {
14863 return;
14864 };
14865
14866 let end_position = if head != tail {
14867 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14868 return;
14869 };
14870 Some(pos)
14871 } else {
14872 None
14873 };
14874
14875 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14876 let url = if let Some(end_pos) = end_position {
14877 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14878 } else {
14879 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14880 };
14881
14882 if let Some(url) = url {
14883 editor.update(cx, |_, cx| {
14884 cx.open_url(&url);
14885 })
14886 } else {
14887 Ok(())
14888 }
14889 });
14890
14891 url_finder.detach();
14892 }
14893
14894 pub fn open_selected_filename(
14895 &mut self,
14896 _: &OpenSelectedFilename,
14897 window: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) {
14900 let Some(workspace) = self.workspace() else {
14901 return;
14902 };
14903
14904 let position = self.selections.newest_anchor().head();
14905
14906 let Some((buffer, buffer_position)) =
14907 self.buffer.read(cx).text_anchor_for_position(position, cx)
14908 else {
14909 return;
14910 };
14911
14912 let project = self.project.clone();
14913
14914 cx.spawn_in(window, async move |_, cx| {
14915 let result = find_file(&buffer, project, buffer_position, cx).await;
14916
14917 if let Some((_, path)) = result {
14918 workspace
14919 .update_in(cx, |workspace, window, cx| {
14920 workspace.open_resolved_path(path, window, cx)
14921 })?
14922 .await?;
14923 }
14924 anyhow::Ok(())
14925 })
14926 .detach();
14927 }
14928
14929 pub(crate) fn navigate_to_hover_links(
14930 &mut self,
14931 kind: Option<GotoDefinitionKind>,
14932 mut definitions: Vec<HoverLink>,
14933 split: bool,
14934 window: &mut Window,
14935 cx: &mut Context<Editor>,
14936 ) -> Task<Result<Navigated>> {
14937 // If there is one definition, just open it directly
14938 if definitions.len() == 1 {
14939 let definition = definitions.pop().unwrap();
14940
14941 enum TargetTaskResult {
14942 Location(Option<Location>),
14943 AlreadyNavigated,
14944 }
14945
14946 let target_task = match definition {
14947 HoverLink::Text(link) => {
14948 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14949 }
14950 HoverLink::InlayHint(lsp_location, server_id) => {
14951 let computation =
14952 self.compute_target_location(lsp_location, server_id, window, cx);
14953 cx.background_spawn(async move {
14954 let location = computation.await?;
14955 Ok(TargetTaskResult::Location(location))
14956 })
14957 }
14958 HoverLink::Url(url) => {
14959 cx.open_url(&url);
14960 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14961 }
14962 HoverLink::File(path) => {
14963 if let Some(workspace) = self.workspace() {
14964 cx.spawn_in(window, async move |_, cx| {
14965 workspace
14966 .update_in(cx, |workspace, window, cx| {
14967 workspace.open_resolved_path(path, window, cx)
14968 })?
14969 .await
14970 .map(|_| TargetTaskResult::AlreadyNavigated)
14971 })
14972 } else {
14973 Task::ready(Ok(TargetTaskResult::Location(None)))
14974 }
14975 }
14976 };
14977 cx.spawn_in(window, async move |editor, cx| {
14978 let target = match target_task.await.context("target resolution task")? {
14979 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14980 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14981 TargetTaskResult::Location(Some(target)) => target,
14982 };
14983
14984 editor.update_in(cx, |editor, window, cx| {
14985 let Some(workspace) = editor.workspace() else {
14986 return Navigated::No;
14987 };
14988 let pane = workspace.read(cx).active_pane().clone();
14989
14990 let range = target.range.to_point(target.buffer.read(cx));
14991 let range = editor.range_for_match(&range);
14992 let range = collapse_multiline_range(range);
14993
14994 if !split
14995 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14996 {
14997 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14998 } else {
14999 window.defer(cx, move |window, cx| {
15000 let target_editor: Entity<Self> =
15001 workspace.update(cx, |workspace, cx| {
15002 let pane = if split {
15003 workspace.adjacent_pane(window, cx)
15004 } else {
15005 workspace.active_pane().clone()
15006 };
15007
15008 workspace.open_project_item(
15009 pane,
15010 target.buffer.clone(),
15011 true,
15012 true,
15013 window,
15014 cx,
15015 )
15016 });
15017 target_editor.update(cx, |target_editor, cx| {
15018 // When selecting a definition in a different buffer, disable the nav history
15019 // to avoid creating a history entry at the previous cursor location.
15020 pane.update(cx, |pane, _| pane.disable_history());
15021 target_editor.go_to_singleton_buffer_range(range, window, cx);
15022 pane.update(cx, |pane, _| pane.enable_history());
15023 });
15024 });
15025 }
15026 Navigated::Yes
15027 })
15028 })
15029 } else if !definitions.is_empty() {
15030 cx.spawn_in(window, async move |editor, cx| {
15031 let (title, location_tasks, workspace) = editor
15032 .update_in(cx, |editor, window, cx| {
15033 let tab_kind = match kind {
15034 Some(GotoDefinitionKind::Implementation) => "Implementations",
15035 _ => "Definitions",
15036 };
15037 let title = definitions
15038 .iter()
15039 .find_map(|definition| match definition {
15040 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15041 let buffer = origin.buffer.read(cx);
15042 format!(
15043 "{} for {}",
15044 tab_kind,
15045 buffer
15046 .text_for_range(origin.range.clone())
15047 .collect::<String>()
15048 )
15049 }),
15050 HoverLink::InlayHint(_, _) => None,
15051 HoverLink::Url(_) => None,
15052 HoverLink::File(_) => None,
15053 })
15054 .unwrap_or(tab_kind.to_string());
15055 let location_tasks = definitions
15056 .into_iter()
15057 .map(|definition| match definition {
15058 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15059 HoverLink::InlayHint(lsp_location, server_id) => editor
15060 .compute_target_location(lsp_location, server_id, window, cx),
15061 HoverLink::Url(_) => Task::ready(Ok(None)),
15062 HoverLink::File(_) => Task::ready(Ok(None)),
15063 })
15064 .collect::<Vec<_>>();
15065 (title, location_tasks, editor.workspace().clone())
15066 })
15067 .context("location tasks preparation")?;
15068
15069 let locations = future::join_all(location_tasks)
15070 .await
15071 .into_iter()
15072 .filter_map(|location| location.transpose())
15073 .collect::<Result<_>>()
15074 .context("location tasks")?;
15075
15076 let Some(workspace) = workspace else {
15077 return Ok(Navigated::No);
15078 };
15079 let opened = workspace
15080 .update_in(cx, |workspace, window, cx| {
15081 Self::open_locations_in_multibuffer(
15082 workspace,
15083 locations,
15084 title,
15085 split,
15086 MultibufferSelectionMode::First,
15087 window,
15088 cx,
15089 )
15090 })
15091 .ok();
15092
15093 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15094 })
15095 } else {
15096 Task::ready(Ok(Navigated::No))
15097 }
15098 }
15099
15100 fn compute_target_location(
15101 &self,
15102 lsp_location: lsp::Location,
15103 server_id: LanguageServerId,
15104 window: &mut Window,
15105 cx: &mut Context<Self>,
15106 ) -> Task<anyhow::Result<Option<Location>>> {
15107 let Some(project) = self.project.clone() else {
15108 return Task::ready(Ok(None));
15109 };
15110
15111 cx.spawn_in(window, async move |editor, cx| {
15112 let location_task = editor.update(cx, |_, cx| {
15113 project.update(cx, |project, cx| {
15114 let language_server_name = project
15115 .language_server_statuses(cx)
15116 .find(|(id, _)| server_id == *id)
15117 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15118 language_server_name.map(|language_server_name| {
15119 project.open_local_buffer_via_lsp(
15120 lsp_location.uri.clone(),
15121 server_id,
15122 language_server_name,
15123 cx,
15124 )
15125 })
15126 })
15127 })?;
15128 let location = match location_task {
15129 Some(task) => Some({
15130 let target_buffer_handle = task.await.context("open local buffer")?;
15131 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15132 let target_start = target_buffer
15133 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15134 let target_end = target_buffer
15135 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15136 target_buffer.anchor_after(target_start)
15137 ..target_buffer.anchor_before(target_end)
15138 })?;
15139 Location {
15140 buffer: target_buffer_handle,
15141 range,
15142 }
15143 }),
15144 None => None,
15145 };
15146 Ok(location)
15147 })
15148 }
15149
15150 pub fn find_all_references(
15151 &mut self,
15152 _: &FindAllReferences,
15153 window: &mut Window,
15154 cx: &mut Context<Self>,
15155 ) -> Option<Task<Result<Navigated>>> {
15156 let selection = self.selections.newest::<usize>(cx);
15157 let multi_buffer = self.buffer.read(cx);
15158 let head = selection.head();
15159
15160 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15161 let head_anchor = multi_buffer_snapshot.anchor_at(
15162 head,
15163 if head < selection.tail() {
15164 Bias::Right
15165 } else {
15166 Bias::Left
15167 },
15168 );
15169
15170 match self
15171 .find_all_references_task_sources
15172 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15173 {
15174 Ok(_) => {
15175 log::info!(
15176 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15177 );
15178 return None;
15179 }
15180 Err(i) => {
15181 self.find_all_references_task_sources.insert(i, head_anchor);
15182 }
15183 }
15184
15185 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15186 let workspace = self.workspace()?;
15187 let project = workspace.read(cx).project().clone();
15188 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15189 Some(cx.spawn_in(window, async move |editor, cx| {
15190 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15191 if let Ok(i) = editor
15192 .find_all_references_task_sources
15193 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15194 {
15195 editor.find_all_references_task_sources.remove(i);
15196 }
15197 });
15198
15199 let locations = references.await?;
15200 if locations.is_empty() {
15201 return anyhow::Ok(Navigated::No);
15202 }
15203
15204 workspace.update_in(cx, |workspace, window, cx| {
15205 let title = locations
15206 .first()
15207 .as_ref()
15208 .map(|location| {
15209 let buffer = location.buffer.read(cx);
15210 format!(
15211 "References to `{}`",
15212 buffer
15213 .text_for_range(location.range.clone())
15214 .collect::<String>()
15215 )
15216 })
15217 .unwrap();
15218 Self::open_locations_in_multibuffer(
15219 workspace,
15220 locations,
15221 title,
15222 false,
15223 MultibufferSelectionMode::First,
15224 window,
15225 cx,
15226 );
15227 Navigated::Yes
15228 })
15229 }))
15230 }
15231
15232 /// Opens a multibuffer with the given project locations in it
15233 pub fn open_locations_in_multibuffer(
15234 workspace: &mut Workspace,
15235 mut locations: Vec<Location>,
15236 title: String,
15237 split: bool,
15238 multibuffer_selection_mode: MultibufferSelectionMode,
15239 window: &mut Window,
15240 cx: &mut Context<Workspace>,
15241 ) {
15242 // If there are multiple definitions, open them in a multibuffer
15243 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15244 let mut locations = locations.into_iter().peekable();
15245 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15246 let capability = workspace.project().read(cx).capability();
15247
15248 let excerpt_buffer = cx.new(|cx| {
15249 let mut multibuffer = MultiBuffer::new(capability);
15250 while let Some(location) = locations.next() {
15251 let buffer = location.buffer.read(cx);
15252 let mut ranges_for_buffer = Vec::new();
15253 let range = location.range.to_point(buffer);
15254 ranges_for_buffer.push(range.clone());
15255
15256 while let Some(next_location) = locations.peek() {
15257 if next_location.buffer == location.buffer {
15258 ranges_for_buffer.push(next_location.range.to_point(buffer));
15259 locations.next();
15260 } else {
15261 break;
15262 }
15263 }
15264
15265 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15266 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15267 PathKey::for_buffer(&location.buffer, cx),
15268 location.buffer.clone(),
15269 ranges_for_buffer,
15270 DEFAULT_MULTIBUFFER_CONTEXT,
15271 cx,
15272 );
15273 ranges.extend(new_ranges)
15274 }
15275
15276 multibuffer.with_title(title)
15277 });
15278
15279 let editor = cx.new(|cx| {
15280 Editor::for_multibuffer(
15281 excerpt_buffer,
15282 Some(workspace.project().clone()),
15283 window,
15284 cx,
15285 )
15286 });
15287 editor.update(cx, |editor, cx| {
15288 match multibuffer_selection_mode {
15289 MultibufferSelectionMode::First => {
15290 if let Some(first_range) = ranges.first() {
15291 editor.change_selections(None, window, cx, |selections| {
15292 selections.clear_disjoint();
15293 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15294 });
15295 }
15296 editor.highlight_background::<Self>(
15297 &ranges,
15298 |theme| theme.editor_highlighted_line_background,
15299 cx,
15300 );
15301 }
15302 MultibufferSelectionMode::All => {
15303 editor.change_selections(None, window, cx, |selections| {
15304 selections.clear_disjoint();
15305 selections.select_anchor_ranges(ranges);
15306 });
15307 }
15308 }
15309 editor.register_buffers_with_language_servers(cx);
15310 });
15311
15312 let item = Box::new(editor);
15313 let item_id = item.item_id();
15314
15315 if split {
15316 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15317 } else {
15318 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15319 let (preview_item_id, preview_item_idx) =
15320 workspace.active_pane().read_with(cx, |pane, _| {
15321 (pane.preview_item_id(), pane.preview_item_idx())
15322 });
15323
15324 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15325
15326 if let Some(preview_item_id) = preview_item_id {
15327 workspace.active_pane().update(cx, |pane, cx| {
15328 pane.remove_item(preview_item_id, false, false, window, cx);
15329 });
15330 }
15331 } else {
15332 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15333 }
15334 }
15335 workspace.active_pane().update(cx, |pane, cx| {
15336 pane.set_preview_item_id(Some(item_id), cx);
15337 });
15338 }
15339
15340 pub fn rename(
15341 &mut self,
15342 _: &Rename,
15343 window: &mut Window,
15344 cx: &mut Context<Self>,
15345 ) -> Option<Task<Result<()>>> {
15346 use language::ToOffset as _;
15347
15348 let provider = self.semantics_provider.clone()?;
15349 let selection = self.selections.newest_anchor().clone();
15350 let (cursor_buffer, cursor_buffer_position) = self
15351 .buffer
15352 .read(cx)
15353 .text_anchor_for_position(selection.head(), cx)?;
15354 let (tail_buffer, cursor_buffer_position_end) = self
15355 .buffer
15356 .read(cx)
15357 .text_anchor_for_position(selection.tail(), cx)?;
15358 if tail_buffer != cursor_buffer {
15359 return None;
15360 }
15361
15362 let snapshot = cursor_buffer.read(cx).snapshot();
15363 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15364 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15365 let prepare_rename = provider
15366 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15367 .unwrap_or_else(|| Task::ready(Ok(None)));
15368 drop(snapshot);
15369
15370 Some(cx.spawn_in(window, async move |this, cx| {
15371 let rename_range = if let Some(range) = prepare_rename.await? {
15372 Some(range)
15373 } else {
15374 this.update(cx, |this, cx| {
15375 let buffer = this.buffer.read(cx).snapshot(cx);
15376 let mut buffer_highlights = this
15377 .document_highlights_for_position(selection.head(), &buffer)
15378 .filter(|highlight| {
15379 highlight.start.excerpt_id == selection.head().excerpt_id
15380 && highlight.end.excerpt_id == selection.head().excerpt_id
15381 });
15382 buffer_highlights
15383 .next()
15384 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15385 })?
15386 };
15387 if let Some(rename_range) = rename_range {
15388 this.update_in(cx, |this, window, cx| {
15389 let snapshot = cursor_buffer.read(cx).snapshot();
15390 let rename_buffer_range = rename_range.to_offset(&snapshot);
15391 let cursor_offset_in_rename_range =
15392 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15393 let cursor_offset_in_rename_range_end =
15394 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15395
15396 this.take_rename(false, window, cx);
15397 let buffer = this.buffer.read(cx).read(cx);
15398 let cursor_offset = selection.head().to_offset(&buffer);
15399 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15400 let rename_end = rename_start + rename_buffer_range.len();
15401 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15402 let mut old_highlight_id = None;
15403 let old_name: Arc<str> = buffer
15404 .chunks(rename_start..rename_end, true)
15405 .map(|chunk| {
15406 if old_highlight_id.is_none() {
15407 old_highlight_id = chunk.syntax_highlight_id;
15408 }
15409 chunk.text
15410 })
15411 .collect::<String>()
15412 .into();
15413
15414 drop(buffer);
15415
15416 // Position the selection in the rename editor so that it matches the current selection.
15417 this.show_local_selections = false;
15418 let rename_editor = cx.new(|cx| {
15419 let mut editor = Editor::single_line(window, cx);
15420 editor.buffer.update(cx, |buffer, cx| {
15421 buffer.edit([(0..0, old_name.clone())], None, cx)
15422 });
15423 let rename_selection_range = match cursor_offset_in_rename_range
15424 .cmp(&cursor_offset_in_rename_range_end)
15425 {
15426 Ordering::Equal => {
15427 editor.select_all(&SelectAll, window, cx);
15428 return editor;
15429 }
15430 Ordering::Less => {
15431 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15432 }
15433 Ordering::Greater => {
15434 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15435 }
15436 };
15437 if rename_selection_range.end > old_name.len() {
15438 editor.select_all(&SelectAll, window, cx);
15439 } else {
15440 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15441 s.select_ranges([rename_selection_range]);
15442 });
15443 }
15444 editor
15445 });
15446 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15447 if e == &EditorEvent::Focused {
15448 cx.emit(EditorEvent::FocusedIn)
15449 }
15450 })
15451 .detach();
15452
15453 let write_highlights =
15454 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15455 let read_highlights =
15456 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15457 let ranges = write_highlights
15458 .iter()
15459 .flat_map(|(_, ranges)| ranges.iter())
15460 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15461 .cloned()
15462 .collect();
15463
15464 this.highlight_text::<Rename>(
15465 ranges,
15466 HighlightStyle {
15467 fade_out: Some(0.6),
15468 ..Default::default()
15469 },
15470 cx,
15471 );
15472 let rename_focus_handle = rename_editor.focus_handle(cx);
15473 window.focus(&rename_focus_handle);
15474 let block_id = this.insert_blocks(
15475 [BlockProperties {
15476 style: BlockStyle::Flex,
15477 placement: BlockPlacement::Below(range.start),
15478 height: Some(1),
15479 render: Arc::new({
15480 let rename_editor = rename_editor.clone();
15481 move |cx: &mut BlockContext| {
15482 let mut text_style = cx.editor_style.text.clone();
15483 if let Some(highlight_style) = old_highlight_id
15484 .and_then(|h| h.style(&cx.editor_style.syntax))
15485 {
15486 text_style = text_style.highlight(highlight_style);
15487 }
15488 div()
15489 .block_mouse_except_scroll()
15490 .pl(cx.anchor_x)
15491 .child(EditorElement::new(
15492 &rename_editor,
15493 EditorStyle {
15494 background: cx.theme().system().transparent,
15495 local_player: cx.editor_style.local_player,
15496 text: text_style,
15497 scrollbar_width: cx.editor_style.scrollbar_width,
15498 syntax: cx.editor_style.syntax.clone(),
15499 status: cx.editor_style.status.clone(),
15500 inlay_hints_style: HighlightStyle {
15501 font_weight: Some(FontWeight::BOLD),
15502 ..make_inlay_hints_style(cx.app)
15503 },
15504 inline_completion_styles: make_suggestion_styles(
15505 cx.app,
15506 ),
15507 ..EditorStyle::default()
15508 },
15509 ))
15510 .into_any_element()
15511 }
15512 }),
15513 priority: 0,
15514 render_in_minimap: true,
15515 }],
15516 Some(Autoscroll::fit()),
15517 cx,
15518 )[0];
15519 this.pending_rename = Some(RenameState {
15520 range,
15521 old_name,
15522 editor: rename_editor,
15523 block_id,
15524 });
15525 })?;
15526 }
15527
15528 Ok(())
15529 }))
15530 }
15531
15532 pub fn confirm_rename(
15533 &mut self,
15534 _: &ConfirmRename,
15535 window: &mut Window,
15536 cx: &mut Context<Self>,
15537 ) -> Option<Task<Result<()>>> {
15538 let rename = self.take_rename(false, window, cx)?;
15539 let workspace = self.workspace()?.downgrade();
15540 let (buffer, start) = self
15541 .buffer
15542 .read(cx)
15543 .text_anchor_for_position(rename.range.start, cx)?;
15544 let (end_buffer, _) = self
15545 .buffer
15546 .read(cx)
15547 .text_anchor_for_position(rename.range.end, cx)?;
15548 if buffer != end_buffer {
15549 return None;
15550 }
15551
15552 let old_name = rename.old_name;
15553 let new_name = rename.editor.read(cx).text(cx);
15554
15555 let rename = self.semantics_provider.as_ref()?.perform_rename(
15556 &buffer,
15557 start,
15558 new_name.clone(),
15559 cx,
15560 )?;
15561
15562 Some(cx.spawn_in(window, async move |editor, cx| {
15563 let project_transaction = rename.await?;
15564 Self::open_project_transaction(
15565 &editor,
15566 workspace,
15567 project_transaction,
15568 format!("Rename: {} → {}", old_name, new_name),
15569 cx,
15570 )
15571 .await?;
15572
15573 editor.update(cx, |editor, cx| {
15574 editor.refresh_document_highlights(cx);
15575 })?;
15576 Ok(())
15577 }))
15578 }
15579
15580 fn take_rename(
15581 &mut self,
15582 moving_cursor: bool,
15583 window: &mut Window,
15584 cx: &mut Context<Self>,
15585 ) -> Option<RenameState> {
15586 let rename = self.pending_rename.take()?;
15587 if rename.editor.focus_handle(cx).is_focused(window) {
15588 window.focus(&self.focus_handle);
15589 }
15590
15591 self.remove_blocks(
15592 [rename.block_id].into_iter().collect(),
15593 Some(Autoscroll::fit()),
15594 cx,
15595 );
15596 self.clear_highlights::<Rename>(cx);
15597 self.show_local_selections = true;
15598
15599 if moving_cursor {
15600 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15601 editor.selections.newest::<usize>(cx).head()
15602 });
15603
15604 // Update the selection to match the position of the selection inside
15605 // the rename editor.
15606 let snapshot = self.buffer.read(cx).read(cx);
15607 let rename_range = rename.range.to_offset(&snapshot);
15608 let cursor_in_editor = snapshot
15609 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15610 .min(rename_range.end);
15611 drop(snapshot);
15612
15613 self.change_selections(None, window, cx, |s| {
15614 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15615 });
15616 } else {
15617 self.refresh_document_highlights(cx);
15618 }
15619
15620 Some(rename)
15621 }
15622
15623 pub fn pending_rename(&self) -> Option<&RenameState> {
15624 self.pending_rename.as_ref()
15625 }
15626
15627 fn format(
15628 &mut self,
15629 _: &Format,
15630 window: &mut Window,
15631 cx: &mut Context<Self>,
15632 ) -> Option<Task<Result<()>>> {
15633 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15634
15635 let project = match &self.project {
15636 Some(project) => project.clone(),
15637 None => return None,
15638 };
15639
15640 Some(self.perform_format(
15641 project,
15642 FormatTrigger::Manual,
15643 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15644 window,
15645 cx,
15646 ))
15647 }
15648
15649 fn format_selections(
15650 &mut self,
15651 _: &FormatSelections,
15652 window: &mut Window,
15653 cx: &mut Context<Self>,
15654 ) -> Option<Task<Result<()>>> {
15655 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15656
15657 let project = match &self.project {
15658 Some(project) => project.clone(),
15659 None => return None,
15660 };
15661
15662 let ranges = self
15663 .selections
15664 .all_adjusted(cx)
15665 .into_iter()
15666 .map(|selection| selection.range())
15667 .collect_vec();
15668
15669 Some(self.perform_format(
15670 project,
15671 FormatTrigger::Manual,
15672 FormatTarget::Ranges(ranges),
15673 window,
15674 cx,
15675 ))
15676 }
15677
15678 fn perform_format(
15679 &mut self,
15680 project: Entity<Project>,
15681 trigger: FormatTrigger,
15682 target: FormatTarget,
15683 window: &mut Window,
15684 cx: &mut Context<Self>,
15685 ) -> Task<Result<()>> {
15686 let buffer = self.buffer.clone();
15687 let (buffers, target) = match target {
15688 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15689 FormatTarget::Ranges(selection_ranges) => {
15690 let multi_buffer = buffer.read(cx);
15691 let snapshot = multi_buffer.read(cx);
15692 let mut buffers = HashSet::default();
15693 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15694 BTreeMap::new();
15695 for selection_range in selection_ranges {
15696 for (buffer, buffer_range, _) in
15697 snapshot.range_to_buffer_ranges(selection_range)
15698 {
15699 let buffer_id = buffer.remote_id();
15700 let start = buffer.anchor_before(buffer_range.start);
15701 let end = buffer.anchor_after(buffer_range.end);
15702 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15703 buffer_id_to_ranges
15704 .entry(buffer_id)
15705 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15706 .or_insert_with(|| vec![start..end]);
15707 }
15708 }
15709 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15710 }
15711 };
15712
15713 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15714 let selections_prev = transaction_id_prev
15715 .and_then(|transaction_id_prev| {
15716 // default to selections as they were after the last edit, if we have them,
15717 // instead of how they are now.
15718 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15719 // will take you back to where you made the last edit, instead of staying where you scrolled
15720 self.selection_history
15721 .transaction(transaction_id_prev)
15722 .map(|t| t.0.clone())
15723 })
15724 .unwrap_or_else(|| {
15725 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15726 self.selections.disjoint_anchors()
15727 });
15728
15729 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15730 let format = project.update(cx, |project, cx| {
15731 project.format(buffers, target, true, trigger, cx)
15732 });
15733
15734 cx.spawn_in(window, async move |editor, cx| {
15735 let transaction = futures::select_biased! {
15736 transaction = format.log_err().fuse() => transaction,
15737 () = timeout => {
15738 log::warn!("timed out waiting for formatting");
15739 None
15740 }
15741 };
15742
15743 buffer
15744 .update(cx, |buffer, cx| {
15745 if let Some(transaction) = transaction {
15746 if !buffer.is_singleton() {
15747 buffer.push_transaction(&transaction.0, cx);
15748 }
15749 }
15750 cx.notify();
15751 })
15752 .ok();
15753
15754 if let Some(transaction_id_now) =
15755 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15756 {
15757 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15758 if has_new_transaction {
15759 _ = editor.update(cx, |editor, _| {
15760 editor
15761 .selection_history
15762 .insert_transaction(transaction_id_now, selections_prev);
15763 });
15764 }
15765 }
15766
15767 Ok(())
15768 })
15769 }
15770
15771 fn organize_imports(
15772 &mut self,
15773 _: &OrganizeImports,
15774 window: &mut Window,
15775 cx: &mut Context<Self>,
15776 ) -> Option<Task<Result<()>>> {
15777 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15778 let project = match &self.project {
15779 Some(project) => project.clone(),
15780 None => return None,
15781 };
15782 Some(self.perform_code_action_kind(
15783 project,
15784 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15785 window,
15786 cx,
15787 ))
15788 }
15789
15790 fn perform_code_action_kind(
15791 &mut self,
15792 project: Entity<Project>,
15793 kind: CodeActionKind,
15794 window: &mut Window,
15795 cx: &mut Context<Self>,
15796 ) -> Task<Result<()>> {
15797 let buffer = self.buffer.clone();
15798 let buffers = buffer.read(cx).all_buffers();
15799 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15800 let apply_action = project.update(cx, |project, cx| {
15801 project.apply_code_action_kind(buffers, kind, true, cx)
15802 });
15803 cx.spawn_in(window, async move |_, cx| {
15804 let transaction = futures::select_biased! {
15805 () = timeout => {
15806 log::warn!("timed out waiting for executing code action");
15807 None
15808 }
15809 transaction = apply_action.log_err().fuse() => transaction,
15810 };
15811 buffer
15812 .update(cx, |buffer, cx| {
15813 // check if we need this
15814 if let Some(transaction) = transaction {
15815 if !buffer.is_singleton() {
15816 buffer.push_transaction(&transaction.0, cx);
15817 }
15818 }
15819 cx.notify();
15820 })
15821 .ok();
15822 Ok(())
15823 })
15824 }
15825
15826 fn restart_language_server(
15827 &mut self,
15828 _: &RestartLanguageServer,
15829 _: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 if let Some(project) = self.project.clone() {
15833 self.buffer.update(cx, |multi_buffer, cx| {
15834 project.update(cx, |project, cx| {
15835 project.restart_language_servers_for_buffers(
15836 multi_buffer.all_buffers().into_iter().collect(),
15837 cx,
15838 );
15839 });
15840 })
15841 }
15842 }
15843
15844 fn stop_language_server(
15845 &mut self,
15846 _: &StopLanguageServer,
15847 _: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) {
15850 if let Some(project) = self.project.clone() {
15851 self.buffer.update(cx, |multi_buffer, cx| {
15852 project.update(cx, |project, cx| {
15853 project.stop_language_servers_for_buffers(
15854 multi_buffer.all_buffers().into_iter().collect(),
15855 cx,
15856 );
15857 cx.emit(project::Event::RefreshInlayHints);
15858 });
15859 });
15860 }
15861 }
15862
15863 fn cancel_language_server_work(
15864 workspace: &mut Workspace,
15865 _: &actions::CancelLanguageServerWork,
15866 _: &mut Window,
15867 cx: &mut Context<Workspace>,
15868 ) {
15869 let project = workspace.project();
15870 let buffers = workspace
15871 .active_item(cx)
15872 .and_then(|item| item.act_as::<Editor>(cx))
15873 .map_or(HashSet::default(), |editor| {
15874 editor.read(cx).buffer.read(cx).all_buffers()
15875 });
15876 project.update(cx, |project, cx| {
15877 project.cancel_language_server_work_for_buffers(buffers, cx);
15878 });
15879 }
15880
15881 fn show_character_palette(
15882 &mut self,
15883 _: &ShowCharacterPalette,
15884 window: &mut Window,
15885 _: &mut Context<Self>,
15886 ) {
15887 window.show_character_palette();
15888 }
15889
15890 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15891 if self.mode.is_minimap() {
15892 return;
15893 }
15894
15895 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15896 let buffer = self.buffer.read(cx).snapshot(cx);
15897 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15898 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15899 let is_valid = buffer
15900 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15901 .any(|entry| {
15902 entry.diagnostic.is_primary
15903 && !entry.range.is_empty()
15904 && entry.range.start == primary_range_start
15905 && entry.diagnostic.message == active_diagnostics.active_message
15906 });
15907
15908 if !is_valid {
15909 self.dismiss_diagnostics(cx);
15910 }
15911 }
15912 }
15913
15914 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15915 match &self.active_diagnostics {
15916 ActiveDiagnostic::Group(group) => Some(group),
15917 _ => None,
15918 }
15919 }
15920
15921 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15922 self.dismiss_diagnostics(cx);
15923 self.active_diagnostics = ActiveDiagnostic::All;
15924 }
15925
15926 fn activate_diagnostics(
15927 &mut self,
15928 buffer_id: BufferId,
15929 diagnostic: DiagnosticEntry<usize>,
15930 window: &mut Window,
15931 cx: &mut Context<Self>,
15932 ) {
15933 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15934 return;
15935 }
15936 self.dismiss_diagnostics(cx);
15937 let snapshot = self.snapshot(window, cx);
15938 let buffer = self.buffer.read(cx).snapshot(cx);
15939 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15940 return;
15941 };
15942
15943 let diagnostic_group = buffer
15944 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15945 .collect::<Vec<_>>();
15946
15947 let blocks =
15948 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15949
15950 let blocks = self.display_map.update(cx, |display_map, cx| {
15951 display_map.insert_blocks(blocks, cx).into_iter().collect()
15952 });
15953 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15954 active_range: buffer.anchor_before(diagnostic.range.start)
15955 ..buffer.anchor_after(diagnostic.range.end),
15956 active_message: diagnostic.diagnostic.message.clone(),
15957 group_id: diagnostic.diagnostic.group_id,
15958 blocks,
15959 });
15960 cx.notify();
15961 }
15962
15963 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15964 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15965 return;
15966 };
15967
15968 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15969 if let ActiveDiagnostic::Group(group) = prev {
15970 self.display_map.update(cx, |display_map, cx| {
15971 display_map.remove_blocks(group.blocks, cx);
15972 });
15973 cx.notify();
15974 }
15975 }
15976
15977 /// Disable inline diagnostics rendering for this editor.
15978 pub fn disable_inline_diagnostics(&mut self) {
15979 self.inline_diagnostics_enabled = false;
15980 self.inline_diagnostics_update = Task::ready(());
15981 self.inline_diagnostics.clear();
15982 }
15983
15984 pub fn diagnostics_enabled(&self) -> bool {
15985 self.mode.is_full()
15986 }
15987
15988 pub fn inline_diagnostics_enabled(&self) -> bool {
15989 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15990 }
15991
15992 pub fn show_inline_diagnostics(&self) -> bool {
15993 self.show_inline_diagnostics
15994 }
15995
15996 pub fn toggle_inline_diagnostics(
15997 &mut self,
15998 _: &ToggleInlineDiagnostics,
15999 window: &mut Window,
16000 cx: &mut Context<Editor>,
16001 ) {
16002 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16003 self.refresh_inline_diagnostics(false, window, cx);
16004 }
16005
16006 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16007 self.diagnostics_max_severity = severity;
16008 self.display_map.update(cx, |display_map, _| {
16009 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16010 });
16011 }
16012
16013 pub fn toggle_diagnostics(
16014 &mut self,
16015 _: &ToggleDiagnostics,
16016 window: &mut Window,
16017 cx: &mut Context<Editor>,
16018 ) {
16019 if !self.diagnostics_enabled() {
16020 return;
16021 }
16022
16023 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16024 EditorSettings::get_global(cx)
16025 .diagnostics_max_severity
16026 .filter(|severity| severity != &DiagnosticSeverity::Off)
16027 .unwrap_or(DiagnosticSeverity::Hint)
16028 } else {
16029 DiagnosticSeverity::Off
16030 };
16031 self.set_max_diagnostics_severity(new_severity, cx);
16032 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16033 self.active_diagnostics = ActiveDiagnostic::None;
16034 self.inline_diagnostics_update = Task::ready(());
16035 self.inline_diagnostics.clear();
16036 } else {
16037 self.refresh_inline_diagnostics(false, window, cx);
16038 }
16039
16040 cx.notify();
16041 }
16042
16043 pub fn toggle_minimap(
16044 &mut self,
16045 _: &ToggleMinimap,
16046 window: &mut Window,
16047 cx: &mut Context<Editor>,
16048 ) {
16049 if self.supports_minimap(cx) {
16050 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16051 }
16052 }
16053
16054 fn refresh_inline_diagnostics(
16055 &mut self,
16056 debounce: bool,
16057 window: &mut Window,
16058 cx: &mut Context<Self>,
16059 ) {
16060 let max_severity = ProjectSettings::get_global(cx)
16061 .diagnostics
16062 .inline
16063 .max_severity
16064 .unwrap_or(self.diagnostics_max_severity);
16065
16066 if !self.inline_diagnostics_enabled()
16067 || !self.show_inline_diagnostics
16068 || max_severity == DiagnosticSeverity::Off
16069 {
16070 self.inline_diagnostics_update = Task::ready(());
16071 self.inline_diagnostics.clear();
16072 return;
16073 }
16074
16075 let debounce_ms = ProjectSettings::get_global(cx)
16076 .diagnostics
16077 .inline
16078 .update_debounce_ms;
16079 let debounce = if debounce && debounce_ms > 0 {
16080 Some(Duration::from_millis(debounce_ms))
16081 } else {
16082 None
16083 };
16084 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16085 if let Some(debounce) = debounce {
16086 cx.background_executor().timer(debounce).await;
16087 }
16088 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16089 editor
16090 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16091 .ok()
16092 }) else {
16093 return;
16094 };
16095
16096 let new_inline_diagnostics = cx
16097 .background_spawn(async move {
16098 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16099 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16100 let message = diagnostic_entry
16101 .diagnostic
16102 .message
16103 .split_once('\n')
16104 .map(|(line, _)| line)
16105 .map(SharedString::new)
16106 .unwrap_or_else(|| {
16107 SharedString::from(diagnostic_entry.diagnostic.message)
16108 });
16109 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16110 let (Ok(i) | Err(i)) = inline_diagnostics
16111 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16112 inline_diagnostics.insert(
16113 i,
16114 (
16115 start_anchor,
16116 InlineDiagnostic {
16117 message,
16118 group_id: diagnostic_entry.diagnostic.group_id,
16119 start: diagnostic_entry.range.start.to_point(&snapshot),
16120 is_primary: diagnostic_entry.diagnostic.is_primary,
16121 severity: diagnostic_entry.diagnostic.severity,
16122 },
16123 ),
16124 );
16125 }
16126 inline_diagnostics
16127 })
16128 .await;
16129
16130 editor
16131 .update(cx, |editor, cx| {
16132 editor.inline_diagnostics = new_inline_diagnostics;
16133 cx.notify();
16134 })
16135 .ok();
16136 });
16137 }
16138
16139 fn pull_diagnostics(
16140 &mut self,
16141 buffer_id: Option<BufferId>,
16142 window: &Window,
16143 cx: &mut Context<Self>,
16144 ) -> Option<()> {
16145 if !self.mode().is_full() {
16146 return None;
16147 }
16148 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16149 .diagnostics
16150 .lsp_pull_diagnostics;
16151 if !pull_diagnostics_settings.enabled {
16152 return None;
16153 }
16154 let project = self.project.as_ref()?.downgrade();
16155 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16156 let mut buffers = self.buffer.read(cx).all_buffers();
16157 if let Some(buffer_id) = buffer_id {
16158 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16159 }
16160
16161 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16162 cx.background_executor().timer(debounce).await;
16163
16164 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16165 buffers
16166 .into_iter()
16167 .flat_map(|buffer| {
16168 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16169 })
16170 .collect::<FuturesUnordered<_>>()
16171 }) else {
16172 return;
16173 };
16174
16175 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16176 match pull_task {
16177 Ok(()) => {
16178 if editor
16179 .update_in(cx, |editor, window, cx| {
16180 editor.update_diagnostics_state(window, cx);
16181 })
16182 .is_err()
16183 {
16184 return;
16185 }
16186 }
16187 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16188 }
16189 }
16190 });
16191
16192 Some(())
16193 }
16194
16195 pub fn set_selections_from_remote(
16196 &mut self,
16197 selections: Vec<Selection<Anchor>>,
16198 pending_selection: Option<Selection<Anchor>>,
16199 window: &mut Window,
16200 cx: &mut Context<Self>,
16201 ) {
16202 let old_cursor_position = self.selections.newest_anchor().head();
16203 self.selections.change_with(cx, |s| {
16204 s.select_anchors(selections);
16205 if let Some(pending_selection) = pending_selection {
16206 s.set_pending(pending_selection, SelectMode::Character);
16207 } else {
16208 s.clear_pending();
16209 }
16210 });
16211 self.selections_did_change(
16212 false,
16213 &old_cursor_position,
16214 SelectionEffects::default(),
16215 window,
16216 cx,
16217 );
16218 }
16219
16220 pub fn transact(
16221 &mut self,
16222 window: &mut Window,
16223 cx: &mut Context<Self>,
16224 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16225 ) -> Option<TransactionId> {
16226 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16227 this.start_transaction_at(Instant::now(), window, cx);
16228 update(this, window, cx);
16229 this.end_transaction_at(Instant::now(), cx)
16230 })
16231 }
16232
16233 pub fn start_transaction_at(
16234 &mut self,
16235 now: Instant,
16236 window: &mut Window,
16237 cx: &mut Context<Self>,
16238 ) {
16239 self.end_selection(window, cx);
16240 if let Some(tx_id) = self
16241 .buffer
16242 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16243 {
16244 self.selection_history
16245 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16246 cx.emit(EditorEvent::TransactionBegun {
16247 transaction_id: tx_id,
16248 })
16249 }
16250 }
16251
16252 pub fn end_transaction_at(
16253 &mut self,
16254 now: Instant,
16255 cx: &mut Context<Self>,
16256 ) -> Option<TransactionId> {
16257 if let Some(transaction_id) = self
16258 .buffer
16259 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16260 {
16261 if let Some((_, end_selections)) =
16262 self.selection_history.transaction_mut(transaction_id)
16263 {
16264 *end_selections = Some(self.selections.disjoint_anchors());
16265 } else {
16266 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16267 }
16268
16269 cx.emit(EditorEvent::Edited { transaction_id });
16270 Some(transaction_id)
16271 } else {
16272 None
16273 }
16274 }
16275
16276 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16277 if self.selection_mark_mode {
16278 self.change_selections(None, window, cx, |s| {
16279 s.move_with(|_, sel| {
16280 sel.collapse_to(sel.head(), SelectionGoal::None);
16281 });
16282 })
16283 }
16284 self.selection_mark_mode = true;
16285 cx.notify();
16286 }
16287
16288 pub fn swap_selection_ends(
16289 &mut self,
16290 _: &actions::SwapSelectionEnds,
16291 window: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) {
16294 self.change_selections(None, window, cx, |s| {
16295 s.move_with(|_, sel| {
16296 if sel.start != sel.end {
16297 sel.reversed = !sel.reversed
16298 }
16299 });
16300 });
16301 self.request_autoscroll(Autoscroll::newest(), cx);
16302 cx.notify();
16303 }
16304
16305 pub fn toggle_fold(
16306 &mut self,
16307 _: &actions::ToggleFold,
16308 window: &mut Window,
16309 cx: &mut Context<Self>,
16310 ) {
16311 if self.is_singleton(cx) {
16312 let selection = self.selections.newest::<Point>(cx);
16313
16314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16315 let range = if selection.is_empty() {
16316 let point = selection.head().to_display_point(&display_map);
16317 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16318 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16319 .to_point(&display_map);
16320 start..end
16321 } else {
16322 selection.range()
16323 };
16324 if display_map.folds_in_range(range).next().is_some() {
16325 self.unfold_lines(&Default::default(), window, cx)
16326 } else {
16327 self.fold(&Default::default(), window, cx)
16328 }
16329 } else {
16330 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16331 let buffer_ids: HashSet<_> = self
16332 .selections
16333 .disjoint_anchor_ranges()
16334 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16335 .collect();
16336
16337 let should_unfold = buffer_ids
16338 .iter()
16339 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16340
16341 for buffer_id in buffer_ids {
16342 if should_unfold {
16343 self.unfold_buffer(buffer_id, cx);
16344 } else {
16345 self.fold_buffer(buffer_id, cx);
16346 }
16347 }
16348 }
16349 }
16350
16351 pub fn toggle_fold_recursive(
16352 &mut self,
16353 _: &actions::ToggleFoldRecursive,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) {
16357 let selection = self.selections.newest::<Point>(cx);
16358
16359 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16360 let range = if selection.is_empty() {
16361 let point = selection.head().to_display_point(&display_map);
16362 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16363 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16364 .to_point(&display_map);
16365 start..end
16366 } else {
16367 selection.range()
16368 };
16369 if display_map.folds_in_range(range).next().is_some() {
16370 self.unfold_recursive(&Default::default(), window, cx)
16371 } else {
16372 self.fold_recursive(&Default::default(), window, cx)
16373 }
16374 }
16375
16376 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16377 if self.is_singleton(cx) {
16378 let mut to_fold = Vec::new();
16379 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16380 let selections = self.selections.all_adjusted(cx);
16381
16382 for selection in selections {
16383 let range = selection.range().sorted();
16384 let buffer_start_row = range.start.row;
16385
16386 if range.start.row != range.end.row {
16387 let mut found = false;
16388 let mut row = range.start.row;
16389 while row <= range.end.row {
16390 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16391 {
16392 found = true;
16393 row = crease.range().end.row + 1;
16394 to_fold.push(crease);
16395 } else {
16396 row += 1
16397 }
16398 }
16399 if found {
16400 continue;
16401 }
16402 }
16403
16404 for row in (0..=range.start.row).rev() {
16405 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16406 if crease.range().end.row >= buffer_start_row {
16407 to_fold.push(crease);
16408 if row <= range.start.row {
16409 break;
16410 }
16411 }
16412 }
16413 }
16414 }
16415
16416 self.fold_creases(to_fold, true, window, cx);
16417 } else {
16418 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16419 let buffer_ids = self
16420 .selections
16421 .disjoint_anchor_ranges()
16422 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16423 .collect::<HashSet<_>>();
16424 for buffer_id in buffer_ids {
16425 self.fold_buffer(buffer_id, cx);
16426 }
16427 }
16428 }
16429
16430 fn fold_at_level(
16431 &mut self,
16432 fold_at: &FoldAtLevel,
16433 window: &mut Window,
16434 cx: &mut Context<Self>,
16435 ) {
16436 if !self.buffer.read(cx).is_singleton() {
16437 return;
16438 }
16439
16440 let fold_at_level = fold_at.0;
16441 let snapshot = self.buffer.read(cx).snapshot(cx);
16442 let mut to_fold = Vec::new();
16443 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16444
16445 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16446 while start_row < end_row {
16447 match self
16448 .snapshot(window, cx)
16449 .crease_for_buffer_row(MultiBufferRow(start_row))
16450 {
16451 Some(crease) => {
16452 let nested_start_row = crease.range().start.row + 1;
16453 let nested_end_row = crease.range().end.row;
16454
16455 if current_level < fold_at_level {
16456 stack.push((nested_start_row, nested_end_row, current_level + 1));
16457 } else if current_level == fold_at_level {
16458 to_fold.push(crease);
16459 }
16460
16461 start_row = nested_end_row + 1;
16462 }
16463 None => start_row += 1,
16464 }
16465 }
16466 }
16467
16468 self.fold_creases(to_fold, true, window, cx);
16469 }
16470
16471 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16472 if self.buffer.read(cx).is_singleton() {
16473 let mut fold_ranges = Vec::new();
16474 let snapshot = self.buffer.read(cx).snapshot(cx);
16475
16476 for row in 0..snapshot.max_row().0 {
16477 if let Some(foldable_range) = self
16478 .snapshot(window, cx)
16479 .crease_for_buffer_row(MultiBufferRow(row))
16480 {
16481 fold_ranges.push(foldable_range);
16482 }
16483 }
16484
16485 self.fold_creases(fold_ranges, true, window, cx);
16486 } else {
16487 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16488 editor
16489 .update_in(cx, |editor, _, cx| {
16490 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16491 editor.fold_buffer(buffer_id, cx);
16492 }
16493 })
16494 .ok();
16495 });
16496 }
16497 }
16498
16499 pub fn fold_function_bodies(
16500 &mut self,
16501 _: &actions::FoldFunctionBodies,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 let snapshot = self.buffer.read(cx).snapshot(cx);
16506
16507 let ranges = snapshot
16508 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16509 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16510 .collect::<Vec<_>>();
16511
16512 let creases = ranges
16513 .into_iter()
16514 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16515 .collect();
16516
16517 self.fold_creases(creases, true, window, cx);
16518 }
16519
16520 pub fn fold_recursive(
16521 &mut self,
16522 _: &actions::FoldRecursive,
16523 window: &mut Window,
16524 cx: &mut Context<Self>,
16525 ) {
16526 let mut to_fold = Vec::new();
16527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16528 let selections = self.selections.all_adjusted(cx);
16529
16530 for selection in selections {
16531 let range = selection.range().sorted();
16532 let buffer_start_row = range.start.row;
16533
16534 if range.start.row != range.end.row {
16535 let mut found = false;
16536 for row in range.start.row..=range.end.row {
16537 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16538 found = true;
16539 to_fold.push(crease);
16540 }
16541 }
16542 if found {
16543 continue;
16544 }
16545 }
16546
16547 for row in (0..=range.start.row).rev() {
16548 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16549 if crease.range().end.row >= buffer_start_row {
16550 to_fold.push(crease);
16551 } else {
16552 break;
16553 }
16554 }
16555 }
16556 }
16557
16558 self.fold_creases(to_fold, true, window, cx);
16559 }
16560
16561 pub fn fold_at(
16562 &mut self,
16563 buffer_row: MultiBufferRow,
16564 window: &mut Window,
16565 cx: &mut Context<Self>,
16566 ) {
16567 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16568
16569 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16570 let autoscroll = self
16571 .selections
16572 .all::<Point>(cx)
16573 .iter()
16574 .any(|selection| crease.range().overlaps(&selection.range()));
16575
16576 self.fold_creases(vec![crease], autoscroll, window, cx);
16577 }
16578 }
16579
16580 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16581 if self.is_singleton(cx) {
16582 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16583 let buffer = &display_map.buffer_snapshot;
16584 let selections = self.selections.all::<Point>(cx);
16585 let ranges = selections
16586 .iter()
16587 .map(|s| {
16588 let range = s.display_range(&display_map).sorted();
16589 let mut start = range.start.to_point(&display_map);
16590 let mut end = range.end.to_point(&display_map);
16591 start.column = 0;
16592 end.column = buffer.line_len(MultiBufferRow(end.row));
16593 start..end
16594 })
16595 .collect::<Vec<_>>();
16596
16597 self.unfold_ranges(&ranges, true, true, cx);
16598 } else {
16599 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16600 let buffer_ids = self
16601 .selections
16602 .disjoint_anchor_ranges()
16603 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16604 .collect::<HashSet<_>>();
16605 for buffer_id in buffer_ids {
16606 self.unfold_buffer(buffer_id, cx);
16607 }
16608 }
16609 }
16610
16611 pub fn unfold_recursive(
16612 &mut self,
16613 _: &UnfoldRecursive,
16614 _window: &mut Window,
16615 cx: &mut Context<Self>,
16616 ) {
16617 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16618 let selections = self.selections.all::<Point>(cx);
16619 let ranges = selections
16620 .iter()
16621 .map(|s| {
16622 let mut range = s.display_range(&display_map).sorted();
16623 *range.start.column_mut() = 0;
16624 *range.end.column_mut() = display_map.line_len(range.end.row());
16625 let start = range.start.to_point(&display_map);
16626 let end = range.end.to_point(&display_map);
16627 start..end
16628 })
16629 .collect::<Vec<_>>();
16630
16631 self.unfold_ranges(&ranges, true, true, cx);
16632 }
16633
16634 pub fn unfold_at(
16635 &mut self,
16636 buffer_row: MultiBufferRow,
16637 _window: &mut Window,
16638 cx: &mut Context<Self>,
16639 ) {
16640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16641
16642 let intersection_range = Point::new(buffer_row.0, 0)
16643 ..Point::new(
16644 buffer_row.0,
16645 display_map.buffer_snapshot.line_len(buffer_row),
16646 );
16647
16648 let autoscroll = self
16649 .selections
16650 .all::<Point>(cx)
16651 .iter()
16652 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16653
16654 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16655 }
16656
16657 pub fn unfold_all(
16658 &mut self,
16659 _: &actions::UnfoldAll,
16660 _window: &mut Window,
16661 cx: &mut Context<Self>,
16662 ) {
16663 if self.buffer.read(cx).is_singleton() {
16664 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16665 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16666 } else {
16667 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16668 editor
16669 .update(cx, |editor, cx| {
16670 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16671 editor.unfold_buffer(buffer_id, cx);
16672 }
16673 })
16674 .ok();
16675 });
16676 }
16677 }
16678
16679 pub fn fold_selected_ranges(
16680 &mut self,
16681 _: &FoldSelectedRanges,
16682 window: &mut Window,
16683 cx: &mut Context<Self>,
16684 ) {
16685 let selections = self.selections.all_adjusted(cx);
16686 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16687 let ranges = selections
16688 .into_iter()
16689 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16690 .collect::<Vec<_>>();
16691 self.fold_creases(ranges, true, window, cx);
16692 }
16693
16694 pub fn fold_ranges<T: ToOffset + Clone>(
16695 &mut self,
16696 ranges: Vec<Range<T>>,
16697 auto_scroll: bool,
16698 window: &mut Window,
16699 cx: &mut Context<Self>,
16700 ) {
16701 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16702 let ranges = ranges
16703 .into_iter()
16704 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16705 .collect::<Vec<_>>();
16706 self.fold_creases(ranges, auto_scroll, window, cx);
16707 }
16708
16709 pub fn fold_creases<T: ToOffset + Clone>(
16710 &mut self,
16711 creases: Vec<Crease<T>>,
16712 auto_scroll: bool,
16713 _window: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) {
16716 if creases.is_empty() {
16717 return;
16718 }
16719
16720 let mut buffers_affected = HashSet::default();
16721 let multi_buffer = self.buffer().read(cx);
16722 for crease in &creases {
16723 if let Some((_, buffer, _)) =
16724 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16725 {
16726 buffers_affected.insert(buffer.read(cx).remote_id());
16727 };
16728 }
16729
16730 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16731
16732 if auto_scroll {
16733 self.request_autoscroll(Autoscroll::fit(), cx);
16734 }
16735
16736 cx.notify();
16737
16738 self.scrollbar_marker_state.dirty = true;
16739 self.folds_did_change(cx);
16740 }
16741
16742 /// Removes any folds whose ranges intersect any of the given ranges.
16743 pub fn unfold_ranges<T: ToOffset + Clone>(
16744 &mut self,
16745 ranges: &[Range<T>],
16746 inclusive: bool,
16747 auto_scroll: bool,
16748 cx: &mut Context<Self>,
16749 ) {
16750 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16751 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16752 });
16753 self.folds_did_change(cx);
16754 }
16755
16756 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16757 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16758 return;
16759 }
16760 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16761 self.display_map.update(cx, |display_map, cx| {
16762 display_map.fold_buffers([buffer_id], cx)
16763 });
16764 cx.emit(EditorEvent::BufferFoldToggled {
16765 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16766 folded: true,
16767 });
16768 cx.notify();
16769 }
16770
16771 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16772 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16773 return;
16774 }
16775 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16776 self.display_map.update(cx, |display_map, cx| {
16777 display_map.unfold_buffers([buffer_id], cx);
16778 });
16779 cx.emit(EditorEvent::BufferFoldToggled {
16780 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16781 folded: false,
16782 });
16783 cx.notify();
16784 }
16785
16786 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16787 self.display_map.read(cx).is_buffer_folded(buffer)
16788 }
16789
16790 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16791 self.display_map.read(cx).folded_buffers()
16792 }
16793
16794 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16795 self.display_map.update(cx, |display_map, cx| {
16796 display_map.disable_header_for_buffer(buffer_id, cx);
16797 });
16798 cx.notify();
16799 }
16800
16801 /// Removes any folds with the given ranges.
16802 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16803 &mut self,
16804 ranges: &[Range<T>],
16805 type_id: TypeId,
16806 auto_scroll: bool,
16807 cx: &mut Context<Self>,
16808 ) {
16809 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16810 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16811 });
16812 self.folds_did_change(cx);
16813 }
16814
16815 fn remove_folds_with<T: ToOffset + Clone>(
16816 &mut self,
16817 ranges: &[Range<T>],
16818 auto_scroll: bool,
16819 cx: &mut Context<Self>,
16820 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16821 ) {
16822 if ranges.is_empty() {
16823 return;
16824 }
16825
16826 let mut buffers_affected = HashSet::default();
16827 let multi_buffer = self.buffer().read(cx);
16828 for range in ranges {
16829 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16830 buffers_affected.insert(buffer.read(cx).remote_id());
16831 };
16832 }
16833
16834 self.display_map.update(cx, update);
16835
16836 if auto_scroll {
16837 self.request_autoscroll(Autoscroll::fit(), cx);
16838 }
16839
16840 cx.notify();
16841 self.scrollbar_marker_state.dirty = true;
16842 self.active_indent_guides_state.dirty = true;
16843 }
16844
16845 pub fn update_fold_widths(
16846 &mut self,
16847 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16848 cx: &mut Context<Self>,
16849 ) -> bool {
16850 self.display_map
16851 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16852 }
16853
16854 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16855 self.display_map.read(cx).fold_placeholder.clone()
16856 }
16857
16858 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16859 self.buffer.update(cx, |buffer, cx| {
16860 buffer.set_all_diff_hunks_expanded(cx);
16861 });
16862 }
16863
16864 pub fn expand_all_diff_hunks(
16865 &mut self,
16866 _: &ExpandAllDiffHunks,
16867 _window: &mut Window,
16868 cx: &mut Context<Self>,
16869 ) {
16870 self.buffer.update(cx, |buffer, cx| {
16871 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16872 });
16873 }
16874
16875 pub fn toggle_selected_diff_hunks(
16876 &mut self,
16877 _: &ToggleSelectedDiffHunks,
16878 _window: &mut Window,
16879 cx: &mut Context<Self>,
16880 ) {
16881 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16882 self.toggle_diff_hunks_in_ranges(ranges, cx);
16883 }
16884
16885 pub fn diff_hunks_in_ranges<'a>(
16886 &'a self,
16887 ranges: &'a [Range<Anchor>],
16888 buffer: &'a MultiBufferSnapshot,
16889 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16890 ranges.iter().flat_map(move |range| {
16891 let end_excerpt_id = range.end.excerpt_id;
16892 let range = range.to_point(buffer);
16893 let mut peek_end = range.end;
16894 if range.end.row < buffer.max_row().0 {
16895 peek_end = Point::new(range.end.row + 1, 0);
16896 }
16897 buffer
16898 .diff_hunks_in_range(range.start..peek_end)
16899 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16900 })
16901 }
16902
16903 pub fn has_stageable_diff_hunks_in_ranges(
16904 &self,
16905 ranges: &[Range<Anchor>],
16906 snapshot: &MultiBufferSnapshot,
16907 ) -> bool {
16908 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16909 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16910 }
16911
16912 pub fn toggle_staged_selected_diff_hunks(
16913 &mut self,
16914 _: &::git::ToggleStaged,
16915 _: &mut Window,
16916 cx: &mut Context<Self>,
16917 ) {
16918 let snapshot = self.buffer.read(cx).snapshot(cx);
16919 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16920 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16921 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16922 }
16923
16924 pub fn set_render_diff_hunk_controls(
16925 &mut self,
16926 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16927 cx: &mut Context<Self>,
16928 ) {
16929 self.render_diff_hunk_controls = render_diff_hunk_controls;
16930 cx.notify();
16931 }
16932
16933 pub fn stage_and_next(
16934 &mut self,
16935 _: &::git::StageAndNext,
16936 window: &mut Window,
16937 cx: &mut Context<Self>,
16938 ) {
16939 self.do_stage_or_unstage_and_next(true, window, cx);
16940 }
16941
16942 pub fn unstage_and_next(
16943 &mut self,
16944 _: &::git::UnstageAndNext,
16945 window: &mut Window,
16946 cx: &mut Context<Self>,
16947 ) {
16948 self.do_stage_or_unstage_and_next(false, window, cx);
16949 }
16950
16951 pub fn stage_or_unstage_diff_hunks(
16952 &mut self,
16953 stage: bool,
16954 ranges: Vec<Range<Anchor>>,
16955 cx: &mut Context<Self>,
16956 ) {
16957 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16958 cx.spawn(async move |this, cx| {
16959 task.await?;
16960 this.update(cx, |this, cx| {
16961 let snapshot = this.buffer.read(cx).snapshot(cx);
16962 let chunk_by = this
16963 .diff_hunks_in_ranges(&ranges, &snapshot)
16964 .chunk_by(|hunk| hunk.buffer_id);
16965 for (buffer_id, hunks) in &chunk_by {
16966 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16967 }
16968 })
16969 })
16970 .detach_and_log_err(cx);
16971 }
16972
16973 fn save_buffers_for_ranges_if_needed(
16974 &mut self,
16975 ranges: &[Range<Anchor>],
16976 cx: &mut Context<Editor>,
16977 ) -> Task<Result<()>> {
16978 let multibuffer = self.buffer.read(cx);
16979 let snapshot = multibuffer.read(cx);
16980 let buffer_ids: HashSet<_> = ranges
16981 .iter()
16982 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16983 .collect();
16984 drop(snapshot);
16985
16986 let mut buffers = HashSet::default();
16987 for buffer_id in buffer_ids {
16988 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16989 let buffer = buffer_entity.read(cx);
16990 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16991 {
16992 buffers.insert(buffer_entity);
16993 }
16994 }
16995 }
16996
16997 if let Some(project) = &self.project {
16998 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16999 } else {
17000 Task::ready(Ok(()))
17001 }
17002 }
17003
17004 fn do_stage_or_unstage_and_next(
17005 &mut self,
17006 stage: bool,
17007 window: &mut Window,
17008 cx: &mut Context<Self>,
17009 ) {
17010 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17011
17012 if ranges.iter().any(|range| range.start != range.end) {
17013 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17014 return;
17015 }
17016
17017 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17018 let snapshot = self.snapshot(window, cx);
17019 let position = self.selections.newest::<Point>(cx).head();
17020 let mut row = snapshot
17021 .buffer_snapshot
17022 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17023 .find(|hunk| hunk.row_range.start.0 > position.row)
17024 .map(|hunk| hunk.row_range.start);
17025
17026 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17027 // Outside of the project diff editor, wrap around to the beginning.
17028 if !all_diff_hunks_expanded {
17029 row = row.or_else(|| {
17030 snapshot
17031 .buffer_snapshot
17032 .diff_hunks_in_range(Point::zero()..position)
17033 .find(|hunk| hunk.row_range.end.0 < position.row)
17034 .map(|hunk| hunk.row_range.start)
17035 });
17036 }
17037
17038 if let Some(row) = row {
17039 let destination = Point::new(row.0, 0);
17040 let autoscroll = Autoscroll::center();
17041
17042 self.unfold_ranges(&[destination..destination], false, false, cx);
17043 self.change_selections(Some(autoscroll), window, cx, |s| {
17044 s.select_ranges([destination..destination]);
17045 });
17046 }
17047 }
17048
17049 fn do_stage_or_unstage(
17050 &self,
17051 stage: bool,
17052 buffer_id: BufferId,
17053 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17054 cx: &mut App,
17055 ) -> Option<()> {
17056 let project = self.project.as_ref()?;
17057 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17058 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17059 let buffer_snapshot = buffer.read(cx).snapshot();
17060 let file_exists = buffer_snapshot
17061 .file()
17062 .is_some_and(|file| file.disk_state().exists());
17063 diff.update(cx, |diff, cx| {
17064 diff.stage_or_unstage_hunks(
17065 stage,
17066 &hunks
17067 .map(|hunk| buffer_diff::DiffHunk {
17068 buffer_range: hunk.buffer_range,
17069 diff_base_byte_range: hunk.diff_base_byte_range,
17070 secondary_status: hunk.secondary_status,
17071 range: Point::zero()..Point::zero(), // unused
17072 })
17073 .collect::<Vec<_>>(),
17074 &buffer_snapshot,
17075 file_exists,
17076 cx,
17077 )
17078 });
17079 None
17080 }
17081
17082 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17083 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17084 self.buffer
17085 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17086 }
17087
17088 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17089 self.buffer.update(cx, |buffer, cx| {
17090 let ranges = vec![Anchor::min()..Anchor::max()];
17091 if !buffer.all_diff_hunks_expanded()
17092 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17093 {
17094 buffer.collapse_diff_hunks(ranges, cx);
17095 true
17096 } else {
17097 false
17098 }
17099 })
17100 }
17101
17102 fn toggle_diff_hunks_in_ranges(
17103 &mut self,
17104 ranges: Vec<Range<Anchor>>,
17105 cx: &mut Context<Editor>,
17106 ) {
17107 self.buffer.update(cx, |buffer, cx| {
17108 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17109 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17110 })
17111 }
17112
17113 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17114 self.buffer.update(cx, |buffer, cx| {
17115 let snapshot = buffer.snapshot(cx);
17116 let excerpt_id = range.end.excerpt_id;
17117 let point_range = range.to_point(&snapshot);
17118 let expand = !buffer.single_hunk_is_expanded(range, cx);
17119 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17120 })
17121 }
17122
17123 pub(crate) fn apply_all_diff_hunks(
17124 &mut self,
17125 _: &ApplyAllDiffHunks,
17126 window: &mut Window,
17127 cx: &mut Context<Self>,
17128 ) {
17129 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17130
17131 let buffers = self.buffer.read(cx).all_buffers();
17132 for branch_buffer in buffers {
17133 branch_buffer.update(cx, |branch_buffer, cx| {
17134 branch_buffer.merge_into_base(Vec::new(), cx);
17135 });
17136 }
17137
17138 if let Some(project) = self.project.clone() {
17139 self.save(
17140 SaveOptions {
17141 format: true,
17142 autosave: false,
17143 },
17144 project,
17145 window,
17146 cx,
17147 )
17148 .detach_and_log_err(cx);
17149 }
17150 }
17151
17152 pub(crate) fn apply_selected_diff_hunks(
17153 &mut self,
17154 _: &ApplyDiffHunk,
17155 window: &mut Window,
17156 cx: &mut Context<Self>,
17157 ) {
17158 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17159 let snapshot = self.snapshot(window, cx);
17160 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17161 let mut ranges_by_buffer = HashMap::default();
17162 self.transact(window, cx, |editor, _window, cx| {
17163 for hunk in hunks {
17164 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17165 ranges_by_buffer
17166 .entry(buffer.clone())
17167 .or_insert_with(Vec::new)
17168 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17169 }
17170 }
17171
17172 for (buffer, ranges) in ranges_by_buffer {
17173 buffer.update(cx, |buffer, cx| {
17174 buffer.merge_into_base(ranges, cx);
17175 });
17176 }
17177 });
17178
17179 if let Some(project) = self.project.clone() {
17180 self.save(
17181 SaveOptions {
17182 format: true,
17183 autosave: false,
17184 },
17185 project,
17186 window,
17187 cx,
17188 )
17189 .detach_and_log_err(cx);
17190 }
17191 }
17192
17193 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17194 if hovered != self.gutter_hovered {
17195 self.gutter_hovered = hovered;
17196 cx.notify();
17197 }
17198 }
17199
17200 pub fn insert_blocks(
17201 &mut self,
17202 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17203 autoscroll: Option<Autoscroll>,
17204 cx: &mut Context<Self>,
17205 ) -> Vec<CustomBlockId> {
17206 let blocks = self
17207 .display_map
17208 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17209 if let Some(autoscroll) = autoscroll {
17210 self.request_autoscroll(autoscroll, cx);
17211 }
17212 cx.notify();
17213 blocks
17214 }
17215
17216 pub fn resize_blocks(
17217 &mut self,
17218 heights: HashMap<CustomBlockId, u32>,
17219 autoscroll: Option<Autoscroll>,
17220 cx: &mut Context<Self>,
17221 ) {
17222 self.display_map
17223 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17224 if let Some(autoscroll) = autoscroll {
17225 self.request_autoscroll(autoscroll, cx);
17226 }
17227 cx.notify();
17228 }
17229
17230 pub fn replace_blocks(
17231 &mut self,
17232 renderers: HashMap<CustomBlockId, RenderBlock>,
17233 autoscroll: Option<Autoscroll>,
17234 cx: &mut Context<Self>,
17235 ) {
17236 self.display_map
17237 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17238 if let Some(autoscroll) = autoscroll {
17239 self.request_autoscroll(autoscroll, cx);
17240 }
17241 cx.notify();
17242 }
17243
17244 pub fn remove_blocks(
17245 &mut self,
17246 block_ids: HashSet<CustomBlockId>,
17247 autoscroll: Option<Autoscroll>,
17248 cx: &mut Context<Self>,
17249 ) {
17250 self.display_map.update(cx, |display_map, cx| {
17251 display_map.remove_blocks(block_ids, cx)
17252 });
17253 if let Some(autoscroll) = autoscroll {
17254 self.request_autoscroll(autoscroll, cx);
17255 }
17256 cx.notify();
17257 }
17258
17259 pub fn row_for_block(
17260 &self,
17261 block_id: CustomBlockId,
17262 cx: &mut Context<Self>,
17263 ) -> Option<DisplayRow> {
17264 self.display_map
17265 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17266 }
17267
17268 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17269 self.focused_block = Some(focused_block);
17270 }
17271
17272 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17273 self.focused_block.take()
17274 }
17275
17276 pub fn insert_creases(
17277 &mut self,
17278 creases: impl IntoIterator<Item = Crease<Anchor>>,
17279 cx: &mut Context<Self>,
17280 ) -> Vec<CreaseId> {
17281 self.display_map
17282 .update(cx, |map, cx| map.insert_creases(creases, cx))
17283 }
17284
17285 pub fn remove_creases(
17286 &mut self,
17287 ids: impl IntoIterator<Item = CreaseId>,
17288 cx: &mut Context<Self>,
17289 ) -> Vec<(CreaseId, Range<Anchor>)> {
17290 self.display_map
17291 .update(cx, |map, cx| map.remove_creases(ids, cx))
17292 }
17293
17294 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17295 self.display_map
17296 .update(cx, |map, cx| map.snapshot(cx))
17297 .longest_row()
17298 }
17299
17300 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17301 self.display_map
17302 .update(cx, |map, cx| map.snapshot(cx))
17303 .max_point()
17304 }
17305
17306 pub fn text(&self, cx: &App) -> String {
17307 self.buffer.read(cx).read(cx).text()
17308 }
17309
17310 pub fn is_empty(&self, cx: &App) -> bool {
17311 self.buffer.read(cx).read(cx).is_empty()
17312 }
17313
17314 pub fn text_option(&self, cx: &App) -> Option<String> {
17315 let text = self.text(cx);
17316 let text = text.trim();
17317
17318 if text.is_empty() {
17319 return None;
17320 }
17321
17322 Some(text.to_string())
17323 }
17324
17325 pub fn set_text(
17326 &mut self,
17327 text: impl Into<Arc<str>>,
17328 window: &mut Window,
17329 cx: &mut Context<Self>,
17330 ) {
17331 self.transact(window, cx, |this, _, cx| {
17332 this.buffer
17333 .read(cx)
17334 .as_singleton()
17335 .expect("you can only call set_text on editors for singleton buffers")
17336 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17337 });
17338 }
17339
17340 pub fn display_text(&self, cx: &mut App) -> String {
17341 self.display_map
17342 .update(cx, |map, cx| map.snapshot(cx))
17343 .text()
17344 }
17345
17346 fn create_minimap(
17347 &self,
17348 minimap_settings: MinimapSettings,
17349 window: &mut Window,
17350 cx: &mut Context<Self>,
17351 ) -> Option<Entity<Self>> {
17352 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17353 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17354 }
17355
17356 fn initialize_new_minimap(
17357 &self,
17358 minimap_settings: MinimapSettings,
17359 window: &mut Window,
17360 cx: &mut Context<Self>,
17361 ) -> Entity<Self> {
17362 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17363
17364 let mut minimap = Editor::new_internal(
17365 EditorMode::Minimap {
17366 parent: cx.weak_entity(),
17367 },
17368 self.buffer.clone(),
17369 self.project.clone(),
17370 Some(self.display_map.clone()),
17371 window,
17372 cx,
17373 );
17374 minimap.scroll_manager.clone_state(&self.scroll_manager);
17375 minimap.set_text_style_refinement(TextStyleRefinement {
17376 font_size: Some(MINIMAP_FONT_SIZE),
17377 font_weight: Some(MINIMAP_FONT_WEIGHT),
17378 ..Default::default()
17379 });
17380 minimap.update_minimap_configuration(minimap_settings, cx);
17381 cx.new(|_| minimap)
17382 }
17383
17384 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17385 let current_line_highlight = minimap_settings
17386 .current_line_highlight
17387 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17388 self.set_current_line_highlight(Some(current_line_highlight));
17389 }
17390
17391 pub fn minimap(&self) -> Option<&Entity<Self>> {
17392 self.minimap
17393 .as_ref()
17394 .filter(|_| self.minimap_visibility.visible())
17395 }
17396
17397 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17398 let mut wrap_guides = smallvec![];
17399
17400 if self.show_wrap_guides == Some(false) {
17401 return wrap_guides;
17402 }
17403
17404 let settings = self.buffer.read(cx).language_settings(cx);
17405 if settings.show_wrap_guides {
17406 match self.soft_wrap_mode(cx) {
17407 SoftWrap::Column(soft_wrap) => {
17408 wrap_guides.push((soft_wrap as usize, true));
17409 }
17410 SoftWrap::Bounded(soft_wrap) => {
17411 wrap_guides.push((soft_wrap as usize, true));
17412 }
17413 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17414 }
17415 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17416 }
17417
17418 wrap_guides
17419 }
17420
17421 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17422 let settings = self.buffer.read(cx).language_settings(cx);
17423 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17424 match mode {
17425 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17426 SoftWrap::None
17427 }
17428 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17429 language_settings::SoftWrap::PreferredLineLength => {
17430 SoftWrap::Column(settings.preferred_line_length)
17431 }
17432 language_settings::SoftWrap::Bounded => {
17433 SoftWrap::Bounded(settings.preferred_line_length)
17434 }
17435 }
17436 }
17437
17438 pub fn set_soft_wrap_mode(
17439 &mut self,
17440 mode: language_settings::SoftWrap,
17441
17442 cx: &mut Context<Self>,
17443 ) {
17444 self.soft_wrap_mode_override = Some(mode);
17445 cx.notify();
17446 }
17447
17448 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17449 self.hard_wrap = hard_wrap;
17450 cx.notify();
17451 }
17452
17453 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17454 self.text_style_refinement = Some(style);
17455 }
17456
17457 /// called by the Element so we know what style we were most recently rendered with.
17458 pub(crate) fn set_style(
17459 &mut self,
17460 style: EditorStyle,
17461 window: &mut Window,
17462 cx: &mut Context<Self>,
17463 ) {
17464 // We intentionally do not inform the display map about the minimap style
17465 // so that wrapping is not recalculated and stays consistent for the editor
17466 // and its linked minimap.
17467 if !self.mode.is_minimap() {
17468 let rem_size = window.rem_size();
17469 self.display_map.update(cx, |map, cx| {
17470 map.set_font(
17471 style.text.font(),
17472 style.text.font_size.to_pixels(rem_size),
17473 cx,
17474 )
17475 });
17476 }
17477 self.style = Some(style);
17478 }
17479
17480 pub fn style(&self) -> Option<&EditorStyle> {
17481 self.style.as_ref()
17482 }
17483
17484 // Called by the element. This method is not designed to be called outside of the editor
17485 // element's layout code because it does not notify when rewrapping is computed synchronously.
17486 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17487 self.display_map
17488 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17489 }
17490
17491 pub fn set_soft_wrap(&mut self) {
17492 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17493 }
17494
17495 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17496 if self.soft_wrap_mode_override.is_some() {
17497 self.soft_wrap_mode_override.take();
17498 } else {
17499 let soft_wrap = match self.soft_wrap_mode(cx) {
17500 SoftWrap::GitDiff => return,
17501 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17502 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17503 language_settings::SoftWrap::None
17504 }
17505 };
17506 self.soft_wrap_mode_override = Some(soft_wrap);
17507 }
17508 cx.notify();
17509 }
17510
17511 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17512 let Some(workspace) = self.workspace() else {
17513 return;
17514 };
17515 let fs = workspace.read(cx).app_state().fs.clone();
17516 let current_show = TabBarSettings::get_global(cx).show;
17517 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17518 setting.show = Some(!current_show);
17519 });
17520 }
17521
17522 pub fn toggle_indent_guides(
17523 &mut self,
17524 _: &ToggleIndentGuides,
17525 _: &mut Window,
17526 cx: &mut Context<Self>,
17527 ) {
17528 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17529 self.buffer
17530 .read(cx)
17531 .language_settings(cx)
17532 .indent_guides
17533 .enabled
17534 });
17535 self.show_indent_guides = Some(!currently_enabled);
17536 cx.notify();
17537 }
17538
17539 fn should_show_indent_guides(&self) -> Option<bool> {
17540 self.show_indent_guides
17541 }
17542
17543 pub fn toggle_line_numbers(
17544 &mut self,
17545 _: &ToggleLineNumbers,
17546 _: &mut Window,
17547 cx: &mut Context<Self>,
17548 ) {
17549 let mut editor_settings = EditorSettings::get_global(cx).clone();
17550 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17551 EditorSettings::override_global(editor_settings, cx);
17552 }
17553
17554 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17555 if let Some(show_line_numbers) = self.show_line_numbers {
17556 return show_line_numbers;
17557 }
17558 EditorSettings::get_global(cx).gutter.line_numbers
17559 }
17560
17561 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17562 self.use_relative_line_numbers
17563 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17564 }
17565
17566 pub fn toggle_relative_line_numbers(
17567 &mut self,
17568 _: &ToggleRelativeLineNumbers,
17569 _: &mut Window,
17570 cx: &mut Context<Self>,
17571 ) {
17572 let is_relative = self.should_use_relative_line_numbers(cx);
17573 self.set_relative_line_number(Some(!is_relative), cx)
17574 }
17575
17576 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17577 self.use_relative_line_numbers = is_relative;
17578 cx.notify();
17579 }
17580
17581 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17582 self.show_gutter = show_gutter;
17583 cx.notify();
17584 }
17585
17586 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17587 self.show_scrollbars = ScrollbarAxes {
17588 horizontal: show,
17589 vertical: show,
17590 };
17591 cx.notify();
17592 }
17593
17594 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17595 self.show_scrollbars.vertical = show;
17596 cx.notify();
17597 }
17598
17599 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17600 self.show_scrollbars.horizontal = show;
17601 cx.notify();
17602 }
17603
17604 pub fn set_minimap_visibility(
17605 &mut self,
17606 minimap_visibility: MinimapVisibility,
17607 window: &mut Window,
17608 cx: &mut Context<Self>,
17609 ) {
17610 if self.minimap_visibility != minimap_visibility {
17611 if minimap_visibility.visible() && self.minimap.is_none() {
17612 let minimap_settings = EditorSettings::get_global(cx).minimap;
17613 self.minimap =
17614 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17615 }
17616 self.minimap_visibility = minimap_visibility;
17617 cx.notify();
17618 }
17619 }
17620
17621 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17622 self.set_show_scrollbars(false, cx);
17623 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17624 }
17625
17626 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17627 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17628 }
17629
17630 /// Normally the text in full mode and auto height editors is padded on the
17631 /// left side by roughly half a character width for improved hit testing.
17632 ///
17633 /// Use this method to disable this for cases where this is not wanted (e.g.
17634 /// if you want to align the editor text with some other text above or below)
17635 /// or if you want to add this padding to single-line editors.
17636 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17637 self.offset_content = offset_content;
17638 cx.notify();
17639 }
17640
17641 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17642 self.show_line_numbers = Some(show_line_numbers);
17643 cx.notify();
17644 }
17645
17646 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17647 self.disable_expand_excerpt_buttons = true;
17648 cx.notify();
17649 }
17650
17651 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17652 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17653 cx.notify();
17654 }
17655
17656 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17657 self.show_code_actions = Some(show_code_actions);
17658 cx.notify();
17659 }
17660
17661 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17662 self.show_runnables = Some(show_runnables);
17663 cx.notify();
17664 }
17665
17666 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17667 self.show_breakpoints = Some(show_breakpoints);
17668 cx.notify();
17669 }
17670
17671 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17672 if self.display_map.read(cx).masked != masked {
17673 self.display_map.update(cx, |map, _| map.masked = masked);
17674 }
17675 cx.notify()
17676 }
17677
17678 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17679 self.show_wrap_guides = Some(show_wrap_guides);
17680 cx.notify();
17681 }
17682
17683 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17684 self.show_indent_guides = Some(show_indent_guides);
17685 cx.notify();
17686 }
17687
17688 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17689 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17690 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17691 if let Some(dir) = file.abs_path(cx).parent() {
17692 return Some(dir.to_owned());
17693 }
17694 }
17695
17696 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17697 return Some(project_path.path.to_path_buf());
17698 }
17699 }
17700
17701 None
17702 }
17703
17704 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17705 self.active_excerpt(cx)?
17706 .1
17707 .read(cx)
17708 .file()
17709 .and_then(|f| f.as_local())
17710 }
17711
17712 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17713 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17714 let buffer = buffer.read(cx);
17715 if let Some(project_path) = buffer.project_path(cx) {
17716 let project = self.project.as_ref()?.read(cx);
17717 project.absolute_path(&project_path, cx)
17718 } else {
17719 buffer
17720 .file()
17721 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17722 }
17723 })
17724 }
17725
17726 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17727 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17728 let project_path = buffer.read(cx).project_path(cx)?;
17729 let project = self.project.as_ref()?.read(cx);
17730 let entry = project.entry_for_path(&project_path, cx)?;
17731 let path = entry.path.to_path_buf();
17732 Some(path)
17733 })
17734 }
17735
17736 pub fn reveal_in_finder(
17737 &mut self,
17738 _: &RevealInFileManager,
17739 _window: &mut Window,
17740 cx: &mut Context<Self>,
17741 ) {
17742 if let Some(target) = self.target_file(cx) {
17743 cx.reveal_path(&target.abs_path(cx));
17744 }
17745 }
17746
17747 pub fn copy_path(
17748 &mut self,
17749 _: &zed_actions::workspace::CopyPath,
17750 _window: &mut Window,
17751 cx: &mut Context<Self>,
17752 ) {
17753 if let Some(path) = self.target_file_abs_path(cx) {
17754 if let Some(path) = path.to_str() {
17755 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17756 }
17757 }
17758 }
17759
17760 pub fn copy_relative_path(
17761 &mut self,
17762 _: &zed_actions::workspace::CopyRelativePath,
17763 _window: &mut Window,
17764 cx: &mut Context<Self>,
17765 ) {
17766 if let Some(path) = self.target_file_path(cx) {
17767 if let Some(path) = path.to_str() {
17768 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17769 }
17770 }
17771 }
17772
17773 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17774 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17775 buffer.read(cx).project_path(cx)
17776 } else {
17777 None
17778 }
17779 }
17780
17781 // Returns true if the editor handled a go-to-line request
17782 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17783 maybe!({
17784 let breakpoint_store = self.breakpoint_store.as_ref()?;
17785
17786 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17787 else {
17788 self.clear_row_highlights::<ActiveDebugLine>();
17789 return None;
17790 };
17791
17792 let position = active_stack_frame.position;
17793 let buffer_id = position.buffer_id?;
17794 let snapshot = self
17795 .project
17796 .as_ref()?
17797 .read(cx)
17798 .buffer_for_id(buffer_id, cx)?
17799 .read(cx)
17800 .snapshot();
17801
17802 let mut handled = false;
17803 for (id, ExcerptRange { context, .. }) in
17804 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17805 {
17806 if context.start.cmp(&position, &snapshot).is_ge()
17807 || context.end.cmp(&position, &snapshot).is_lt()
17808 {
17809 continue;
17810 }
17811 let snapshot = self.buffer.read(cx).snapshot(cx);
17812 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17813
17814 handled = true;
17815 self.clear_row_highlights::<ActiveDebugLine>();
17816
17817 self.go_to_line::<ActiveDebugLine>(
17818 multibuffer_anchor,
17819 Some(cx.theme().colors().editor_debugger_active_line_background),
17820 window,
17821 cx,
17822 );
17823
17824 cx.notify();
17825 }
17826
17827 handled.then_some(())
17828 })
17829 .is_some()
17830 }
17831
17832 pub fn copy_file_name_without_extension(
17833 &mut self,
17834 _: &CopyFileNameWithoutExtension,
17835 _: &mut Window,
17836 cx: &mut Context<Self>,
17837 ) {
17838 if let Some(file) = self.target_file(cx) {
17839 if let Some(file_stem) = file.path().file_stem() {
17840 if let Some(name) = file_stem.to_str() {
17841 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17842 }
17843 }
17844 }
17845 }
17846
17847 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17848 if let Some(file) = self.target_file(cx) {
17849 if let Some(file_name) = file.path().file_name() {
17850 if let Some(name) = file_name.to_str() {
17851 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17852 }
17853 }
17854 }
17855 }
17856
17857 pub fn toggle_git_blame(
17858 &mut self,
17859 _: &::git::Blame,
17860 window: &mut Window,
17861 cx: &mut Context<Self>,
17862 ) {
17863 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17864
17865 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17866 self.start_git_blame(true, window, cx);
17867 }
17868
17869 cx.notify();
17870 }
17871
17872 pub fn toggle_git_blame_inline(
17873 &mut self,
17874 _: &ToggleGitBlameInline,
17875 window: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 self.toggle_git_blame_inline_internal(true, window, cx);
17879 cx.notify();
17880 }
17881
17882 pub fn open_git_blame_commit(
17883 &mut self,
17884 _: &OpenGitBlameCommit,
17885 window: &mut Window,
17886 cx: &mut Context<Self>,
17887 ) {
17888 self.open_git_blame_commit_internal(window, cx);
17889 }
17890
17891 fn open_git_blame_commit_internal(
17892 &mut self,
17893 window: &mut Window,
17894 cx: &mut Context<Self>,
17895 ) -> Option<()> {
17896 let blame = self.blame.as_ref()?;
17897 let snapshot = self.snapshot(window, cx);
17898 let cursor = self.selections.newest::<Point>(cx).head();
17899 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17900 let blame_entry = blame
17901 .update(cx, |blame, cx| {
17902 blame
17903 .blame_for_rows(
17904 &[RowInfo {
17905 buffer_id: Some(buffer.remote_id()),
17906 buffer_row: Some(point.row),
17907 ..Default::default()
17908 }],
17909 cx,
17910 )
17911 .next()
17912 })
17913 .flatten()?;
17914 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17915 let repo = blame.read(cx).repository(cx)?;
17916 let workspace = self.workspace()?.downgrade();
17917 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17918 None
17919 }
17920
17921 pub fn git_blame_inline_enabled(&self) -> bool {
17922 self.git_blame_inline_enabled
17923 }
17924
17925 pub fn toggle_selection_menu(
17926 &mut self,
17927 _: &ToggleSelectionMenu,
17928 _: &mut Window,
17929 cx: &mut Context<Self>,
17930 ) {
17931 self.show_selection_menu = self
17932 .show_selection_menu
17933 .map(|show_selections_menu| !show_selections_menu)
17934 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17935
17936 cx.notify();
17937 }
17938
17939 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17940 self.show_selection_menu
17941 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17942 }
17943
17944 fn start_git_blame(
17945 &mut self,
17946 user_triggered: bool,
17947 window: &mut Window,
17948 cx: &mut Context<Self>,
17949 ) {
17950 if let Some(project) = self.project.as_ref() {
17951 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17952 return;
17953 };
17954
17955 if buffer.read(cx).file().is_none() {
17956 return;
17957 }
17958
17959 let focused = self.focus_handle(cx).contains_focused(window, cx);
17960
17961 let project = project.clone();
17962 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17963 self.blame_subscription =
17964 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17965 self.blame = Some(blame);
17966 }
17967 }
17968
17969 fn toggle_git_blame_inline_internal(
17970 &mut self,
17971 user_triggered: bool,
17972 window: &mut Window,
17973 cx: &mut Context<Self>,
17974 ) {
17975 if self.git_blame_inline_enabled {
17976 self.git_blame_inline_enabled = false;
17977 self.show_git_blame_inline = false;
17978 self.show_git_blame_inline_delay_task.take();
17979 } else {
17980 self.git_blame_inline_enabled = true;
17981 self.start_git_blame_inline(user_triggered, window, cx);
17982 }
17983
17984 cx.notify();
17985 }
17986
17987 fn start_git_blame_inline(
17988 &mut self,
17989 user_triggered: bool,
17990 window: &mut Window,
17991 cx: &mut Context<Self>,
17992 ) {
17993 self.start_git_blame(user_triggered, window, cx);
17994
17995 if ProjectSettings::get_global(cx)
17996 .git
17997 .inline_blame_delay()
17998 .is_some()
17999 {
18000 self.start_inline_blame_timer(window, cx);
18001 } else {
18002 self.show_git_blame_inline = true
18003 }
18004 }
18005
18006 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18007 self.blame.as_ref()
18008 }
18009
18010 pub fn show_git_blame_gutter(&self) -> bool {
18011 self.show_git_blame_gutter
18012 }
18013
18014 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18015 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18016 }
18017
18018 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18019 self.show_git_blame_inline
18020 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18021 && !self.newest_selection_head_on_empty_line(cx)
18022 && self.has_blame_entries(cx)
18023 }
18024
18025 fn has_blame_entries(&self, cx: &App) -> bool {
18026 self.blame()
18027 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18028 }
18029
18030 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18031 let cursor_anchor = self.selections.newest_anchor().head();
18032
18033 let snapshot = self.buffer.read(cx).snapshot(cx);
18034 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18035
18036 snapshot.line_len(buffer_row) == 0
18037 }
18038
18039 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18040 let buffer_and_selection = maybe!({
18041 let selection = self.selections.newest::<Point>(cx);
18042 let selection_range = selection.range();
18043
18044 let multi_buffer = self.buffer().read(cx);
18045 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18046 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18047
18048 let (buffer, range, _) = if selection.reversed {
18049 buffer_ranges.first()
18050 } else {
18051 buffer_ranges.last()
18052 }?;
18053
18054 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18055 ..text::ToPoint::to_point(&range.end, &buffer).row;
18056 Some((
18057 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18058 selection,
18059 ))
18060 });
18061
18062 let Some((buffer, selection)) = buffer_and_selection else {
18063 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18064 };
18065
18066 let Some(project) = self.project.as_ref() else {
18067 return Task::ready(Err(anyhow!("editor does not have project")));
18068 };
18069
18070 project.update(cx, |project, cx| {
18071 project.get_permalink_to_line(&buffer, selection, cx)
18072 })
18073 }
18074
18075 pub fn copy_permalink_to_line(
18076 &mut self,
18077 _: &CopyPermalinkToLine,
18078 window: &mut Window,
18079 cx: &mut Context<Self>,
18080 ) {
18081 let permalink_task = self.get_permalink_to_line(cx);
18082 let workspace = self.workspace();
18083
18084 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18085 Ok(permalink) => {
18086 cx.update(|_, cx| {
18087 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18088 })
18089 .ok();
18090 }
18091 Err(err) => {
18092 let message = format!("Failed to copy permalink: {err}");
18093
18094 anyhow::Result::<()>::Err(err).log_err();
18095
18096 if let Some(workspace) = workspace {
18097 workspace
18098 .update_in(cx, |workspace, _, cx| {
18099 struct CopyPermalinkToLine;
18100
18101 workspace.show_toast(
18102 Toast::new(
18103 NotificationId::unique::<CopyPermalinkToLine>(),
18104 message,
18105 ),
18106 cx,
18107 )
18108 })
18109 .ok();
18110 }
18111 }
18112 })
18113 .detach();
18114 }
18115
18116 pub fn copy_file_location(
18117 &mut self,
18118 _: &CopyFileLocation,
18119 _: &mut Window,
18120 cx: &mut Context<Self>,
18121 ) {
18122 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18123 if let Some(file) = self.target_file(cx) {
18124 if let Some(path) = file.path().to_str() {
18125 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18126 }
18127 }
18128 }
18129
18130 pub fn open_permalink_to_line(
18131 &mut self,
18132 _: &OpenPermalinkToLine,
18133 window: &mut Window,
18134 cx: &mut Context<Self>,
18135 ) {
18136 let permalink_task = self.get_permalink_to_line(cx);
18137 let workspace = self.workspace();
18138
18139 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18140 Ok(permalink) => {
18141 cx.update(|_, cx| {
18142 cx.open_url(permalink.as_ref());
18143 })
18144 .ok();
18145 }
18146 Err(err) => {
18147 let message = format!("Failed to open permalink: {err}");
18148
18149 anyhow::Result::<()>::Err(err).log_err();
18150
18151 if let Some(workspace) = workspace {
18152 workspace
18153 .update(cx, |workspace, cx| {
18154 struct OpenPermalinkToLine;
18155
18156 workspace.show_toast(
18157 Toast::new(
18158 NotificationId::unique::<OpenPermalinkToLine>(),
18159 message,
18160 ),
18161 cx,
18162 )
18163 })
18164 .ok();
18165 }
18166 }
18167 })
18168 .detach();
18169 }
18170
18171 pub fn insert_uuid_v4(
18172 &mut self,
18173 _: &InsertUuidV4,
18174 window: &mut Window,
18175 cx: &mut Context<Self>,
18176 ) {
18177 self.insert_uuid(UuidVersion::V4, window, cx);
18178 }
18179
18180 pub fn insert_uuid_v7(
18181 &mut self,
18182 _: &InsertUuidV7,
18183 window: &mut Window,
18184 cx: &mut Context<Self>,
18185 ) {
18186 self.insert_uuid(UuidVersion::V7, window, cx);
18187 }
18188
18189 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18190 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18191 self.transact(window, cx, |this, window, cx| {
18192 let edits = this
18193 .selections
18194 .all::<Point>(cx)
18195 .into_iter()
18196 .map(|selection| {
18197 let uuid = match version {
18198 UuidVersion::V4 => uuid::Uuid::new_v4(),
18199 UuidVersion::V7 => uuid::Uuid::now_v7(),
18200 };
18201
18202 (selection.range(), uuid.to_string())
18203 });
18204 this.edit(edits, cx);
18205 this.refresh_inline_completion(true, false, window, cx);
18206 });
18207 }
18208
18209 pub fn open_selections_in_multibuffer(
18210 &mut self,
18211 _: &OpenSelectionsInMultibuffer,
18212 window: &mut Window,
18213 cx: &mut Context<Self>,
18214 ) {
18215 let multibuffer = self.buffer.read(cx);
18216
18217 let Some(buffer) = multibuffer.as_singleton() else {
18218 return;
18219 };
18220
18221 let Some(workspace) = self.workspace() else {
18222 return;
18223 };
18224
18225 let locations = self
18226 .selections
18227 .disjoint_anchors()
18228 .iter()
18229 .map(|range| Location {
18230 buffer: buffer.clone(),
18231 range: range.start.text_anchor..range.end.text_anchor,
18232 })
18233 .collect::<Vec<_>>();
18234
18235 let title = multibuffer.title(cx).to_string();
18236
18237 cx.spawn_in(window, async move |_, cx| {
18238 workspace.update_in(cx, |workspace, window, cx| {
18239 Self::open_locations_in_multibuffer(
18240 workspace,
18241 locations,
18242 format!("Selections for '{title}'"),
18243 false,
18244 MultibufferSelectionMode::All,
18245 window,
18246 cx,
18247 );
18248 })
18249 })
18250 .detach();
18251 }
18252
18253 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18254 /// last highlight added will be used.
18255 ///
18256 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18257 pub fn highlight_rows<T: 'static>(
18258 &mut self,
18259 range: Range<Anchor>,
18260 color: Hsla,
18261 options: RowHighlightOptions,
18262 cx: &mut Context<Self>,
18263 ) {
18264 let snapshot = self.buffer().read(cx).snapshot(cx);
18265 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18266 let ix = row_highlights.binary_search_by(|highlight| {
18267 Ordering::Equal
18268 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18269 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18270 });
18271
18272 if let Err(mut ix) = ix {
18273 let index = post_inc(&mut self.highlight_order);
18274
18275 // If this range intersects with the preceding highlight, then merge it with
18276 // the preceding highlight. Otherwise insert a new highlight.
18277 let mut merged = false;
18278 if ix > 0 {
18279 let prev_highlight = &mut row_highlights[ix - 1];
18280 if prev_highlight
18281 .range
18282 .end
18283 .cmp(&range.start, &snapshot)
18284 .is_ge()
18285 {
18286 ix -= 1;
18287 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18288 prev_highlight.range.end = range.end;
18289 }
18290 merged = true;
18291 prev_highlight.index = index;
18292 prev_highlight.color = color;
18293 prev_highlight.options = options;
18294 }
18295 }
18296
18297 if !merged {
18298 row_highlights.insert(
18299 ix,
18300 RowHighlight {
18301 range: range.clone(),
18302 index,
18303 color,
18304 options,
18305 type_id: TypeId::of::<T>(),
18306 },
18307 );
18308 }
18309
18310 // If any of the following highlights intersect with this one, merge them.
18311 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18312 let highlight = &row_highlights[ix];
18313 if next_highlight
18314 .range
18315 .start
18316 .cmp(&highlight.range.end, &snapshot)
18317 .is_le()
18318 {
18319 if next_highlight
18320 .range
18321 .end
18322 .cmp(&highlight.range.end, &snapshot)
18323 .is_gt()
18324 {
18325 row_highlights[ix].range.end = next_highlight.range.end;
18326 }
18327 row_highlights.remove(ix + 1);
18328 } else {
18329 break;
18330 }
18331 }
18332 }
18333 }
18334
18335 /// Remove any highlighted row ranges of the given type that intersect the
18336 /// given ranges.
18337 pub fn remove_highlighted_rows<T: 'static>(
18338 &mut self,
18339 ranges_to_remove: Vec<Range<Anchor>>,
18340 cx: &mut Context<Self>,
18341 ) {
18342 let snapshot = self.buffer().read(cx).snapshot(cx);
18343 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18344 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18345 row_highlights.retain(|highlight| {
18346 while let Some(range_to_remove) = ranges_to_remove.peek() {
18347 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18348 Ordering::Less | Ordering::Equal => {
18349 ranges_to_remove.next();
18350 }
18351 Ordering::Greater => {
18352 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18353 Ordering::Less | Ordering::Equal => {
18354 return false;
18355 }
18356 Ordering::Greater => break,
18357 }
18358 }
18359 }
18360 }
18361
18362 true
18363 })
18364 }
18365
18366 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18367 pub fn clear_row_highlights<T: 'static>(&mut self) {
18368 self.highlighted_rows.remove(&TypeId::of::<T>());
18369 }
18370
18371 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18372 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18373 self.highlighted_rows
18374 .get(&TypeId::of::<T>())
18375 .map_or(&[] as &[_], |vec| vec.as_slice())
18376 .iter()
18377 .map(|highlight| (highlight.range.clone(), highlight.color))
18378 }
18379
18380 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18381 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18382 /// Allows to ignore certain kinds of highlights.
18383 pub fn highlighted_display_rows(
18384 &self,
18385 window: &mut Window,
18386 cx: &mut App,
18387 ) -> BTreeMap<DisplayRow, LineHighlight> {
18388 let snapshot = self.snapshot(window, cx);
18389 let mut used_highlight_orders = HashMap::default();
18390 self.highlighted_rows
18391 .iter()
18392 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18393 .fold(
18394 BTreeMap::<DisplayRow, LineHighlight>::new(),
18395 |mut unique_rows, highlight| {
18396 let start = highlight.range.start.to_display_point(&snapshot);
18397 let end = highlight.range.end.to_display_point(&snapshot);
18398 let start_row = start.row().0;
18399 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18400 && end.column() == 0
18401 {
18402 end.row().0.saturating_sub(1)
18403 } else {
18404 end.row().0
18405 };
18406 for row in start_row..=end_row {
18407 let used_index =
18408 used_highlight_orders.entry(row).or_insert(highlight.index);
18409 if highlight.index >= *used_index {
18410 *used_index = highlight.index;
18411 unique_rows.insert(
18412 DisplayRow(row),
18413 LineHighlight {
18414 include_gutter: highlight.options.include_gutter,
18415 border: None,
18416 background: highlight.color.into(),
18417 type_id: Some(highlight.type_id),
18418 },
18419 );
18420 }
18421 }
18422 unique_rows
18423 },
18424 )
18425 }
18426
18427 pub fn highlighted_display_row_for_autoscroll(
18428 &self,
18429 snapshot: &DisplaySnapshot,
18430 ) -> Option<DisplayRow> {
18431 self.highlighted_rows
18432 .values()
18433 .flat_map(|highlighted_rows| highlighted_rows.iter())
18434 .filter_map(|highlight| {
18435 if highlight.options.autoscroll {
18436 Some(highlight.range.start.to_display_point(snapshot).row())
18437 } else {
18438 None
18439 }
18440 })
18441 .min()
18442 }
18443
18444 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18445 self.highlight_background::<SearchWithinRange>(
18446 ranges,
18447 |colors| colors.editor_document_highlight_read_background,
18448 cx,
18449 )
18450 }
18451
18452 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18453 self.breadcrumb_header = Some(new_header);
18454 }
18455
18456 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18457 self.clear_background_highlights::<SearchWithinRange>(cx);
18458 }
18459
18460 pub fn highlight_background<T: 'static>(
18461 &mut self,
18462 ranges: &[Range<Anchor>],
18463 color_fetcher: fn(&ThemeColors) -> Hsla,
18464 cx: &mut Context<Self>,
18465 ) {
18466 self.background_highlights
18467 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18468 self.scrollbar_marker_state.dirty = true;
18469 cx.notify();
18470 }
18471
18472 pub fn clear_background_highlights<T: 'static>(
18473 &mut self,
18474 cx: &mut Context<Self>,
18475 ) -> Option<BackgroundHighlight> {
18476 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18477 if !text_highlights.1.is_empty() {
18478 self.scrollbar_marker_state.dirty = true;
18479 cx.notify();
18480 }
18481 Some(text_highlights)
18482 }
18483
18484 pub fn highlight_gutter<T: 'static>(
18485 &mut self,
18486 ranges: impl Into<Vec<Range<Anchor>>>,
18487 color_fetcher: fn(&App) -> Hsla,
18488 cx: &mut Context<Self>,
18489 ) {
18490 self.gutter_highlights
18491 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18492 cx.notify();
18493 }
18494
18495 pub fn clear_gutter_highlights<T: 'static>(
18496 &mut self,
18497 cx: &mut Context<Self>,
18498 ) -> Option<GutterHighlight> {
18499 cx.notify();
18500 self.gutter_highlights.remove(&TypeId::of::<T>())
18501 }
18502
18503 pub fn insert_gutter_highlight<T: 'static>(
18504 &mut self,
18505 range: Range<Anchor>,
18506 color_fetcher: fn(&App) -> Hsla,
18507 cx: &mut Context<Self>,
18508 ) {
18509 let snapshot = self.buffer().read(cx).snapshot(cx);
18510 let mut highlights = self
18511 .gutter_highlights
18512 .remove(&TypeId::of::<T>())
18513 .map(|(_, highlights)| highlights)
18514 .unwrap_or_default();
18515 let ix = highlights.binary_search_by(|highlight| {
18516 Ordering::Equal
18517 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18518 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18519 });
18520 if let Err(ix) = ix {
18521 highlights.insert(ix, range);
18522 }
18523 self.gutter_highlights
18524 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18525 }
18526
18527 pub fn remove_gutter_highlights<T: 'static>(
18528 &mut self,
18529 ranges_to_remove: Vec<Range<Anchor>>,
18530 cx: &mut Context<Self>,
18531 ) {
18532 let snapshot = self.buffer().read(cx).snapshot(cx);
18533 let Some((color_fetcher, mut gutter_highlights)) =
18534 self.gutter_highlights.remove(&TypeId::of::<T>())
18535 else {
18536 return;
18537 };
18538 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18539 gutter_highlights.retain(|highlight| {
18540 while let Some(range_to_remove) = ranges_to_remove.peek() {
18541 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18542 Ordering::Less | Ordering::Equal => {
18543 ranges_to_remove.next();
18544 }
18545 Ordering::Greater => {
18546 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18547 Ordering::Less | Ordering::Equal => {
18548 return false;
18549 }
18550 Ordering::Greater => break,
18551 }
18552 }
18553 }
18554 }
18555
18556 true
18557 });
18558 self.gutter_highlights
18559 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18560 }
18561
18562 #[cfg(feature = "test-support")]
18563 pub fn all_text_background_highlights(
18564 &self,
18565 window: &mut Window,
18566 cx: &mut Context<Self>,
18567 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18568 let snapshot = self.snapshot(window, cx);
18569 let buffer = &snapshot.buffer_snapshot;
18570 let start = buffer.anchor_before(0);
18571 let end = buffer.anchor_after(buffer.len());
18572 let theme = cx.theme().colors();
18573 self.background_highlights_in_range(start..end, &snapshot, theme)
18574 }
18575
18576 #[cfg(feature = "test-support")]
18577 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18578 let snapshot = self.buffer().read(cx).snapshot(cx);
18579
18580 let highlights = self
18581 .background_highlights
18582 .get(&TypeId::of::<items::BufferSearchHighlights>());
18583
18584 if let Some((_color, ranges)) = highlights {
18585 ranges
18586 .iter()
18587 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18588 .collect_vec()
18589 } else {
18590 vec![]
18591 }
18592 }
18593
18594 fn document_highlights_for_position<'a>(
18595 &'a self,
18596 position: Anchor,
18597 buffer: &'a MultiBufferSnapshot,
18598 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18599 let read_highlights = self
18600 .background_highlights
18601 .get(&TypeId::of::<DocumentHighlightRead>())
18602 .map(|h| &h.1);
18603 let write_highlights = self
18604 .background_highlights
18605 .get(&TypeId::of::<DocumentHighlightWrite>())
18606 .map(|h| &h.1);
18607 let left_position = position.bias_left(buffer);
18608 let right_position = position.bias_right(buffer);
18609 read_highlights
18610 .into_iter()
18611 .chain(write_highlights)
18612 .flat_map(move |ranges| {
18613 let start_ix = match ranges.binary_search_by(|probe| {
18614 let cmp = probe.end.cmp(&left_position, buffer);
18615 if cmp.is_ge() {
18616 Ordering::Greater
18617 } else {
18618 Ordering::Less
18619 }
18620 }) {
18621 Ok(i) | Err(i) => i,
18622 };
18623
18624 ranges[start_ix..]
18625 .iter()
18626 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18627 })
18628 }
18629
18630 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18631 self.background_highlights
18632 .get(&TypeId::of::<T>())
18633 .map_or(false, |(_, highlights)| !highlights.is_empty())
18634 }
18635
18636 pub fn background_highlights_in_range(
18637 &self,
18638 search_range: Range<Anchor>,
18639 display_snapshot: &DisplaySnapshot,
18640 theme: &ThemeColors,
18641 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18642 let mut results = Vec::new();
18643 for (color_fetcher, ranges) in self.background_highlights.values() {
18644 let color = color_fetcher(theme);
18645 let start_ix = match ranges.binary_search_by(|probe| {
18646 let cmp = probe
18647 .end
18648 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18649 if cmp.is_gt() {
18650 Ordering::Greater
18651 } else {
18652 Ordering::Less
18653 }
18654 }) {
18655 Ok(i) | Err(i) => i,
18656 };
18657 for range in &ranges[start_ix..] {
18658 if range
18659 .start
18660 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18661 .is_ge()
18662 {
18663 break;
18664 }
18665
18666 let start = range.start.to_display_point(display_snapshot);
18667 let end = range.end.to_display_point(display_snapshot);
18668 results.push((start..end, color))
18669 }
18670 }
18671 results
18672 }
18673
18674 pub fn background_highlight_row_ranges<T: 'static>(
18675 &self,
18676 search_range: Range<Anchor>,
18677 display_snapshot: &DisplaySnapshot,
18678 count: usize,
18679 ) -> Vec<RangeInclusive<DisplayPoint>> {
18680 let mut results = Vec::new();
18681 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18682 return vec![];
18683 };
18684
18685 let start_ix = match ranges.binary_search_by(|probe| {
18686 let cmp = probe
18687 .end
18688 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18689 if cmp.is_gt() {
18690 Ordering::Greater
18691 } else {
18692 Ordering::Less
18693 }
18694 }) {
18695 Ok(i) | Err(i) => i,
18696 };
18697 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18698 if let (Some(start_display), Some(end_display)) = (start, end) {
18699 results.push(
18700 start_display.to_display_point(display_snapshot)
18701 ..=end_display.to_display_point(display_snapshot),
18702 );
18703 }
18704 };
18705 let mut start_row: Option<Point> = None;
18706 let mut end_row: Option<Point> = None;
18707 if ranges.len() > count {
18708 return Vec::new();
18709 }
18710 for range in &ranges[start_ix..] {
18711 if range
18712 .start
18713 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18714 .is_ge()
18715 {
18716 break;
18717 }
18718 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18719 if let Some(current_row) = &end_row {
18720 if end.row == current_row.row {
18721 continue;
18722 }
18723 }
18724 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18725 if start_row.is_none() {
18726 assert_eq!(end_row, None);
18727 start_row = Some(start);
18728 end_row = Some(end);
18729 continue;
18730 }
18731 if let Some(current_end) = end_row.as_mut() {
18732 if start.row > current_end.row + 1 {
18733 push_region(start_row, end_row);
18734 start_row = Some(start);
18735 end_row = Some(end);
18736 } else {
18737 // Merge two hunks.
18738 *current_end = end;
18739 }
18740 } else {
18741 unreachable!();
18742 }
18743 }
18744 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18745 push_region(start_row, end_row);
18746 results
18747 }
18748
18749 pub fn gutter_highlights_in_range(
18750 &self,
18751 search_range: Range<Anchor>,
18752 display_snapshot: &DisplaySnapshot,
18753 cx: &App,
18754 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18755 let mut results = Vec::new();
18756 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18757 let color = color_fetcher(cx);
18758 let start_ix = match ranges.binary_search_by(|probe| {
18759 let cmp = probe
18760 .end
18761 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18762 if cmp.is_gt() {
18763 Ordering::Greater
18764 } else {
18765 Ordering::Less
18766 }
18767 }) {
18768 Ok(i) | Err(i) => i,
18769 };
18770 for range in &ranges[start_ix..] {
18771 if range
18772 .start
18773 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18774 .is_ge()
18775 {
18776 break;
18777 }
18778
18779 let start = range.start.to_display_point(display_snapshot);
18780 let end = range.end.to_display_point(display_snapshot);
18781 results.push((start..end, color))
18782 }
18783 }
18784 results
18785 }
18786
18787 /// Get the text ranges corresponding to the redaction query
18788 pub fn redacted_ranges(
18789 &self,
18790 search_range: Range<Anchor>,
18791 display_snapshot: &DisplaySnapshot,
18792 cx: &App,
18793 ) -> Vec<Range<DisplayPoint>> {
18794 display_snapshot
18795 .buffer_snapshot
18796 .redacted_ranges(search_range, |file| {
18797 if let Some(file) = file {
18798 file.is_private()
18799 && EditorSettings::get(
18800 Some(SettingsLocation {
18801 worktree_id: file.worktree_id(cx),
18802 path: file.path().as_ref(),
18803 }),
18804 cx,
18805 )
18806 .redact_private_values
18807 } else {
18808 false
18809 }
18810 })
18811 .map(|range| {
18812 range.start.to_display_point(display_snapshot)
18813 ..range.end.to_display_point(display_snapshot)
18814 })
18815 .collect()
18816 }
18817
18818 pub fn highlight_text<T: 'static>(
18819 &mut self,
18820 ranges: Vec<Range<Anchor>>,
18821 style: HighlightStyle,
18822 cx: &mut Context<Self>,
18823 ) {
18824 self.display_map.update(cx, |map, _| {
18825 map.highlight_text(TypeId::of::<T>(), ranges, style)
18826 });
18827 cx.notify();
18828 }
18829
18830 pub(crate) fn highlight_inlays<T: 'static>(
18831 &mut self,
18832 highlights: Vec<InlayHighlight>,
18833 style: HighlightStyle,
18834 cx: &mut Context<Self>,
18835 ) {
18836 self.display_map.update(cx, |map, _| {
18837 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18838 });
18839 cx.notify();
18840 }
18841
18842 pub fn text_highlights<'a, T: 'static>(
18843 &'a self,
18844 cx: &'a App,
18845 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18846 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18847 }
18848
18849 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18850 let cleared = self
18851 .display_map
18852 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18853 if cleared {
18854 cx.notify();
18855 }
18856 }
18857
18858 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18859 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18860 && self.focus_handle.is_focused(window)
18861 }
18862
18863 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18864 self.show_cursor_when_unfocused = is_enabled;
18865 cx.notify();
18866 }
18867
18868 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18869 cx.notify();
18870 }
18871
18872 fn on_debug_session_event(
18873 &mut self,
18874 _session: Entity<Session>,
18875 event: &SessionEvent,
18876 cx: &mut Context<Self>,
18877 ) {
18878 match event {
18879 SessionEvent::InvalidateInlineValue => {
18880 self.refresh_inline_values(cx);
18881 }
18882 _ => {}
18883 }
18884 }
18885
18886 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18887 let Some(project) = self.project.clone() else {
18888 return;
18889 };
18890
18891 if !self.inline_value_cache.enabled {
18892 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18893 self.splice_inlays(&inlays, Vec::new(), cx);
18894 return;
18895 }
18896
18897 let current_execution_position = self
18898 .highlighted_rows
18899 .get(&TypeId::of::<ActiveDebugLine>())
18900 .and_then(|lines| lines.last().map(|line| line.range.start));
18901
18902 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18903 let inline_values = editor
18904 .update(cx, |editor, cx| {
18905 let Some(current_execution_position) = current_execution_position else {
18906 return Some(Task::ready(Ok(Vec::new())));
18907 };
18908
18909 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18910 let snapshot = buffer.snapshot(cx);
18911
18912 let excerpt = snapshot.excerpt_containing(
18913 current_execution_position..current_execution_position,
18914 )?;
18915
18916 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18917 })?;
18918
18919 let range =
18920 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18921
18922 project.inline_values(buffer, range, cx)
18923 })
18924 .ok()
18925 .flatten()?
18926 .await
18927 .context("refreshing debugger inlays")
18928 .log_err()?;
18929
18930 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18931
18932 for (buffer_id, inline_value) in inline_values
18933 .into_iter()
18934 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18935 {
18936 buffer_inline_values
18937 .entry(buffer_id)
18938 .or_default()
18939 .push(inline_value);
18940 }
18941
18942 editor
18943 .update(cx, |editor, cx| {
18944 let snapshot = editor.buffer.read(cx).snapshot(cx);
18945 let mut new_inlays = Vec::default();
18946
18947 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18948 let buffer_id = buffer_snapshot.remote_id();
18949 buffer_inline_values
18950 .get(&buffer_id)
18951 .into_iter()
18952 .flatten()
18953 .for_each(|hint| {
18954 let inlay = Inlay::debugger_hint(
18955 post_inc(&mut editor.next_inlay_id),
18956 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18957 hint.text(),
18958 );
18959
18960 new_inlays.push(inlay);
18961 });
18962 }
18963
18964 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18965 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18966
18967 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18968 })
18969 .ok()?;
18970 Some(())
18971 });
18972 }
18973
18974 fn on_buffer_event(
18975 &mut self,
18976 multibuffer: &Entity<MultiBuffer>,
18977 event: &multi_buffer::Event,
18978 window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) {
18981 match event {
18982 multi_buffer::Event::Edited {
18983 singleton_buffer_edited,
18984 edited_buffer,
18985 } => {
18986 self.scrollbar_marker_state.dirty = true;
18987 self.active_indent_guides_state.dirty = true;
18988 self.refresh_active_diagnostics(cx);
18989 self.refresh_code_actions(window, cx);
18990 self.refresh_selected_text_highlights(true, window, cx);
18991 refresh_matching_bracket_highlights(self, window, cx);
18992 if self.has_active_inline_completion() {
18993 self.update_visible_inline_completion(window, cx);
18994 }
18995 if let Some(project) = self.project.as_ref() {
18996 if let Some(edited_buffer) = edited_buffer {
18997 project.update(cx, |project, cx| {
18998 self.registered_buffers
18999 .entry(edited_buffer.read(cx).remote_id())
19000 .or_insert_with(|| {
19001 project
19002 .register_buffer_with_language_servers(&edited_buffer, cx)
19003 });
19004 });
19005 if edited_buffer.read(cx).file().is_some() {
19006 self.pull_diagnostics(
19007 Some(edited_buffer.read(cx).remote_id()),
19008 window,
19009 cx,
19010 );
19011 }
19012 }
19013 }
19014 cx.emit(EditorEvent::BufferEdited);
19015 cx.emit(SearchEvent::MatchesInvalidated);
19016 if *singleton_buffer_edited {
19017 if let Some(buffer) = edited_buffer {
19018 if buffer.read(cx).file().is_none() {
19019 cx.emit(EditorEvent::TitleChanged);
19020 }
19021 }
19022 if let Some(project) = &self.project {
19023 #[allow(clippy::mutable_key_type)]
19024 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19025 multibuffer
19026 .all_buffers()
19027 .into_iter()
19028 .filter_map(|buffer| {
19029 buffer.update(cx, |buffer, cx| {
19030 let language = buffer.language()?;
19031 let should_discard = project.update(cx, |project, cx| {
19032 project.is_local()
19033 && !project.has_language_servers_for(buffer, cx)
19034 });
19035 should_discard.not().then_some(language.clone())
19036 })
19037 })
19038 .collect::<HashSet<_>>()
19039 });
19040 if !languages_affected.is_empty() {
19041 self.refresh_inlay_hints(
19042 InlayHintRefreshReason::BufferEdited(languages_affected),
19043 cx,
19044 );
19045 }
19046 }
19047 }
19048
19049 let Some(project) = &self.project else { return };
19050 let (telemetry, is_via_ssh) = {
19051 let project = project.read(cx);
19052 let telemetry = project.client().telemetry().clone();
19053 let is_via_ssh = project.is_via_ssh();
19054 (telemetry, is_via_ssh)
19055 };
19056 refresh_linked_ranges(self, window, cx);
19057 telemetry.log_edit_event("editor", is_via_ssh);
19058 }
19059 multi_buffer::Event::ExcerptsAdded {
19060 buffer,
19061 predecessor,
19062 excerpts,
19063 } => {
19064 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19065 let buffer_id = buffer.read(cx).remote_id();
19066 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19067 if let Some(project) = &self.project {
19068 update_uncommitted_diff_for_buffer(
19069 cx.entity(),
19070 project,
19071 [buffer.clone()],
19072 self.buffer.clone(),
19073 cx,
19074 )
19075 .detach();
19076 }
19077 }
19078 cx.emit(EditorEvent::ExcerptsAdded {
19079 buffer: buffer.clone(),
19080 predecessor: *predecessor,
19081 excerpts: excerpts.clone(),
19082 });
19083 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19084 }
19085 multi_buffer::Event::ExcerptsRemoved {
19086 ids,
19087 removed_buffer_ids,
19088 } => {
19089 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19090 let buffer = self.buffer.read(cx);
19091 self.registered_buffers
19092 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19093 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19094 cx.emit(EditorEvent::ExcerptsRemoved {
19095 ids: ids.clone(),
19096 removed_buffer_ids: removed_buffer_ids.clone(),
19097 })
19098 }
19099 multi_buffer::Event::ExcerptsEdited {
19100 excerpt_ids,
19101 buffer_ids,
19102 } => {
19103 self.display_map.update(cx, |map, cx| {
19104 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19105 });
19106 cx.emit(EditorEvent::ExcerptsEdited {
19107 ids: excerpt_ids.clone(),
19108 })
19109 }
19110 multi_buffer::Event::ExcerptsExpanded { ids } => {
19111 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19112 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19113 }
19114 multi_buffer::Event::Reparsed(buffer_id) => {
19115 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19116 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19117
19118 cx.emit(EditorEvent::Reparsed(*buffer_id));
19119 }
19120 multi_buffer::Event::DiffHunksToggled => {
19121 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19122 }
19123 multi_buffer::Event::LanguageChanged(buffer_id) => {
19124 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19125 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19126 cx.emit(EditorEvent::Reparsed(*buffer_id));
19127 cx.notify();
19128 }
19129 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19130 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19131 multi_buffer::Event::FileHandleChanged
19132 | multi_buffer::Event::Reloaded
19133 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19134 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19135 multi_buffer::Event::DiagnosticsUpdated => {
19136 self.update_diagnostics_state(window, cx);
19137 }
19138 _ => {}
19139 };
19140 }
19141
19142 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19143 self.refresh_active_diagnostics(cx);
19144 self.refresh_inline_diagnostics(true, window, cx);
19145 self.scrollbar_marker_state.dirty = true;
19146 cx.notify();
19147 }
19148
19149 pub fn start_temporary_diff_override(&mut self) {
19150 self.load_diff_task.take();
19151 self.temporary_diff_override = true;
19152 }
19153
19154 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19155 self.temporary_diff_override = false;
19156 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19157 self.buffer.update(cx, |buffer, cx| {
19158 buffer.set_all_diff_hunks_collapsed(cx);
19159 });
19160
19161 if let Some(project) = self.project.clone() {
19162 self.load_diff_task = Some(
19163 update_uncommitted_diff_for_buffer(
19164 cx.entity(),
19165 &project,
19166 self.buffer.read(cx).all_buffers(),
19167 self.buffer.clone(),
19168 cx,
19169 )
19170 .shared(),
19171 );
19172 }
19173 }
19174
19175 fn on_display_map_changed(
19176 &mut self,
19177 _: Entity<DisplayMap>,
19178 _: &mut Window,
19179 cx: &mut Context<Self>,
19180 ) {
19181 cx.notify();
19182 }
19183
19184 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19185 let new_severity = if self.diagnostics_enabled() {
19186 EditorSettings::get_global(cx)
19187 .diagnostics_max_severity
19188 .unwrap_or(DiagnosticSeverity::Hint)
19189 } else {
19190 DiagnosticSeverity::Off
19191 };
19192 self.set_max_diagnostics_severity(new_severity, cx);
19193 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19194 self.update_edit_prediction_settings(cx);
19195 self.refresh_inline_completion(true, false, window, cx);
19196 self.refresh_inlay_hints(
19197 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19198 self.selections.newest_anchor().head(),
19199 &self.buffer.read(cx).snapshot(cx),
19200 cx,
19201 )),
19202 cx,
19203 );
19204
19205 let old_cursor_shape = self.cursor_shape;
19206
19207 {
19208 let editor_settings = EditorSettings::get_global(cx);
19209 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19210 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19211 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19212 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19213 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19214 }
19215
19216 if old_cursor_shape != self.cursor_shape {
19217 cx.emit(EditorEvent::CursorShapeChanged);
19218 }
19219
19220 let project_settings = ProjectSettings::get_global(cx);
19221 self.serialize_dirty_buffers =
19222 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19223
19224 if self.mode.is_full() {
19225 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19226 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19227 if self.show_inline_diagnostics != show_inline_diagnostics {
19228 self.show_inline_diagnostics = show_inline_diagnostics;
19229 self.refresh_inline_diagnostics(false, window, cx);
19230 }
19231
19232 if self.git_blame_inline_enabled != inline_blame_enabled {
19233 self.toggle_git_blame_inline_internal(false, window, cx);
19234 }
19235
19236 let minimap_settings = EditorSettings::get_global(cx).minimap;
19237 if self.minimap_visibility != MinimapVisibility::Disabled {
19238 if self.minimap_visibility.settings_visibility()
19239 != minimap_settings.minimap_enabled()
19240 {
19241 self.set_minimap_visibility(
19242 MinimapVisibility::for_mode(self.mode(), cx),
19243 window,
19244 cx,
19245 );
19246 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19247 minimap_entity.update(cx, |minimap_editor, cx| {
19248 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19249 })
19250 }
19251 }
19252 }
19253
19254 cx.notify();
19255 }
19256
19257 pub fn set_searchable(&mut self, searchable: bool) {
19258 self.searchable = searchable;
19259 }
19260
19261 pub fn searchable(&self) -> bool {
19262 self.searchable
19263 }
19264
19265 fn open_proposed_changes_editor(
19266 &mut self,
19267 _: &OpenProposedChangesEditor,
19268 window: &mut Window,
19269 cx: &mut Context<Self>,
19270 ) {
19271 let Some(workspace) = self.workspace() else {
19272 cx.propagate();
19273 return;
19274 };
19275
19276 let selections = self.selections.all::<usize>(cx);
19277 let multi_buffer = self.buffer.read(cx);
19278 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19279 let mut new_selections_by_buffer = HashMap::default();
19280 for selection in selections {
19281 for (buffer, range, _) in
19282 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19283 {
19284 let mut range = range.to_point(buffer);
19285 range.start.column = 0;
19286 range.end.column = buffer.line_len(range.end.row);
19287 new_selections_by_buffer
19288 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19289 .or_insert(Vec::new())
19290 .push(range)
19291 }
19292 }
19293
19294 let proposed_changes_buffers = new_selections_by_buffer
19295 .into_iter()
19296 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19297 .collect::<Vec<_>>();
19298 let proposed_changes_editor = cx.new(|cx| {
19299 ProposedChangesEditor::new(
19300 "Proposed changes",
19301 proposed_changes_buffers,
19302 self.project.clone(),
19303 window,
19304 cx,
19305 )
19306 });
19307
19308 window.defer(cx, move |window, cx| {
19309 workspace.update(cx, |workspace, cx| {
19310 workspace.active_pane().update(cx, |pane, cx| {
19311 pane.add_item(
19312 Box::new(proposed_changes_editor),
19313 true,
19314 true,
19315 None,
19316 window,
19317 cx,
19318 );
19319 });
19320 });
19321 });
19322 }
19323
19324 pub fn open_excerpts_in_split(
19325 &mut self,
19326 _: &OpenExcerptsSplit,
19327 window: &mut Window,
19328 cx: &mut Context<Self>,
19329 ) {
19330 self.open_excerpts_common(None, true, window, cx)
19331 }
19332
19333 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19334 self.open_excerpts_common(None, false, window, cx)
19335 }
19336
19337 fn open_excerpts_common(
19338 &mut self,
19339 jump_data: Option<JumpData>,
19340 split: bool,
19341 window: &mut Window,
19342 cx: &mut Context<Self>,
19343 ) {
19344 let Some(workspace) = self.workspace() else {
19345 cx.propagate();
19346 return;
19347 };
19348
19349 if self.buffer.read(cx).is_singleton() {
19350 cx.propagate();
19351 return;
19352 }
19353
19354 let mut new_selections_by_buffer = HashMap::default();
19355 match &jump_data {
19356 Some(JumpData::MultiBufferPoint {
19357 excerpt_id,
19358 position,
19359 anchor,
19360 line_offset_from_top,
19361 }) => {
19362 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19363 if let Some(buffer) = multi_buffer_snapshot
19364 .buffer_id_for_excerpt(*excerpt_id)
19365 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19366 {
19367 let buffer_snapshot = buffer.read(cx).snapshot();
19368 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19369 language::ToPoint::to_point(anchor, &buffer_snapshot)
19370 } else {
19371 buffer_snapshot.clip_point(*position, Bias::Left)
19372 };
19373 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19374 new_selections_by_buffer.insert(
19375 buffer,
19376 (
19377 vec![jump_to_offset..jump_to_offset],
19378 Some(*line_offset_from_top),
19379 ),
19380 );
19381 }
19382 }
19383 Some(JumpData::MultiBufferRow {
19384 row,
19385 line_offset_from_top,
19386 }) => {
19387 let point = MultiBufferPoint::new(row.0, 0);
19388 if let Some((buffer, buffer_point, _)) =
19389 self.buffer.read(cx).point_to_buffer_point(point, cx)
19390 {
19391 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19392 new_selections_by_buffer
19393 .entry(buffer)
19394 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19395 .0
19396 .push(buffer_offset..buffer_offset)
19397 }
19398 }
19399 None => {
19400 let selections = self.selections.all::<usize>(cx);
19401 let multi_buffer = self.buffer.read(cx);
19402 for selection in selections {
19403 for (snapshot, range, _, anchor) in multi_buffer
19404 .snapshot(cx)
19405 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19406 {
19407 if let Some(anchor) = anchor {
19408 // selection is in a deleted hunk
19409 let Some(buffer_id) = anchor.buffer_id else {
19410 continue;
19411 };
19412 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19413 continue;
19414 };
19415 let offset = text::ToOffset::to_offset(
19416 &anchor.text_anchor,
19417 &buffer_handle.read(cx).snapshot(),
19418 );
19419 let range = offset..offset;
19420 new_selections_by_buffer
19421 .entry(buffer_handle)
19422 .or_insert((Vec::new(), None))
19423 .0
19424 .push(range)
19425 } else {
19426 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19427 else {
19428 continue;
19429 };
19430 new_selections_by_buffer
19431 .entry(buffer_handle)
19432 .or_insert((Vec::new(), None))
19433 .0
19434 .push(range)
19435 }
19436 }
19437 }
19438 }
19439 }
19440
19441 new_selections_by_buffer
19442 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19443
19444 if new_selections_by_buffer.is_empty() {
19445 return;
19446 }
19447
19448 // We defer the pane interaction because we ourselves are a workspace item
19449 // and activating a new item causes the pane to call a method on us reentrantly,
19450 // which panics if we're on the stack.
19451 window.defer(cx, move |window, cx| {
19452 workspace.update(cx, |workspace, cx| {
19453 let pane = if split {
19454 workspace.adjacent_pane(window, cx)
19455 } else {
19456 workspace.active_pane().clone()
19457 };
19458
19459 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19460 let editor = buffer
19461 .read(cx)
19462 .file()
19463 .is_none()
19464 .then(|| {
19465 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19466 // so `workspace.open_project_item` will never find them, always opening a new editor.
19467 // Instead, we try to activate the existing editor in the pane first.
19468 let (editor, pane_item_index) =
19469 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19470 let editor = item.downcast::<Editor>()?;
19471 let singleton_buffer =
19472 editor.read(cx).buffer().read(cx).as_singleton()?;
19473 if singleton_buffer == buffer {
19474 Some((editor, i))
19475 } else {
19476 None
19477 }
19478 })?;
19479 pane.update(cx, |pane, cx| {
19480 pane.activate_item(pane_item_index, true, true, window, cx)
19481 });
19482 Some(editor)
19483 })
19484 .flatten()
19485 .unwrap_or_else(|| {
19486 workspace.open_project_item::<Self>(
19487 pane.clone(),
19488 buffer,
19489 true,
19490 true,
19491 window,
19492 cx,
19493 )
19494 });
19495
19496 editor.update(cx, |editor, cx| {
19497 let autoscroll = match scroll_offset {
19498 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19499 None => Autoscroll::newest(),
19500 };
19501 let nav_history = editor.nav_history.take();
19502 editor.change_selections(Some(autoscroll), window, cx, |s| {
19503 s.select_ranges(ranges);
19504 });
19505 editor.nav_history = nav_history;
19506 });
19507 }
19508 })
19509 });
19510 }
19511
19512 // For now, don't allow opening excerpts in buffers that aren't backed by
19513 // regular project files.
19514 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19515 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19516 }
19517
19518 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19519 let snapshot = self.buffer.read(cx).read(cx);
19520 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19521 Some(
19522 ranges
19523 .iter()
19524 .map(move |range| {
19525 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19526 })
19527 .collect(),
19528 )
19529 }
19530
19531 fn selection_replacement_ranges(
19532 &self,
19533 range: Range<OffsetUtf16>,
19534 cx: &mut App,
19535 ) -> Vec<Range<OffsetUtf16>> {
19536 let selections = self.selections.all::<OffsetUtf16>(cx);
19537 let newest_selection = selections
19538 .iter()
19539 .max_by_key(|selection| selection.id)
19540 .unwrap();
19541 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19542 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19543 let snapshot = self.buffer.read(cx).read(cx);
19544 selections
19545 .into_iter()
19546 .map(|mut selection| {
19547 selection.start.0 =
19548 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19549 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19550 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19551 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19552 })
19553 .collect()
19554 }
19555
19556 fn report_editor_event(
19557 &self,
19558 event_type: &'static str,
19559 file_extension: Option<String>,
19560 cx: &App,
19561 ) {
19562 if cfg!(any(test, feature = "test-support")) {
19563 return;
19564 }
19565
19566 let Some(project) = &self.project else { return };
19567
19568 // If None, we are in a file without an extension
19569 let file = self
19570 .buffer
19571 .read(cx)
19572 .as_singleton()
19573 .and_then(|b| b.read(cx).file());
19574 let file_extension = file_extension.or(file
19575 .as_ref()
19576 .and_then(|file| Path::new(file.file_name(cx)).extension())
19577 .and_then(|e| e.to_str())
19578 .map(|a| a.to_string()));
19579
19580 let vim_mode = vim_enabled(cx);
19581
19582 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19583 let copilot_enabled = edit_predictions_provider
19584 == language::language_settings::EditPredictionProvider::Copilot;
19585 let copilot_enabled_for_language = self
19586 .buffer
19587 .read(cx)
19588 .language_settings(cx)
19589 .show_edit_predictions;
19590
19591 let project = project.read(cx);
19592 telemetry::event!(
19593 event_type,
19594 file_extension,
19595 vim_mode,
19596 copilot_enabled,
19597 copilot_enabled_for_language,
19598 edit_predictions_provider,
19599 is_via_ssh = project.is_via_ssh(),
19600 );
19601 }
19602
19603 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19604 /// with each line being an array of {text, highlight} objects.
19605 fn copy_highlight_json(
19606 &mut self,
19607 _: &CopyHighlightJson,
19608 window: &mut Window,
19609 cx: &mut Context<Self>,
19610 ) {
19611 #[derive(Serialize)]
19612 struct Chunk<'a> {
19613 text: String,
19614 highlight: Option<&'a str>,
19615 }
19616
19617 let snapshot = self.buffer.read(cx).snapshot(cx);
19618 let range = self
19619 .selected_text_range(false, window, cx)
19620 .and_then(|selection| {
19621 if selection.range.is_empty() {
19622 None
19623 } else {
19624 Some(selection.range)
19625 }
19626 })
19627 .unwrap_or_else(|| 0..snapshot.len());
19628
19629 let chunks = snapshot.chunks(range, true);
19630 let mut lines = Vec::new();
19631 let mut line: VecDeque<Chunk> = VecDeque::new();
19632
19633 let Some(style) = self.style.as_ref() else {
19634 return;
19635 };
19636
19637 for chunk in chunks {
19638 let highlight = chunk
19639 .syntax_highlight_id
19640 .and_then(|id| id.name(&style.syntax));
19641 let mut chunk_lines = chunk.text.split('\n').peekable();
19642 while let Some(text) = chunk_lines.next() {
19643 let mut merged_with_last_token = false;
19644 if let Some(last_token) = line.back_mut() {
19645 if last_token.highlight == highlight {
19646 last_token.text.push_str(text);
19647 merged_with_last_token = true;
19648 }
19649 }
19650
19651 if !merged_with_last_token {
19652 line.push_back(Chunk {
19653 text: text.into(),
19654 highlight,
19655 });
19656 }
19657
19658 if chunk_lines.peek().is_some() {
19659 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19660 line.pop_front();
19661 }
19662 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19663 line.pop_back();
19664 }
19665
19666 lines.push(mem::take(&mut line));
19667 }
19668 }
19669 }
19670
19671 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19672 return;
19673 };
19674 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19675 }
19676
19677 pub fn open_context_menu(
19678 &mut self,
19679 _: &OpenContextMenu,
19680 window: &mut Window,
19681 cx: &mut Context<Self>,
19682 ) {
19683 self.request_autoscroll(Autoscroll::newest(), cx);
19684 let position = self.selections.newest_display(cx).start;
19685 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19686 }
19687
19688 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19689 &self.inlay_hint_cache
19690 }
19691
19692 pub fn replay_insert_event(
19693 &mut self,
19694 text: &str,
19695 relative_utf16_range: Option<Range<isize>>,
19696 window: &mut Window,
19697 cx: &mut Context<Self>,
19698 ) {
19699 if !self.input_enabled {
19700 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19701 return;
19702 }
19703 if let Some(relative_utf16_range) = relative_utf16_range {
19704 let selections = self.selections.all::<OffsetUtf16>(cx);
19705 self.change_selections(None, window, cx, |s| {
19706 let new_ranges = selections.into_iter().map(|range| {
19707 let start = OffsetUtf16(
19708 range
19709 .head()
19710 .0
19711 .saturating_add_signed(relative_utf16_range.start),
19712 );
19713 let end = OffsetUtf16(
19714 range
19715 .head()
19716 .0
19717 .saturating_add_signed(relative_utf16_range.end),
19718 );
19719 start..end
19720 });
19721 s.select_ranges(new_ranges);
19722 });
19723 }
19724
19725 self.handle_input(text, window, cx);
19726 }
19727
19728 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19729 let Some(provider) = self.semantics_provider.as_ref() else {
19730 return false;
19731 };
19732
19733 let mut supports = false;
19734 self.buffer().update(cx, |this, cx| {
19735 this.for_each_buffer(|buffer| {
19736 supports |= provider.supports_inlay_hints(buffer, cx);
19737 });
19738 });
19739
19740 supports
19741 }
19742
19743 pub fn is_focused(&self, window: &Window) -> bool {
19744 self.focus_handle.is_focused(window)
19745 }
19746
19747 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19748 cx.emit(EditorEvent::Focused);
19749
19750 if let Some(descendant) = self
19751 .last_focused_descendant
19752 .take()
19753 .and_then(|descendant| descendant.upgrade())
19754 {
19755 window.focus(&descendant);
19756 } else {
19757 if let Some(blame) = self.blame.as_ref() {
19758 blame.update(cx, GitBlame::focus)
19759 }
19760
19761 self.blink_manager.update(cx, BlinkManager::enable);
19762 self.show_cursor_names(window, cx);
19763 self.buffer.update(cx, |buffer, cx| {
19764 buffer.finalize_last_transaction(cx);
19765 if self.leader_id.is_none() {
19766 buffer.set_active_selections(
19767 &self.selections.disjoint_anchors(),
19768 self.selections.line_mode,
19769 self.cursor_shape,
19770 cx,
19771 );
19772 }
19773 });
19774 }
19775 }
19776
19777 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19778 cx.emit(EditorEvent::FocusedIn)
19779 }
19780
19781 fn handle_focus_out(
19782 &mut self,
19783 event: FocusOutEvent,
19784 _window: &mut Window,
19785 cx: &mut Context<Self>,
19786 ) {
19787 if event.blurred != self.focus_handle {
19788 self.last_focused_descendant = Some(event.blurred);
19789 }
19790 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19791 }
19792
19793 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19794 self.blink_manager.update(cx, BlinkManager::disable);
19795 self.buffer
19796 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19797
19798 if let Some(blame) = self.blame.as_ref() {
19799 blame.update(cx, GitBlame::blur)
19800 }
19801 if !self.hover_state.focused(window, cx) {
19802 hide_hover(self, cx);
19803 }
19804 if !self
19805 .context_menu
19806 .borrow()
19807 .as_ref()
19808 .is_some_and(|context_menu| context_menu.focused(window, cx))
19809 {
19810 self.hide_context_menu(window, cx);
19811 }
19812 self.discard_inline_completion(false, cx);
19813 cx.emit(EditorEvent::Blurred);
19814 cx.notify();
19815 }
19816
19817 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19818 let mut pending: String = window
19819 .pending_input_keystrokes()
19820 .into_iter()
19821 .flatten()
19822 .filter_map(|keystroke| {
19823 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19824 keystroke.key_char.clone()
19825 } else {
19826 None
19827 }
19828 })
19829 .collect();
19830
19831 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19832 pending = "".to_string();
19833 }
19834
19835 let existing_pending = self
19836 .text_highlights::<PendingInput>(cx)
19837 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19838 if existing_pending.is_none() && pending.is_empty() {
19839 return;
19840 }
19841 let transaction =
19842 self.transact(window, cx, |this, window, cx| {
19843 let selections = this.selections.all::<usize>(cx);
19844 let edits = selections
19845 .iter()
19846 .map(|selection| (selection.end..selection.end, pending.clone()));
19847 this.edit(edits, cx);
19848 this.change_selections(None, window, cx, |s| {
19849 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19850 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19851 }));
19852 });
19853 if let Some(existing_ranges) = existing_pending {
19854 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19855 this.edit(edits, cx);
19856 }
19857 });
19858
19859 let snapshot = self.snapshot(window, cx);
19860 let ranges = self
19861 .selections
19862 .all::<usize>(cx)
19863 .into_iter()
19864 .map(|selection| {
19865 snapshot.buffer_snapshot.anchor_after(selection.end)
19866 ..snapshot
19867 .buffer_snapshot
19868 .anchor_before(selection.end + pending.len())
19869 })
19870 .collect();
19871
19872 if pending.is_empty() {
19873 self.clear_highlights::<PendingInput>(cx);
19874 } else {
19875 self.highlight_text::<PendingInput>(
19876 ranges,
19877 HighlightStyle {
19878 underline: Some(UnderlineStyle {
19879 thickness: px(1.),
19880 color: None,
19881 wavy: false,
19882 }),
19883 ..Default::default()
19884 },
19885 cx,
19886 );
19887 }
19888
19889 self.ime_transaction = self.ime_transaction.or(transaction);
19890 if let Some(transaction) = self.ime_transaction {
19891 self.buffer.update(cx, |buffer, cx| {
19892 buffer.group_until_transaction(transaction, cx);
19893 });
19894 }
19895
19896 if self.text_highlights::<PendingInput>(cx).is_none() {
19897 self.ime_transaction.take();
19898 }
19899 }
19900
19901 pub fn register_action_renderer(
19902 &mut self,
19903 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19904 ) -> Subscription {
19905 let id = self.next_editor_action_id.post_inc();
19906 self.editor_actions
19907 .borrow_mut()
19908 .insert(id, Box::new(listener));
19909
19910 let editor_actions = self.editor_actions.clone();
19911 Subscription::new(move || {
19912 editor_actions.borrow_mut().remove(&id);
19913 })
19914 }
19915
19916 pub fn register_action<A: Action>(
19917 &mut self,
19918 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19919 ) -> Subscription {
19920 let id = self.next_editor_action_id.post_inc();
19921 let listener = Arc::new(listener);
19922 self.editor_actions.borrow_mut().insert(
19923 id,
19924 Box::new(move |_, window, _| {
19925 let listener = listener.clone();
19926 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19927 let action = action.downcast_ref().unwrap();
19928 if phase == DispatchPhase::Bubble {
19929 listener(action, window, cx)
19930 }
19931 })
19932 }),
19933 );
19934
19935 let editor_actions = self.editor_actions.clone();
19936 Subscription::new(move || {
19937 editor_actions.borrow_mut().remove(&id);
19938 })
19939 }
19940
19941 pub fn file_header_size(&self) -> u32 {
19942 FILE_HEADER_HEIGHT
19943 }
19944
19945 pub fn restore(
19946 &mut self,
19947 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19948 window: &mut Window,
19949 cx: &mut Context<Self>,
19950 ) {
19951 let workspace = self.workspace();
19952 let project = self.project.as_ref();
19953 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19954 let mut tasks = Vec::new();
19955 for (buffer_id, changes) in revert_changes {
19956 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19957 buffer.update(cx, |buffer, cx| {
19958 buffer.edit(
19959 changes
19960 .into_iter()
19961 .map(|(range, text)| (range, text.to_string())),
19962 None,
19963 cx,
19964 );
19965 });
19966
19967 if let Some(project) =
19968 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19969 {
19970 project.update(cx, |project, cx| {
19971 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19972 })
19973 }
19974 }
19975 }
19976 tasks
19977 });
19978 cx.spawn_in(window, async move |_, cx| {
19979 for (buffer, task) in save_tasks {
19980 let result = task.await;
19981 if result.is_err() {
19982 let Some(path) = buffer
19983 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19984 .ok()
19985 else {
19986 continue;
19987 };
19988 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19989 let Some(task) = cx
19990 .update_window_entity(&workspace, |workspace, window, cx| {
19991 workspace
19992 .open_path_preview(path, None, false, false, false, window, cx)
19993 })
19994 .ok()
19995 else {
19996 continue;
19997 };
19998 task.await.log_err();
19999 }
20000 }
20001 }
20002 })
20003 .detach();
20004 self.change_selections(None, window, cx, |selections| selections.refresh());
20005 }
20006
20007 pub fn to_pixel_point(
20008 &self,
20009 source: multi_buffer::Anchor,
20010 editor_snapshot: &EditorSnapshot,
20011 window: &mut Window,
20012 ) -> Option<gpui::Point<Pixels>> {
20013 let source_point = source.to_display_point(editor_snapshot);
20014 self.display_to_pixel_point(source_point, editor_snapshot, window)
20015 }
20016
20017 pub fn display_to_pixel_point(
20018 &self,
20019 source: DisplayPoint,
20020 editor_snapshot: &EditorSnapshot,
20021 window: &mut Window,
20022 ) -> Option<gpui::Point<Pixels>> {
20023 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20024 let text_layout_details = self.text_layout_details(window);
20025 let scroll_top = text_layout_details
20026 .scroll_anchor
20027 .scroll_position(editor_snapshot)
20028 .y;
20029
20030 if source.row().as_f32() < scroll_top.floor() {
20031 return None;
20032 }
20033 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20034 let source_y = line_height * (source.row().as_f32() - scroll_top);
20035 Some(gpui::Point::new(source_x, source_y))
20036 }
20037
20038 pub fn has_visible_completions_menu(&self) -> bool {
20039 !self.edit_prediction_preview_is_active()
20040 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20041 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20042 })
20043 }
20044
20045 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20046 if self.mode.is_minimap() {
20047 return;
20048 }
20049 self.addons
20050 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20051 }
20052
20053 pub fn unregister_addon<T: Addon>(&mut self) {
20054 self.addons.remove(&std::any::TypeId::of::<T>());
20055 }
20056
20057 pub fn addon<T: Addon>(&self) -> Option<&T> {
20058 let type_id = std::any::TypeId::of::<T>();
20059 self.addons
20060 .get(&type_id)
20061 .and_then(|item| item.to_any().downcast_ref::<T>())
20062 }
20063
20064 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20065 let type_id = std::any::TypeId::of::<T>();
20066 self.addons
20067 .get_mut(&type_id)
20068 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20069 }
20070
20071 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20072 let text_layout_details = self.text_layout_details(window);
20073 let style = &text_layout_details.editor_style;
20074 let font_id = window.text_system().resolve_font(&style.text.font());
20075 let font_size = style.text.font_size.to_pixels(window.rem_size());
20076 let line_height = style.text.line_height_in_pixels(window.rem_size());
20077 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20078
20079 gpui::Size::new(em_width, line_height)
20080 }
20081
20082 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20083 self.load_diff_task.clone()
20084 }
20085
20086 fn read_metadata_from_db(
20087 &mut self,
20088 item_id: u64,
20089 workspace_id: WorkspaceId,
20090 window: &mut Window,
20091 cx: &mut Context<Editor>,
20092 ) {
20093 if self.is_singleton(cx)
20094 && !self.mode.is_minimap()
20095 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20096 {
20097 let buffer_snapshot = OnceCell::new();
20098
20099 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20100 if !folds.is_empty() {
20101 let snapshot =
20102 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20103 self.fold_ranges(
20104 folds
20105 .into_iter()
20106 .map(|(start, end)| {
20107 snapshot.clip_offset(start, Bias::Left)
20108 ..snapshot.clip_offset(end, Bias::Right)
20109 })
20110 .collect(),
20111 false,
20112 window,
20113 cx,
20114 );
20115 }
20116 }
20117
20118 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20119 if !selections.is_empty() {
20120 let snapshot =
20121 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20122 // skip adding the initial selection to selection history
20123 self.selection_history.mode = SelectionHistoryMode::Skipping;
20124 self.change_selections(None, window, cx, |s| {
20125 s.select_ranges(selections.into_iter().map(|(start, end)| {
20126 snapshot.clip_offset(start, Bias::Left)
20127 ..snapshot.clip_offset(end, Bias::Right)
20128 }));
20129 });
20130 self.selection_history.mode = SelectionHistoryMode::Normal;
20131 }
20132 };
20133 }
20134
20135 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20136 }
20137}
20138
20139fn vim_enabled(cx: &App) -> bool {
20140 cx.global::<SettingsStore>()
20141 .raw_user_settings()
20142 .get("vim_mode")
20143 == Some(&serde_json::Value::Bool(true))
20144}
20145
20146fn process_completion_for_edit(
20147 completion: &Completion,
20148 intent: CompletionIntent,
20149 buffer: &Entity<Buffer>,
20150 cursor_position: &text::Anchor,
20151 cx: &mut Context<Editor>,
20152) -> CompletionEdit {
20153 let buffer = buffer.read(cx);
20154 let buffer_snapshot = buffer.snapshot();
20155 let (snippet, new_text) = if completion.is_snippet() {
20156 // Workaround for typescript language server issues so that methods don't expand within
20157 // strings and functions with type expressions. The previous point is used because the query
20158 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20159 let mut snippet_source = completion.new_text.clone();
20160 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20161 previous_point.column = previous_point.column.saturating_sub(1);
20162 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20163 if scope.prefers_label_for_snippet_in_completion() {
20164 if let Some(label) = completion.label() {
20165 if matches!(
20166 completion.kind(),
20167 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20168 ) {
20169 snippet_source = label;
20170 }
20171 }
20172 }
20173 }
20174 match Snippet::parse(&snippet_source).log_err() {
20175 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20176 None => (None, completion.new_text.clone()),
20177 }
20178 } else {
20179 (None, completion.new_text.clone())
20180 };
20181
20182 let mut range_to_replace = {
20183 let replace_range = &completion.replace_range;
20184 if let CompletionSource::Lsp {
20185 insert_range: Some(insert_range),
20186 ..
20187 } = &completion.source
20188 {
20189 debug_assert_eq!(
20190 insert_range.start, replace_range.start,
20191 "insert_range and replace_range should start at the same position"
20192 );
20193 debug_assert!(
20194 insert_range
20195 .start
20196 .cmp(&cursor_position, &buffer_snapshot)
20197 .is_le(),
20198 "insert_range should start before or at cursor position"
20199 );
20200 debug_assert!(
20201 replace_range
20202 .start
20203 .cmp(&cursor_position, &buffer_snapshot)
20204 .is_le(),
20205 "replace_range should start before or at cursor position"
20206 );
20207 debug_assert!(
20208 insert_range
20209 .end
20210 .cmp(&cursor_position, &buffer_snapshot)
20211 .is_le(),
20212 "insert_range should end before or at cursor position"
20213 );
20214
20215 let should_replace = match intent {
20216 CompletionIntent::CompleteWithInsert => false,
20217 CompletionIntent::CompleteWithReplace => true,
20218 CompletionIntent::Complete | CompletionIntent::Compose => {
20219 let insert_mode =
20220 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20221 .completions
20222 .lsp_insert_mode;
20223 match insert_mode {
20224 LspInsertMode::Insert => false,
20225 LspInsertMode::Replace => true,
20226 LspInsertMode::ReplaceSubsequence => {
20227 let mut text_to_replace = buffer.chars_for_range(
20228 buffer.anchor_before(replace_range.start)
20229 ..buffer.anchor_after(replace_range.end),
20230 );
20231 let mut current_needle = text_to_replace.next();
20232 for haystack_ch in completion.label.text.chars() {
20233 if let Some(needle_ch) = current_needle {
20234 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20235 current_needle = text_to_replace.next();
20236 }
20237 }
20238 }
20239 current_needle.is_none()
20240 }
20241 LspInsertMode::ReplaceSuffix => {
20242 if replace_range
20243 .end
20244 .cmp(&cursor_position, &buffer_snapshot)
20245 .is_gt()
20246 {
20247 let range_after_cursor = *cursor_position..replace_range.end;
20248 let text_after_cursor = buffer
20249 .text_for_range(
20250 buffer.anchor_before(range_after_cursor.start)
20251 ..buffer.anchor_after(range_after_cursor.end),
20252 )
20253 .collect::<String>()
20254 .to_ascii_lowercase();
20255 completion
20256 .label
20257 .text
20258 .to_ascii_lowercase()
20259 .ends_with(&text_after_cursor)
20260 } else {
20261 true
20262 }
20263 }
20264 }
20265 }
20266 };
20267
20268 if should_replace {
20269 replace_range.clone()
20270 } else {
20271 insert_range.clone()
20272 }
20273 } else {
20274 replace_range.clone()
20275 }
20276 };
20277
20278 if range_to_replace
20279 .end
20280 .cmp(&cursor_position, &buffer_snapshot)
20281 .is_lt()
20282 {
20283 range_to_replace.end = *cursor_position;
20284 }
20285
20286 CompletionEdit {
20287 new_text,
20288 replace_range: range_to_replace.to_offset(&buffer),
20289 snippet,
20290 }
20291}
20292
20293struct CompletionEdit {
20294 new_text: String,
20295 replace_range: Range<usize>,
20296 snippet: Option<Snippet>,
20297}
20298
20299fn insert_extra_newline_brackets(
20300 buffer: &MultiBufferSnapshot,
20301 range: Range<usize>,
20302 language: &language::LanguageScope,
20303) -> bool {
20304 let leading_whitespace_len = buffer
20305 .reversed_chars_at(range.start)
20306 .take_while(|c| c.is_whitespace() && *c != '\n')
20307 .map(|c| c.len_utf8())
20308 .sum::<usize>();
20309 let trailing_whitespace_len = buffer
20310 .chars_at(range.end)
20311 .take_while(|c| c.is_whitespace() && *c != '\n')
20312 .map(|c| c.len_utf8())
20313 .sum::<usize>();
20314 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20315
20316 language.brackets().any(|(pair, enabled)| {
20317 let pair_start = pair.start.trim_end();
20318 let pair_end = pair.end.trim_start();
20319
20320 enabled
20321 && pair.newline
20322 && buffer.contains_str_at(range.end, pair_end)
20323 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20324 })
20325}
20326
20327fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20328 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20329 [(buffer, range, _)] => (*buffer, range.clone()),
20330 _ => return false,
20331 };
20332 let pair = {
20333 let mut result: Option<BracketMatch> = None;
20334
20335 for pair in buffer
20336 .all_bracket_ranges(range.clone())
20337 .filter(move |pair| {
20338 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20339 })
20340 {
20341 let len = pair.close_range.end - pair.open_range.start;
20342
20343 if let Some(existing) = &result {
20344 let existing_len = existing.close_range.end - existing.open_range.start;
20345 if len > existing_len {
20346 continue;
20347 }
20348 }
20349
20350 result = Some(pair);
20351 }
20352
20353 result
20354 };
20355 let Some(pair) = pair else {
20356 return false;
20357 };
20358 pair.newline_only
20359 && buffer
20360 .chars_for_range(pair.open_range.end..range.start)
20361 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20362 .all(|c| c.is_whitespace() && c != '\n')
20363}
20364
20365fn update_uncommitted_diff_for_buffer(
20366 editor: Entity<Editor>,
20367 project: &Entity<Project>,
20368 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20369 buffer: Entity<MultiBuffer>,
20370 cx: &mut App,
20371) -> Task<()> {
20372 let mut tasks = Vec::new();
20373 project.update(cx, |project, cx| {
20374 for buffer in buffers {
20375 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20376 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20377 }
20378 }
20379 });
20380 cx.spawn(async move |cx| {
20381 let diffs = future::join_all(tasks).await;
20382 if editor
20383 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20384 .unwrap_or(false)
20385 {
20386 return;
20387 }
20388
20389 buffer
20390 .update(cx, |buffer, cx| {
20391 for diff in diffs.into_iter().flatten() {
20392 buffer.add_diff(diff, cx);
20393 }
20394 })
20395 .ok();
20396 })
20397}
20398
20399fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20400 let tab_size = tab_size.get() as usize;
20401 let mut width = offset;
20402
20403 for ch in text.chars() {
20404 width += if ch == '\t' {
20405 tab_size - (width % tab_size)
20406 } else {
20407 1
20408 };
20409 }
20410
20411 width - offset
20412}
20413
20414#[cfg(test)]
20415mod tests {
20416 use super::*;
20417
20418 #[test]
20419 fn test_string_size_with_expanded_tabs() {
20420 let nz = |val| NonZeroU32::new(val).unwrap();
20421 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20422 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20423 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20424 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20425 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20426 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20427 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20428 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20429 }
20430}
20431
20432/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20433struct WordBreakingTokenizer<'a> {
20434 input: &'a str,
20435}
20436
20437impl<'a> WordBreakingTokenizer<'a> {
20438 fn new(input: &'a str) -> Self {
20439 Self { input }
20440 }
20441}
20442
20443fn is_char_ideographic(ch: char) -> bool {
20444 use unicode_script::Script::*;
20445 use unicode_script::UnicodeScript;
20446 matches!(ch.script(), Han | Tangut | Yi)
20447}
20448
20449fn is_grapheme_ideographic(text: &str) -> bool {
20450 text.chars().any(is_char_ideographic)
20451}
20452
20453fn is_grapheme_whitespace(text: &str) -> bool {
20454 text.chars().any(|x| x.is_whitespace())
20455}
20456
20457fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20458 text.chars().next().map_or(false, |ch| {
20459 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20460 })
20461}
20462
20463#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20464enum WordBreakToken<'a> {
20465 Word { token: &'a str, grapheme_len: usize },
20466 InlineWhitespace { token: &'a str, grapheme_len: usize },
20467 Newline,
20468}
20469
20470impl<'a> Iterator for WordBreakingTokenizer<'a> {
20471 /// Yields a span, the count of graphemes in the token, and whether it was
20472 /// whitespace. Note that it also breaks at word boundaries.
20473 type Item = WordBreakToken<'a>;
20474
20475 fn next(&mut self) -> Option<Self::Item> {
20476 use unicode_segmentation::UnicodeSegmentation;
20477 if self.input.is_empty() {
20478 return None;
20479 }
20480
20481 let mut iter = self.input.graphemes(true).peekable();
20482 let mut offset = 0;
20483 let mut grapheme_len = 0;
20484 if let Some(first_grapheme) = iter.next() {
20485 let is_newline = first_grapheme == "\n";
20486 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20487 offset += first_grapheme.len();
20488 grapheme_len += 1;
20489 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20490 if let Some(grapheme) = iter.peek().copied() {
20491 if should_stay_with_preceding_ideograph(grapheme) {
20492 offset += grapheme.len();
20493 grapheme_len += 1;
20494 }
20495 }
20496 } else {
20497 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20498 let mut next_word_bound = words.peek().copied();
20499 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20500 next_word_bound = words.next();
20501 }
20502 while let Some(grapheme) = iter.peek().copied() {
20503 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20504 break;
20505 };
20506 if is_grapheme_whitespace(grapheme) != is_whitespace
20507 || (grapheme == "\n") != is_newline
20508 {
20509 break;
20510 };
20511 offset += grapheme.len();
20512 grapheme_len += 1;
20513 iter.next();
20514 }
20515 }
20516 let token = &self.input[..offset];
20517 self.input = &self.input[offset..];
20518 if token == "\n" {
20519 Some(WordBreakToken::Newline)
20520 } else if is_whitespace {
20521 Some(WordBreakToken::InlineWhitespace {
20522 token,
20523 grapheme_len,
20524 })
20525 } else {
20526 Some(WordBreakToken::Word {
20527 token,
20528 grapheme_len,
20529 })
20530 }
20531 } else {
20532 None
20533 }
20534 }
20535}
20536
20537#[test]
20538fn test_word_breaking_tokenizer() {
20539 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20540 ("", &[]),
20541 (" ", &[whitespace(" ", 2)]),
20542 ("Ʒ", &[word("Ʒ", 1)]),
20543 ("Ǽ", &[word("Ǽ", 1)]),
20544 ("⋑", &[word("⋑", 1)]),
20545 ("⋑⋑", &[word("⋑⋑", 2)]),
20546 (
20547 "原理,进而",
20548 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20549 ),
20550 (
20551 "hello world",
20552 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20553 ),
20554 (
20555 "hello, world",
20556 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20557 ),
20558 (
20559 " hello world",
20560 &[
20561 whitespace(" ", 2),
20562 word("hello", 5),
20563 whitespace(" ", 1),
20564 word("world", 5),
20565 ],
20566 ),
20567 (
20568 "这是什么 \n 钢笔",
20569 &[
20570 word("这", 1),
20571 word("是", 1),
20572 word("什", 1),
20573 word("么", 1),
20574 whitespace(" ", 1),
20575 newline(),
20576 whitespace(" ", 1),
20577 word("钢", 1),
20578 word("笔", 1),
20579 ],
20580 ),
20581 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20582 ];
20583
20584 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20585 WordBreakToken::Word {
20586 token,
20587 grapheme_len,
20588 }
20589 }
20590
20591 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20592 WordBreakToken::InlineWhitespace {
20593 token,
20594 grapheme_len,
20595 }
20596 }
20597
20598 fn newline() -> WordBreakToken<'static> {
20599 WordBreakToken::Newline
20600 }
20601
20602 for (input, result) in tests {
20603 assert_eq!(
20604 WordBreakingTokenizer::new(input)
20605 .collect::<Vec<_>>()
20606 .as_slice(),
20607 *result,
20608 );
20609 }
20610}
20611
20612fn wrap_with_prefix(
20613 line_prefix: String,
20614 unwrapped_text: String,
20615 wrap_column: usize,
20616 tab_size: NonZeroU32,
20617 preserve_existing_whitespace: bool,
20618) -> String {
20619 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20620 let mut wrapped_text = String::new();
20621 let mut current_line = line_prefix.clone();
20622
20623 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20624 let mut current_line_len = line_prefix_len;
20625 let mut in_whitespace = false;
20626 for token in tokenizer {
20627 let have_preceding_whitespace = in_whitespace;
20628 match token {
20629 WordBreakToken::Word {
20630 token,
20631 grapheme_len,
20632 } => {
20633 in_whitespace = false;
20634 if current_line_len + grapheme_len > wrap_column
20635 && current_line_len != line_prefix_len
20636 {
20637 wrapped_text.push_str(current_line.trim_end());
20638 wrapped_text.push('\n');
20639 current_line.truncate(line_prefix.len());
20640 current_line_len = line_prefix_len;
20641 }
20642 current_line.push_str(token);
20643 current_line_len += grapheme_len;
20644 }
20645 WordBreakToken::InlineWhitespace {
20646 mut token,
20647 mut grapheme_len,
20648 } => {
20649 in_whitespace = true;
20650 if have_preceding_whitespace && !preserve_existing_whitespace {
20651 continue;
20652 }
20653 if !preserve_existing_whitespace {
20654 token = " ";
20655 grapheme_len = 1;
20656 }
20657 if current_line_len + grapheme_len > wrap_column {
20658 wrapped_text.push_str(current_line.trim_end());
20659 wrapped_text.push('\n');
20660 current_line.truncate(line_prefix.len());
20661 current_line_len = line_prefix_len;
20662 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20663 current_line.push_str(token);
20664 current_line_len += grapheme_len;
20665 }
20666 }
20667 WordBreakToken::Newline => {
20668 in_whitespace = true;
20669 if preserve_existing_whitespace {
20670 wrapped_text.push_str(current_line.trim_end());
20671 wrapped_text.push('\n');
20672 current_line.truncate(line_prefix.len());
20673 current_line_len = line_prefix_len;
20674 } else if have_preceding_whitespace {
20675 continue;
20676 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20677 {
20678 wrapped_text.push_str(current_line.trim_end());
20679 wrapped_text.push('\n');
20680 current_line.truncate(line_prefix.len());
20681 current_line_len = line_prefix_len;
20682 } else if current_line_len != line_prefix_len {
20683 current_line.push(' ');
20684 current_line_len += 1;
20685 }
20686 }
20687 }
20688 }
20689
20690 if !current_line.is_empty() {
20691 wrapped_text.push_str(¤t_line);
20692 }
20693 wrapped_text
20694}
20695
20696#[test]
20697fn test_wrap_with_prefix() {
20698 assert_eq!(
20699 wrap_with_prefix(
20700 "# ".to_string(),
20701 "abcdefg".to_string(),
20702 4,
20703 NonZeroU32::new(4).unwrap(),
20704 false,
20705 ),
20706 "# abcdefg"
20707 );
20708 assert_eq!(
20709 wrap_with_prefix(
20710 "".to_string(),
20711 "\thello world".to_string(),
20712 8,
20713 NonZeroU32::new(4).unwrap(),
20714 false,
20715 ),
20716 "hello\nworld"
20717 );
20718 assert_eq!(
20719 wrap_with_prefix(
20720 "// ".to_string(),
20721 "xx \nyy zz aa bb cc".to_string(),
20722 12,
20723 NonZeroU32::new(4).unwrap(),
20724 false,
20725 ),
20726 "// xx yy zz\n// aa bb cc"
20727 );
20728 assert_eq!(
20729 wrap_with_prefix(
20730 String::new(),
20731 "这是什么 \n 钢笔".to_string(),
20732 3,
20733 NonZeroU32::new(4).unwrap(),
20734 false,
20735 ),
20736 "这是什\n么 钢\n笔"
20737 );
20738}
20739
20740pub trait CollaborationHub {
20741 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20742 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20743 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20744}
20745
20746impl CollaborationHub for Entity<Project> {
20747 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20748 self.read(cx).collaborators()
20749 }
20750
20751 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20752 self.read(cx).user_store().read(cx).participant_indices()
20753 }
20754
20755 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20756 let this = self.read(cx);
20757 let user_ids = this.collaborators().values().map(|c| c.user_id);
20758 this.user_store().read(cx).participant_names(user_ids, cx)
20759 }
20760}
20761
20762pub trait SemanticsProvider {
20763 fn hover(
20764 &self,
20765 buffer: &Entity<Buffer>,
20766 position: text::Anchor,
20767 cx: &mut App,
20768 ) -> Option<Task<Vec<project::Hover>>>;
20769
20770 fn inline_values(
20771 &self,
20772 buffer_handle: Entity<Buffer>,
20773 range: Range<text::Anchor>,
20774 cx: &mut App,
20775 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20776
20777 fn inlay_hints(
20778 &self,
20779 buffer_handle: Entity<Buffer>,
20780 range: Range<text::Anchor>,
20781 cx: &mut App,
20782 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20783
20784 fn resolve_inlay_hint(
20785 &self,
20786 hint: InlayHint,
20787 buffer_handle: Entity<Buffer>,
20788 server_id: LanguageServerId,
20789 cx: &mut App,
20790 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20791
20792 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20793
20794 fn document_highlights(
20795 &self,
20796 buffer: &Entity<Buffer>,
20797 position: text::Anchor,
20798 cx: &mut App,
20799 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20800
20801 fn definitions(
20802 &self,
20803 buffer: &Entity<Buffer>,
20804 position: text::Anchor,
20805 kind: GotoDefinitionKind,
20806 cx: &mut App,
20807 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20808
20809 fn range_for_rename(
20810 &self,
20811 buffer: &Entity<Buffer>,
20812 position: text::Anchor,
20813 cx: &mut App,
20814 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20815
20816 fn perform_rename(
20817 &self,
20818 buffer: &Entity<Buffer>,
20819 position: text::Anchor,
20820 new_name: String,
20821 cx: &mut App,
20822 ) -> Option<Task<Result<ProjectTransaction>>>;
20823
20824 fn pull_diagnostics_for_buffer(
20825 &self,
20826 buffer: Entity<Buffer>,
20827 cx: &mut App,
20828 ) -> Task<anyhow::Result<()>>;
20829}
20830
20831pub trait CompletionProvider {
20832 fn completions(
20833 &self,
20834 excerpt_id: ExcerptId,
20835 buffer: &Entity<Buffer>,
20836 buffer_position: text::Anchor,
20837 trigger: CompletionContext,
20838 window: &mut Window,
20839 cx: &mut Context<Editor>,
20840 ) -> Task<Result<Vec<CompletionResponse>>>;
20841
20842 fn resolve_completions(
20843 &self,
20844 _buffer: Entity<Buffer>,
20845 _completion_indices: Vec<usize>,
20846 _completions: Rc<RefCell<Box<[Completion]>>>,
20847 _cx: &mut Context<Editor>,
20848 ) -> Task<Result<bool>> {
20849 Task::ready(Ok(false))
20850 }
20851
20852 fn apply_additional_edits_for_completion(
20853 &self,
20854 _buffer: Entity<Buffer>,
20855 _completions: Rc<RefCell<Box<[Completion]>>>,
20856 _completion_index: usize,
20857 _push_to_history: bool,
20858 _cx: &mut Context<Editor>,
20859 ) -> Task<Result<Option<language::Transaction>>> {
20860 Task::ready(Ok(None))
20861 }
20862
20863 fn is_completion_trigger(
20864 &self,
20865 buffer: &Entity<Buffer>,
20866 position: language::Anchor,
20867 text: &str,
20868 trigger_in_words: bool,
20869 menu_is_open: bool,
20870 cx: &mut Context<Editor>,
20871 ) -> bool;
20872
20873 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20874
20875 fn sort_completions(&self) -> bool {
20876 true
20877 }
20878
20879 fn filter_completions(&self) -> bool {
20880 true
20881 }
20882}
20883
20884pub trait CodeActionProvider {
20885 fn id(&self) -> Arc<str>;
20886
20887 fn code_actions(
20888 &self,
20889 buffer: &Entity<Buffer>,
20890 range: Range<text::Anchor>,
20891 window: &mut Window,
20892 cx: &mut App,
20893 ) -> Task<Result<Vec<CodeAction>>>;
20894
20895 fn apply_code_action(
20896 &self,
20897 buffer_handle: Entity<Buffer>,
20898 action: CodeAction,
20899 excerpt_id: ExcerptId,
20900 push_to_history: bool,
20901 window: &mut Window,
20902 cx: &mut App,
20903 ) -> Task<Result<ProjectTransaction>>;
20904}
20905
20906impl CodeActionProvider for Entity<Project> {
20907 fn id(&self) -> Arc<str> {
20908 "project".into()
20909 }
20910
20911 fn code_actions(
20912 &self,
20913 buffer: &Entity<Buffer>,
20914 range: Range<text::Anchor>,
20915 _window: &mut Window,
20916 cx: &mut App,
20917 ) -> Task<Result<Vec<CodeAction>>> {
20918 self.update(cx, |project, cx| {
20919 let code_lens = project.code_lens(buffer, range.clone(), cx);
20920 let code_actions = project.code_actions(buffer, range, None, cx);
20921 cx.background_spawn(async move {
20922 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20923 Ok(code_lens
20924 .context("code lens fetch")?
20925 .into_iter()
20926 .chain(code_actions.context("code action fetch")?)
20927 .collect())
20928 })
20929 })
20930 }
20931
20932 fn apply_code_action(
20933 &self,
20934 buffer_handle: Entity<Buffer>,
20935 action: CodeAction,
20936 _excerpt_id: ExcerptId,
20937 push_to_history: bool,
20938 _window: &mut Window,
20939 cx: &mut App,
20940 ) -> Task<Result<ProjectTransaction>> {
20941 self.update(cx, |project, cx| {
20942 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20943 })
20944 }
20945}
20946
20947fn snippet_completions(
20948 project: &Project,
20949 buffer: &Entity<Buffer>,
20950 buffer_position: text::Anchor,
20951 cx: &mut App,
20952) -> Task<Result<CompletionResponse>> {
20953 let languages = buffer.read(cx).languages_at(buffer_position);
20954 let snippet_store = project.snippets().read(cx);
20955
20956 let scopes: Vec<_> = languages
20957 .iter()
20958 .filter_map(|language| {
20959 let language_name = language.lsp_id();
20960 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20961
20962 if snippets.is_empty() {
20963 None
20964 } else {
20965 Some((language.default_scope(), snippets))
20966 }
20967 })
20968 .collect();
20969
20970 if scopes.is_empty() {
20971 return Task::ready(Ok(CompletionResponse {
20972 completions: vec![],
20973 is_incomplete: false,
20974 }));
20975 }
20976
20977 let snapshot = buffer.read(cx).text_snapshot();
20978 let chars: String = snapshot
20979 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20980 .collect();
20981 let executor = cx.background_executor().clone();
20982
20983 cx.background_spawn(async move {
20984 let mut is_incomplete = false;
20985 let mut completions: Vec<Completion> = Vec::new();
20986 for (scope, snippets) in scopes.into_iter() {
20987 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20988 let mut last_word = chars
20989 .chars()
20990 .take_while(|c| classifier.is_word(*c))
20991 .collect::<String>();
20992 last_word = last_word.chars().rev().collect();
20993
20994 if last_word.is_empty() {
20995 return Ok(CompletionResponse {
20996 completions: vec![],
20997 is_incomplete: true,
20998 });
20999 }
21000
21001 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21002 let to_lsp = |point: &text::Anchor| {
21003 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21004 point_to_lsp(end)
21005 };
21006 let lsp_end = to_lsp(&buffer_position);
21007
21008 let candidates = snippets
21009 .iter()
21010 .enumerate()
21011 .flat_map(|(ix, snippet)| {
21012 snippet
21013 .prefix
21014 .iter()
21015 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21016 })
21017 .collect::<Vec<StringMatchCandidate>>();
21018
21019 const MAX_RESULTS: usize = 100;
21020 let mut matches = fuzzy::match_strings(
21021 &candidates,
21022 &last_word,
21023 last_word.chars().any(|c| c.is_uppercase()),
21024 MAX_RESULTS,
21025 &Default::default(),
21026 executor.clone(),
21027 )
21028 .await;
21029
21030 if matches.len() >= MAX_RESULTS {
21031 is_incomplete = true;
21032 }
21033
21034 // Remove all candidates where the query's start does not match the start of any word in the candidate
21035 if let Some(query_start) = last_word.chars().next() {
21036 matches.retain(|string_match| {
21037 split_words(&string_match.string).any(|word| {
21038 // Check that the first codepoint of the word as lowercase matches the first
21039 // codepoint of the query as lowercase
21040 word.chars()
21041 .flat_map(|codepoint| codepoint.to_lowercase())
21042 .zip(query_start.to_lowercase())
21043 .all(|(word_cp, query_cp)| word_cp == query_cp)
21044 })
21045 });
21046 }
21047
21048 let matched_strings = matches
21049 .into_iter()
21050 .map(|m| m.string)
21051 .collect::<HashSet<_>>();
21052
21053 completions.extend(snippets.iter().filter_map(|snippet| {
21054 let matching_prefix = snippet
21055 .prefix
21056 .iter()
21057 .find(|prefix| matched_strings.contains(*prefix))?;
21058 let start = as_offset - last_word.len();
21059 let start = snapshot.anchor_before(start);
21060 let range = start..buffer_position;
21061 let lsp_start = to_lsp(&start);
21062 let lsp_range = lsp::Range {
21063 start: lsp_start,
21064 end: lsp_end,
21065 };
21066 Some(Completion {
21067 replace_range: range,
21068 new_text: snippet.body.clone(),
21069 source: CompletionSource::Lsp {
21070 insert_range: None,
21071 server_id: LanguageServerId(usize::MAX),
21072 resolved: true,
21073 lsp_completion: Box::new(lsp::CompletionItem {
21074 label: snippet.prefix.first().unwrap().clone(),
21075 kind: Some(CompletionItemKind::SNIPPET),
21076 label_details: snippet.description.as_ref().map(|description| {
21077 lsp::CompletionItemLabelDetails {
21078 detail: Some(description.clone()),
21079 description: None,
21080 }
21081 }),
21082 insert_text_format: Some(InsertTextFormat::SNIPPET),
21083 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21084 lsp::InsertReplaceEdit {
21085 new_text: snippet.body.clone(),
21086 insert: lsp_range,
21087 replace: lsp_range,
21088 },
21089 )),
21090 filter_text: Some(snippet.body.clone()),
21091 sort_text: Some(char::MAX.to_string()),
21092 ..lsp::CompletionItem::default()
21093 }),
21094 lsp_defaults: None,
21095 },
21096 label: CodeLabel {
21097 text: matching_prefix.clone(),
21098 runs: Vec::new(),
21099 filter_range: 0..matching_prefix.len(),
21100 },
21101 icon_path: None,
21102 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21103 single_line: snippet.name.clone().into(),
21104 plain_text: snippet
21105 .description
21106 .clone()
21107 .map(|description| description.into()),
21108 }),
21109 insert_text_mode: None,
21110 confirm: None,
21111 })
21112 }))
21113 }
21114
21115 Ok(CompletionResponse {
21116 completions,
21117 is_incomplete,
21118 })
21119 })
21120}
21121
21122impl CompletionProvider for Entity<Project> {
21123 fn completions(
21124 &self,
21125 _excerpt_id: ExcerptId,
21126 buffer: &Entity<Buffer>,
21127 buffer_position: text::Anchor,
21128 options: CompletionContext,
21129 _window: &mut Window,
21130 cx: &mut Context<Editor>,
21131 ) -> Task<Result<Vec<CompletionResponse>>> {
21132 self.update(cx, |project, cx| {
21133 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21134 let project_completions = project.completions(buffer, buffer_position, options, cx);
21135 cx.background_spawn(async move {
21136 let mut responses = project_completions.await?;
21137 let snippets = snippets.await?;
21138 if !snippets.completions.is_empty() {
21139 responses.push(snippets);
21140 }
21141 Ok(responses)
21142 })
21143 })
21144 }
21145
21146 fn resolve_completions(
21147 &self,
21148 buffer: Entity<Buffer>,
21149 completion_indices: Vec<usize>,
21150 completions: Rc<RefCell<Box<[Completion]>>>,
21151 cx: &mut Context<Editor>,
21152 ) -> Task<Result<bool>> {
21153 self.update(cx, |project, cx| {
21154 project.lsp_store().update(cx, |lsp_store, cx| {
21155 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21156 })
21157 })
21158 }
21159
21160 fn apply_additional_edits_for_completion(
21161 &self,
21162 buffer: Entity<Buffer>,
21163 completions: Rc<RefCell<Box<[Completion]>>>,
21164 completion_index: usize,
21165 push_to_history: bool,
21166 cx: &mut Context<Editor>,
21167 ) -> Task<Result<Option<language::Transaction>>> {
21168 self.update(cx, |project, cx| {
21169 project.lsp_store().update(cx, |lsp_store, cx| {
21170 lsp_store.apply_additional_edits_for_completion(
21171 buffer,
21172 completions,
21173 completion_index,
21174 push_to_history,
21175 cx,
21176 )
21177 })
21178 })
21179 }
21180
21181 fn is_completion_trigger(
21182 &self,
21183 buffer: &Entity<Buffer>,
21184 position: language::Anchor,
21185 text: &str,
21186 trigger_in_words: bool,
21187 menu_is_open: bool,
21188 cx: &mut Context<Editor>,
21189 ) -> bool {
21190 let mut chars = text.chars();
21191 let char = if let Some(char) = chars.next() {
21192 char
21193 } else {
21194 return false;
21195 };
21196 if chars.next().is_some() {
21197 return false;
21198 }
21199
21200 let buffer = buffer.read(cx);
21201 let snapshot = buffer.snapshot();
21202 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21203 return false;
21204 }
21205 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21206 if trigger_in_words && classifier.is_word(char) {
21207 return true;
21208 }
21209
21210 buffer.completion_triggers().contains(text)
21211 }
21212}
21213
21214impl SemanticsProvider for Entity<Project> {
21215 fn hover(
21216 &self,
21217 buffer: &Entity<Buffer>,
21218 position: text::Anchor,
21219 cx: &mut App,
21220 ) -> Option<Task<Vec<project::Hover>>> {
21221 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21222 }
21223
21224 fn document_highlights(
21225 &self,
21226 buffer: &Entity<Buffer>,
21227 position: text::Anchor,
21228 cx: &mut App,
21229 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21230 Some(self.update(cx, |project, cx| {
21231 project.document_highlights(buffer, position, cx)
21232 }))
21233 }
21234
21235 fn definitions(
21236 &self,
21237 buffer: &Entity<Buffer>,
21238 position: text::Anchor,
21239 kind: GotoDefinitionKind,
21240 cx: &mut App,
21241 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21242 Some(self.update(cx, |project, cx| match kind {
21243 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21244 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21245 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21246 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21247 }))
21248 }
21249
21250 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21251 // TODO: make this work for remote projects
21252 self.update(cx, |project, cx| {
21253 if project
21254 .active_debug_session(cx)
21255 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21256 {
21257 return true;
21258 }
21259
21260 buffer.update(cx, |buffer, cx| {
21261 project.any_language_server_supports_inlay_hints(buffer, cx)
21262 })
21263 })
21264 }
21265
21266 fn inline_values(
21267 &self,
21268 buffer_handle: Entity<Buffer>,
21269
21270 range: Range<text::Anchor>,
21271 cx: &mut App,
21272 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21273 self.update(cx, |project, cx| {
21274 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21275
21276 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21277 })
21278 }
21279
21280 fn inlay_hints(
21281 &self,
21282 buffer_handle: Entity<Buffer>,
21283 range: Range<text::Anchor>,
21284 cx: &mut App,
21285 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21286 Some(self.update(cx, |project, cx| {
21287 project.inlay_hints(buffer_handle, range, cx)
21288 }))
21289 }
21290
21291 fn resolve_inlay_hint(
21292 &self,
21293 hint: InlayHint,
21294 buffer_handle: Entity<Buffer>,
21295 server_id: LanguageServerId,
21296 cx: &mut App,
21297 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21298 Some(self.update(cx, |project, cx| {
21299 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21300 }))
21301 }
21302
21303 fn range_for_rename(
21304 &self,
21305 buffer: &Entity<Buffer>,
21306 position: text::Anchor,
21307 cx: &mut App,
21308 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21309 Some(self.update(cx, |project, cx| {
21310 let buffer = buffer.clone();
21311 let task = project.prepare_rename(buffer.clone(), position, cx);
21312 cx.spawn(async move |_, cx| {
21313 Ok(match task.await? {
21314 PrepareRenameResponse::Success(range) => Some(range),
21315 PrepareRenameResponse::InvalidPosition => None,
21316 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21317 // Fallback on using TreeSitter info to determine identifier range
21318 buffer.read_with(cx, |buffer, _| {
21319 let snapshot = buffer.snapshot();
21320 let (range, kind) = snapshot.surrounding_word(position);
21321 if kind != Some(CharKind::Word) {
21322 return None;
21323 }
21324 Some(
21325 snapshot.anchor_before(range.start)
21326 ..snapshot.anchor_after(range.end),
21327 )
21328 })?
21329 }
21330 })
21331 })
21332 }))
21333 }
21334
21335 fn perform_rename(
21336 &self,
21337 buffer: &Entity<Buffer>,
21338 position: text::Anchor,
21339 new_name: String,
21340 cx: &mut App,
21341 ) -> Option<Task<Result<ProjectTransaction>>> {
21342 Some(self.update(cx, |project, cx| {
21343 project.perform_rename(buffer.clone(), position, new_name, cx)
21344 }))
21345 }
21346
21347 fn pull_diagnostics_for_buffer(
21348 &self,
21349 buffer: Entity<Buffer>,
21350 cx: &mut App,
21351 ) -> Task<anyhow::Result<()>> {
21352 let diagnostics = self.update(cx, |project, cx| {
21353 project
21354 .lsp_store()
21355 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21356 });
21357 let project = self.clone();
21358 cx.spawn(async move |cx| {
21359 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21360 project.update(cx, |project, cx| {
21361 project.lsp_store().update(cx, |lsp_store, cx| {
21362 for diagnostics_set in diagnostics {
21363 let LspPullDiagnostics::Response {
21364 server_id,
21365 uri,
21366 diagnostics,
21367 } = diagnostics_set
21368 else {
21369 continue;
21370 };
21371
21372 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21373 let disk_based_sources = adapter
21374 .as_ref()
21375 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21376 .unwrap_or(&[]);
21377 match diagnostics {
21378 PulledDiagnostics::Unchanged { result_id } => {
21379 lsp_store
21380 .merge_diagnostics(
21381 server_id,
21382 lsp::PublishDiagnosticsParams {
21383 uri: uri.clone(),
21384 diagnostics: Vec::new(),
21385 version: None,
21386 },
21387 Some(result_id),
21388 DiagnosticSourceKind::Pulled,
21389 disk_based_sources,
21390 |_, _| true,
21391 cx,
21392 )
21393 .log_err();
21394 }
21395 PulledDiagnostics::Changed {
21396 diagnostics,
21397 result_id,
21398 } => {
21399 lsp_store
21400 .merge_diagnostics(
21401 server_id,
21402 lsp::PublishDiagnosticsParams {
21403 uri: uri.clone(),
21404 diagnostics,
21405 version: None,
21406 },
21407 result_id,
21408 DiagnosticSourceKind::Pulled,
21409 disk_based_sources,
21410 |old_diagnostic, _| match old_diagnostic.source_kind {
21411 DiagnosticSourceKind::Pulled => false,
21412 DiagnosticSourceKind::Other
21413 | DiagnosticSourceKind::Pushed => true,
21414 },
21415 cx,
21416 )
21417 .log_err();
21418 }
21419 }
21420 }
21421 })
21422 })
21423 })
21424 }
21425}
21426
21427fn inlay_hint_settings(
21428 location: Anchor,
21429 snapshot: &MultiBufferSnapshot,
21430 cx: &mut Context<Editor>,
21431) -> InlayHintSettings {
21432 let file = snapshot.file_at(location);
21433 let language = snapshot.language_at(location).map(|l| l.name());
21434 language_settings(language, file, cx).inlay_hints
21435}
21436
21437fn consume_contiguous_rows(
21438 contiguous_row_selections: &mut Vec<Selection<Point>>,
21439 selection: &Selection<Point>,
21440 display_map: &DisplaySnapshot,
21441 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21442) -> (MultiBufferRow, MultiBufferRow) {
21443 contiguous_row_selections.push(selection.clone());
21444 let start_row = MultiBufferRow(selection.start.row);
21445 let mut end_row = ending_row(selection, display_map);
21446
21447 while let Some(next_selection) = selections.peek() {
21448 if next_selection.start.row <= end_row.0 {
21449 end_row = ending_row(next_selection, display_map);
21450 contiguous_row_selections.push(selections.next().unwrap().clone());
21451 } else {
21452 break;
21453 }
21454 }
21455 (start_row, end_row)
21456}
21457
21458fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21459 if next_selection.end.column > 0 || next_selection.is_empty() {
21460 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21461 } else {
21462 MultiBufferRow(next_selection.end.row)
21463 }
21464}
21465
21466impl EditorSnapshot {
21467 pub fn remote_selections_in_range<'a>(
21468 &'a self,
21469 range: &'a Range<Anchor>,
21470 collaboration_hub: &dyn CollaborationHub,
21471 cx: &'a App,
21472 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21473 let participant_names = collaboration_hub.user_names(cx);
21474 let participant_indices = collaboration_hub.user_participant_indices(cx);
21475 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21476 let collaborators_by_replica_id = collaborators_by_peer_id
21477 .values()
21478 .map(|collaborator| (collaborator.replica_id, collaborator))
21479 .collect::<HashMap<_, _>>();
21480 self.buffer_snapshot
21481 .selections_in_range(range, false)
21482 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21483 if replica_id == AGENT_REPLICA_ID {
21484 Some(RemoteSelection {
21485 replica_id,
21486 selection,
21487 cursor_shape,
21488 line_mode,
21489 collaborator_id: CollaboratorId::Agent,
21490 user_name: Some("Agent".into()),
21491 color: cx.theme().players().agent(),
21492 })
21493 } else {
21494 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21495 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21496 let user_name = participant_names.get(&collaborator.user_id).cloned();
21497 Some(RemoteSelection {
21498 replica_id,
21499 selection,
21500 cursor_shape,
21501 line_mode,
21502 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21503 user_name,
21504 color: if let Some(index) = participant_index {
21505 cx.theme().players().color_for_participant(index.0)
21506 } else {
21507 cx.theme().players().absent()
21508 },
21509 })
21510 }
21511 })
21512 }
21513
21514 pub fn hunks_for_ranges(
21515 &self,
21516 ranges: impl IntoIterator<Item = Range<Point>>,
21517 ) -> Vec<MultiBufferDiffHunk> {
21518 let mut hunks = Vec::new();
21519 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21520 HashMap::default();
21521 for query_range in ranges {
21522 let query_rows =
21523 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21524 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21525 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21526 ) {
21527 // Include deleted hunks that are adjacent to the query range, because
21528 // otherwise they would be missed.
21529 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21530 if hunk.status().is_deleted() {
21531 intersects_range |= hunk.row_range.start == query_rows.end;
21532 intersects_range |= hunk.row_range.end == query_rows.start;
21533 }
21534 if intersects_range {
21535 if !processed_buffer_rows
21536 .entry(hunk.buffer_id)
21537 .or_default()
21538 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21539 {
21540 continue;
21541 }
21542 hunks.push(hunk);
21543 }
21544 }
21545 }
21546
21547 hunks
21548 }
21549
21550 fn display_diff_hunks_for_rows<'a>(
21551 &'a self,
21552 display_rows: Range<DisplayRow>,
21553 folded_buffers: &'a HashSet<BufferId>,
21554 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21555 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21556 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21557
21558 self.buffer_snapshot
21559 .diff_hunks_in_range(buffer_start..buffer_end)
21560 .filter_map(|hunk| {
21561 if folded_buffers.contains(&hunk.buffer_id) {
21562 return None;
21563 }
21564
21565 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21566 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21567
21568 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21569 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21570
21571 let display_hunk = if hunk_display_start.column() != 0 {
21572 DisplayDiffHunk::Folded {
21573 display_row: hunk_display_start.row(),
21574 }
21575 } else {
21576 let mut end_row = hunk_display_end.row();
21577 if hunk_display_end.column() > 0 {
21578 end_row.0 += 1;
21579 }
21580 let is_created_file = hunk.is_created_file();
21581 DisplayDiffHunk::Unfolded {
21582 status: hunk.status(),
21583 diff_base_byte_range: hunk.diff_base_byte_range,
21584 display_row_range: hunk_display_start.row()..end_row,
21585 multi_buffer_range: Anchor::range_in_buffer(
21586 hunk.excerpt_id,
21587 hunk.buffer_id,
21588 hunk.buffer_range,
21589 ),
21590 is_created_file,
21591 }
21592 };
21593
21594 Some(display_hunk)
21595 })
21596 }
21597
21598 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21599 self.display_snapshot.buffer_snapshot.language_at(position)
21600 }
21601
21602 pub fn is_focused(&self) -> bool {
21603 self.is_focused
21604 }
21605
21606 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21607 self.placeholder_text.as_ref()
21608 }
21609
21610 pub fn scroll_position(&self) -> gpui::Point<f32> {
21611 self.scroll_anchor.scroll_position(&self.display_snapshot)
21612 }
21613
21614 fn gutter_dimensions(
21615 &self,
21616 font_id: FontId,
21617 font_size: Pixels,
21618 max_line_number_width: Pixels,
21619 cx: &App,
21620 ) -> Option<GutterDimensions> {
21621 if !self.show_gutter {
21622 return None;
21623 }
21624
21625 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21626 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21627
21628 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21629 matches!(
21630 ProjectSettings::get_global(cx).git.git_gutter,
21631 Some(GitGutterSetting::TrackedFiles)
21632 )
21633 });
21634 let gutter_settings = EditorSettings::get_global(cx).gutter;
21635 let show_line_numbers = self
21636 .show_line_numbers
21637 .unwrap_or(gutter_settings.line_numbers);
21638 let line_gutter_width = if show_line_numbers {
21639 // Avoid flicker-like gutter resizes when the line number gains another digit by
21640 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21641 let min_width_for_number_on_gutter =
21642 ch_advance * gutter_settings.min_line_number_digits as f32;
21643 max_line_number_width.max(min_width_for_number_on_gutter)
21644 } else {
21645 0.0.into()
21646 };
21647
21648 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21649 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21650
21651 let git_blame_entries_width =
21652 self.git_blame_gutter_max_author_length
21653 .map(|max_author_length| {
21654 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21655 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21656
21657 /// The number of characters to dedicate to gaps and margins.
21658 const SPACING_WIDTH: usize = 4;
21659
21660 let max_char_count = max_author_length.min(renderer.max_author_length())
21661 + ::git::SHORT_SHA_LENGTH
21662 + MAX_RELATIVE_TIMESTAMP.len()
21663 + SPACING_WIDTH;
21664
21665 ch_advance * max_char_count
21666 });
21667
21668 let is_singleton = self.buffer_snapshot.is_singleton();
21669
21670 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21671 left_padding += if !is_singleton {
21672 ch_width * 4.0
21673 } else if show_runnables || show_breakpoints {
21674 ch_width * 3.0
21675 } else if show_git_gutter && show_line_numbers {
21676 ch_width * 2.0
21677 } else if show_git_gutter || show_line_numbers {
21678 ch_width
21679 } else {
21680 px(0.)
21681 };
21682
21683 let shows_folds = is_singleton && gutter_settings.folds;
21684
21685 let right_padding = if shows_folds && show_line_numbers {
21686 ch_width * 4.0
21687 } else if shows_folds || (!is_singleton && show_line_numbers) {
21688 ch_width * 3.0
21689 } else if show_line_numbers {
21690 ch_width
21691 } else {
21692 px(0.)
21693 };
21694
21695 Some(GutterDimensions {
21696 left_padding,
21697 right_padding,
21698 width: line_gutter_width + left_padding + right_padding,
21699 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21700 git_blame_entries_width,
21701 })
21702 }
21703
21704 pub fn render_crease_toggle(
21705 &self,
21706 buffer_row: MultiBufferRow,
21707 row_contains_cursor: bool,
21708 editor: Entity<Editor>,
21709 window: &mut Window,
21710 cx: &mut App,
21711 ) -> Option<AnyElement> {
21712 let folded = self.is_line_folded(buffer_row);
21713 let mut is_foldable = false;
21714
21715 if let Some(crease) = self
21716 .crease_snapshot
21717 .query_row(buffer_row, &self.buffer_snapshot)
21718 {
21719 is_foldable = true;
21720 match crease {
21721 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21722 if let Some(render_toggle) = render_toggle {
21723 let toggle_callback =
21724 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21725 if folded {
21726 editor.update(cx, |editor, cx| {
21727 editor.fold_at(buffer_row, window, cx)
21728 });
21729 } else {
21730 editor.update(cx, |editor, cx| {
21731 editor.unfold_at(buffer_row, window, cx)
21732 });
21733 }
21734 });
21735 return Some((render_toggle)(
21736 buffer_row,
21737 folded,
21738 toggle_callback,
21739 window,
21740 cx,
21741 ));
21742 }
21743 }
21744 }
21745 }
21746
21747 is_foldable |= self.starts_indent(buffer_row);
21748
21749 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21750 Some(
21751 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21752 .toggle_state(folded)
21753 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21754 if folded {
21755 this.unfold_at(buffer_row, window, cx);
21756 } else {
21757 this.fold_at(buffer_row, window, cx);
21758 }
21759 }))
21760 .into_any_element(),
21761 )
21762 } else {
21763 None
21764 }
21765 }
21766
21767 pub fn render_crease_trailer(
21768 &self,
21769 buffer_row: MultiBufferRow,
21770 window: &mut Window,
21771 cx: &mut App,
21772 ) -> Option<AnyElement> {
21773 let folded = self.is_line_folded(buffer_row);
21774 if let Crease::Inline { render_trailer, .. } = self
21775 .crease_snapshot
21776 .query_row(buffer_row, &self.buffer_snapshot)?
21777 {
21778 let render_trailer = render_trailer.as_ref()?;
21779 Some(render_trailer(buffer_row, folded, window, cx))
21780 } else {
21781 None
21782 }
21783 }
21784}
21785
21786impl Deref for EditorSnapshot {
21787 type Target = DisplaySnapshot;
21788
21789 fn deref(&self) -> &Self::Target {
21790 &self.display_snapshot
21791 }
21792}
21793
21794#[derive(Clone, Debug, PartialEq, Eq)]
21795pub enum EditorEvent {
21796 InputIgnored {
21797 text: Arc<str>,
21798 },
21799 InputHandled {
21800 utf16_range_to_replace: Option<Range<isize>>,
21801 text: Arc<str>,
21802 },
21803 ExcerptsAdded {
21804 buffer: Entity<Buffer>,
21805 predecessor: ExcerptId,
21806 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21807 },
21808 ExcerptsRemoved {
21809 ids: Vec<ExcerptId>,
21810 removed_buffer_ids: Vec<BufferId>,
21811 },
21812 BufferFoldToggled {
21813 ids: Vec<ExcerptId>,
21814 folded: bool,
21815 },
21816 ExcerptsEdited {
21817 ids: Vec<ExcerptId>,
21818 },
21819 ExcerptsExpanded {
21820 ids: Vec<ExcerptId>,
21821 },
21822 BufferEdited,
21823 Edited {
21824 transaction_id: clock::Lamport,
21825 },
21826 Reparsed(BufferId),
21827 Focused,
21828 FocusedIn,
21829 Blurred,
21830 DirtyChanged,
21831 Saved,
21832 TitleChanged,
21833 DiffBaseChanged,
21834 SelectionsChanged {
21835 local: bool,
21836 },
21837 ScrollPositionChanged {
21838 local: bool,
21839 autoscroll: bool,
21840 },
21841 Closed,
21842 TransactionUndone {
21843 transaction_id: clock::Lamport,
21844 },
21845 TransactionBegun {
21846 transaction_id: clock::Lamport,
21847 },
21848 Reloaded,
21849 CursorShapeChanged,
21850 PushedToNavHistory {
21851 anchor: Anchor,
21852 is_deactivate: bool,
21853 },
21854}
21855
21856impl EventEmitter<EditorEvent> for Editor {}
21857
21858impl Focusable for Editor {
21859 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21860 self.focus_handle.clone()
21861 }
21862}
21863
21864impl Render for Editor {
21865 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21866 let settings = ThemeSettings::get_global(cx);
21867
21868 let mut text_style = match self.mode {
21869 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21870 color: cx.theme().colors().editor_foreground,
21871 font_family: settings.ui_font.family.clone(),
21872 font_features: settings.ui_font.features.clone(),
21873 font_fallbacks: settings.ui_font.fallbacks.clone(),
21874 font_size: rems(0.875).into(),
21875 font_weight: settings.ui_font.weight,
21876 line_height: relative(settings.buffer_line_height.value()),
21877 ..Default::default()
21878 },
21879 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21880 color: cx.theme().colors().editor_foreground,
21881 font_family: settings.buffer_font.family.clone(),
21882 font_features: settings.buffer_font.features.clone(),
21883 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21884 font_size: settings.buffer_font_size(cx).into(),
21885 font_weight: settings.buffer_font.weight,
21886 line_height: relative(settings.buffer_line_height.value()),
21887 ..Default::default()
21888 },
21889 };
21890 if let Some(text_style_refinement) = &self.text_style_refinement {
21891 text_style.refine(text_style_refinement)
21892 }
21893
21894 let background = match self.mode {
21895 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21896 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
21897 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21898 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21899 };
21900
21901 EditorElement::new(
21902 &cx.entity(),
21903 EditorStyle {
21904 background,
21905 local_player: cx.theme().players().local(),
21906 text: text_style,
21907 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21908 syntax: cx.theme().syntax().clone(),
21909 status: cx.theme().status().clone(),
21910 inlay_hints_style: make_inlay_hints_style(cx),
21911 inline_completion_styles: make_suggestion_styles(cx),
21912 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21913 show_underlines: !self.mode.is_minimap(),
21914 },
21915 )
21916 }
21917}
21918
21919impl EntityInputHandler for Editor {
21920 fn text_for_range(
21921 &mut self,
21922 range_utf16: Range<usize>,
21923 adjusted_range: &mut Option<Range<usize>>,
21924 _: &mut Window,
21925 cx: &mut Context<Self>,
21926 ) -> Option<String> {
21927 let snapshot = self.buffer.read(cx).read(cx);
21928 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21929 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21930 if (start.0..end.0) != range_utf16 {
21931 adjusted_range.replace(start.0..end.0);
21932 }
21933 Some(snapshot.text_for_range(start..end).collect())
21934 }
21935
21936 fn selected_text_range(
21937 &mut self,
21938 ignore_disabled_input: bool,
21939 _: &mut Window,
21940 cx: &mut Context<Self>,
21941 ) -> Option<UTF16Selection> {
21942 // Prevent the IME menu from appearing when holding down an alphabetic key
21943 // while input is disabled.
21944 if !ignore_disabled_input && !self.input_enabled {
21945 return None;
21946 }
21947
21948 let selection = self.selections.newest::<OffsetUtf16>(cx);
21949 let range = selection.range();
21950
21951 Some(UTF16Selection {
21952 range: range.start.0..range.end.0,
21953 reversed: selection.reversed,
21954 })
21955 }
21956
21957 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21958 let snapshot = self.buffer.read(cx).read(cx);
21959 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21960 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21961 }
21962
21963 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21964 self.clear_highlights::<InputComposition>(cx);
21965 self.ime_transaction.take();
21966 }
21967
21968 fn replace_text_in_range(
21969 &mut self,
21970 range_utf16: Option<Range<usize>>,
21971 text: &str,
21972 window: &mut Window,
21973 cx: &mut Context<Self>,
21974 ) {
21975 if !self.input_enabled {
21976 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21977 return;
21978 }
21979
21980 self.transact(window, cx, |this, window, cx| {
21981 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21982 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21983 Some(this.selection_replacement_ranges(range_utf16, cx))
21984 } else {
21985 this.marked_text_ranges(cx)
21986 };
21987
21988 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21989 let newest_selection_id = this.selections.newest_anchor().id;
21990 this.selections
21991 .all::<OffsetUtf16>(cx)
21992 .iter()
21993 .zip(ranges_to_replace.iter())
21994 .find_map(|(selection, range)| {
21995 if selection.id == newest_selection_id {
21996 Some(
21997 (range.start.0 as isize - selection.head().0 as isize)
21998 ..(range.end.0 as isize - selection.head().0 as isize),
21999 )
22000 } else {
22001 None
22002 }
22003 })
22004 });
22005
22006 cx.emit(EditorEvent::InputHandled {
22007 utf16_range_to_replace: range_to_replace,
22008 text: text.into(),
22009 });
22010
22011 if let Some(new_selected_ranges) = new_selected_ranges {
22012 this.change_selections(None, window, cx, |selections| {
22013 selections.select_ranges(new_selected_ranges)
22014 });
22015 this.backspace(&Default::default(), window, cx);
22016 }
22017
22018 this.handle_input(text, window, cx);
22019 });
22020
22021 if let Some(transaction) = self.ime_transaction {
22022 self.buffer.update(cx, |buffer, cx| {
22023 buffer.group_until_transaction(transaction, cx);
22024 });
22025 }
22026
22027 self.unmark_text(window, cx);
22028 }
22029
22030 fn replace_and_mark_text_in_range(
22031 &mut self,
22032 range_utf16: Option<Range<usize>>,
22033 text: &str,
22034 new_selected_range_utf16: Option<Range<usize>>,
22035 window: &mut Window,
22036 cx: &mut Context<Self>,
22037 ) {
22038 if !self.input_enabled {
22039 return;
22040 }
22041
22042 let transaction = self.transact(window, cx, |this, window, cx| {
22043 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22044 let snapshot = this.buffer.read(cx).read(cx);
22045 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22046 for marked_range in &mut marked_ranges {
22047 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22048 marked_range.start.0 += relative_range_utf16.start;
22049 marked_range.start =
22050 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22051 marked_range.end =
22052 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22053 }
22054 }
22055 Some(marked_ranges)
22056 } else if let Some(range_utf16) = range_utf16 {
22057 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22058 Some(this.selection_replacement_ranges(range_utf16, cx))
22059 } else {
22060 None
22061 };
22062
22063 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22064 let newest_selection_id = this.selections.newest_anchor().id;
22065 this.selections
22066 .all::<OffsetUtf16>(cx)
22067 .iter()
22068 .zip(ranges_to_replace.iter())
22069 .find_map(|(selection, range)| {
22070 if selection.id == newest_selection_id {
22071 Some(
22072 (range.start.0 as isize - selection.head().0 as isize)
22073 ..(range.end.0 as isize - selection.head().0 as isize),
22074 )
22075 } else {
22076 None
22077 }
22078 })
22079 });
22080
22081 cx.emit(EditorEvent::InputHandled {
22082 utf16_range_to_replace: range_to_replace,
22083 text: text.into(),
22084 });
22085
22086 if let Some(ranges) = ranges_to_replace {
22087 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22088 }
22089
22090 let marked_ranges = {
22091 let snapshot = this.buffer.read(cx).read(cx);
22092 this.selections
22093 .disjoint_anchors()
22094 .iter()
22095 .map(|selection| {
22096 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22097 })
22098 .collect::<Vec<_>>()
22099 };
22100
22101 if text.is_empty() {
22102 this.unmark_text(window, cx);
22103 } else {
22104 this.highlight_text::<InputComposition>(
22105 marked_ranges.clone(),
22106 HighlightStyle {
22107 underline: Some(UnderlineStyle {
22108 thickness: px(1.),
22109 color: None,
22110 wavy: false,
22111 }),
22112 ..Default::default()
22113 },
22114 cx,
22115 );
22116 }
22117
22118 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22119 let use_autoclose = this.use_autoclose;
22120 let use_auto_surround = this.use_auto_surround;
22121 this.set_use_autoclose(false);
22122 this.set_use_auto_surround(false);
22123 this.handle_input(text, window, cx);
22124 this.set_use_autoclose(use_autoclose);
22125 this.set_use_auto_surround(use_auto_surround);
22126
22127 if let Some(new_selected_range) = new_selected_range_utf16 {
22128 let snapshot = this.buffer.read(cx).read(cx);
22129 let new_selected_ranges = marked_ranges
22130 .into_iter()
22131 .map(|marked_range| {
22132 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22133 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22134 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22135 snapshot.clip_offset_utf16(new_start, Bias::Left)
22136 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22137 })
22138 .collect::<Vec<_>>();
22139
22140 drop(snapshot);
22141 this.change_selections(None, window, cx, |selections| {
22142 selections.select_ranges(new_selected_ranges)
22143 });
22144 }
22145 });
22146
22147 self.ime_transaction = self.ime_transaction.or(transaction);
22148 if let Some(transaction) = self.ime_transaction {
22149 self.buffer.update(cx, |buffer, cx| {
22150 buffer.group_until_transaction(transaction, cx);
22151 });
22152 }
22153
22154 if self.text_highlights::<InputComposition>(cx).is_none() {
22155 self.ime_transaction.take();
22156 }
22157 }
22158
22159 fn bounds_for_range(
22160 &mut self,
22161 range_utf16: Range<usize>,
22162 element_bounds: gpui::Bounds<Pixels>,
22163 window: &mut Window,
22164 cx: &mut Context<Self>,
22165 ) -> Option<gpui::Bounds<Pixels>> {
22166 let text_layout_details = self.text_layout_details(window);
22167 let gpui::Size {
22168 width: em_width,
22169 height: line_height,
22170 } = self.character_size(window);
22171
22172 let snapshot = self.snapshot(window, cx);
22173 let scroll_position = snapshot.scroll_position();
22174 let scroll_left = scroll_position.x * em_width;
22175
22176 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22177 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22178 + self.gutter_dimensions.width
22179 + self.gutter_dimensions.margin;
22180 let y = line_height * (start.row().as_f32() - scroll_position.y);
22181
22182 Some(Bounds {
22183 origin: element_bounds.origin + point(x, y),
22184 size: size(em_width, line_height),
22185 })
22186 }
22187
22188 fn character_index_for_point(
22189 &mut self,
22190 point: gpui::Point<Pixels>,
22191 _window: &mut Window,
22192 _cx: &mut Context<Self>,
22193 ) -> Option<usize> {
22194 let position_map = self.last_position_map.as_ref()?;
22195 if !position_map.text_hitbox.contains(&point) {
22196 return None;
22197 }
22198 let display_point = position_map.point_for_position(point).previous_valid;
22199 let anchor = position_map
22200 .snapshot
22201 .display_point_to_anchor(display_point, Bias::Left);
22202 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22203 Some(utf16_offset.0)
22204 }
22205}
22206
22207trait SelectionExt {
22208 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22209 fn spanned_rows(
22210 &self,
22211 include_end_if_at_line_start: bool,
22212 map: &DisplaySnapshot,
22213 ) -> Range<MultiBufferRow>;
22214}
22215
22216impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22217 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22218 let start = self
22219 .start
22220 .to_point(&map.buffer_snapshot)
22221 .to_display_point(map);
22222 let end = self
22223 .end
22224 .to_point(&map.buffer_snapshot)
22225 .to_display_point(map);
22226 if self.reversed {
22227 end..start
22228 } else {
22229 start..end
22230 }
22231 }
22232
22233 fn spanned_rows(
22234 &self,
22235 include_end_if_at_line_start: bool,
22236 map: &DisplaySnapshot,
22237 ) -> Range<MultiBufferRow> {
22238 let start = self.start.to_point(&map.buffer_snapshot);
22239 let mut end = self.end.to_point(&map.buffer_snapshot);
22240 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22241 end.row -= 1;
22242 }
22243
22244 let buffer_start = map.prev_line_boundary(start).0;
22245 let buffer_end = map.next_line_boundary(end).0;
22246 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22247 }
22248}
22249
22250impl<T: InvalidationRegion> InvalidationStack<T> {
22251 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22252 where
22253 S: Clone + ToOffset,
22254 {
22255 while let Some(region) = self.last() {
22256 let all_selections_inside_invalidation_ranges =
22257 if selections.len() == region.ranges().len() {
22258 selections
22259 .iter()
22260 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22261 .all(|(selection, invalidation_range)| {
22262 let head = selection.head().to_offset(buffer);
22263 invalidation_range.start <= head && invalidation_range.end >= head
22264 })
22265 } else {
22266 false
22267 };
22268
22269 if all_selections_inside_invalidation_ranges {
22270 break;
22271 } else {
22272 self.pop();
22273 }
22274 }
22275 }
22276}
22277
22278impl<T> Default for InvalidationStack<T> {
22279 fn default() -> Self {
22280 Self(Default::default())
22281 }
22282}
22283
22284impl<T> Deref for InvalidationStack<T> {
22285 type Target = Vec<T>;
22286
22287 fn deref(&self) -> &Self::Target {
22288 &self.0
22289 }
22290}
22291
22292impl<T> DerefMut for InvalidationStack<T> {
22293 fn deref_mut(&mut self) -> &mut Self::Target {
22294 &mut self.0
22295 }
22296}
22297
22298impl InvalidationRegion for SnippetState {
22299 fn ranges(&self) -> &[Range<Anchor>] {
22300 &self.ranges[self.active_index]
22301 }
22302}
22303
22304fn inline_completion_edit_text(
22305 current_snapshot: &BufferSnapshot,
22306 edits: &[(Range<Anchor>, String)],
22307 edit_preview: &EditPreview,
22308 include_deletions: bool,
22309 cx: &App,
22310) -> HighlightedText {
22311 let edits = edits
22312 .iter()
22313 .map(|(anchor, text)| {
22314 (
22315 anchor.start.text_anchor..anchor.end.text_anchor,
22316 text.clone(),
22317 )
22318 })
22319 .collect::<Vec<_>>();
22320
22321 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22322}
22323
22324pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22325 match severity {
22326 lsp::DiagnosticSeverity::ERROR => colors.error,
22327 lsp::DiagnosticSeverity::WARNING => colors.warning,
22328 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22329 lsp::DiagnosticSeverity::HINT => colors.info,
22330 _ => colors.ignored,
22331 }
22332}
22333
22334pub fn styled_runs_for_code_label<'a>(
22335 label: &'a CodeLabel,
22336 syntax_theme: &'a theme::SyntaxTheme,
22337) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22338 let fade_out = HighlightStyle {
22339 fade_out: Some(0.35),
22340 ..Default::default()
22341 };
22342
22343 let mut prev_end = label.filter_range.end;
22344 label
22345 .runs
22346 .iter()
22347 .enumerate()
22348 .flat_map(move |(ix, (range, highlight_id))| {
22349 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22350 style
22351 } else {
22352 return Default::default();
22353 };
22354 let mut muted_style = style;
22355 muted_style.highlight(fade_out);
22356
22357 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22358 if range.start >= label.filter_range.end {
22359 if range.start > prev_end {
22360 runs.push((prev_end..range.start, fade_out));
22361 }
22362 runs.push((range.clone(), muted_style));
22363 } else if range.end <= label.filter_range.end {
22364 runs.push((range.clone(), style));
22365 } else {
22366 runs.push((range.start..label.filter_range.end, style));
22367 runs.push((label.filter_range.end..range.end, muted_style));
22368 }
22369 prev_end = cmp::max(prev_end, range.end);
22370
22371 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22372 runs.push((prev_end..label.text.len(), fade_out));
22373 }
22374
22375 runs
22376 })
22377}
22378
22379pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22380 let mut prev_index = 0;
22381 let mut prev_codepoint: Option<char> = None;
22382 text.char_indices()
22383 .chain([(text.len(), '\0')])
22384 .filter_map(move |(index, codepoint)| {
22385 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22386 let is_boundary = index == text.len()
22387 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22388 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22389 if is_boundary {
22390 let chunk = &text[prev_index..index];
22391 prev_index = index;
22392 Some(chunk)
22393 } else {
22394 None
22395 }
22396 })
22397}
22398
22399pub trait RangeToAnchorExt: Sized {
22400 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22401
22402 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22403 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22404 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22405 }
22406}
22407
22408impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22409 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22410 let start_offset = self.start.to_offset(snapshot);
22411 let end_offset = self.end.to_offset(snapshot);
22412 if start_offset == end_offset {
22413 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22414 } else {
22415 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22416 }
22417 }
22418}
22419
22420pub trait RowExt {
22421 fn as_f32(&self) -> f32;
22422
22423 fn next_row(&self) -> Self;
22424
22425 fn previous_row(&self) -> Self;
22426
22427 fn minus(&self, other: Self) -> u32;
22428}
22429
22430impl RowExt for DisplayRow {
22431 fn as_f32(&self) -> f32 {
22432 self.0 as f32
22433 }
22434
22435 fn next_row(&self) -> Self {
22436 Self(self.0 + 1)
22437 }
22438
22439 fn previous_row(&self) -> Self {
22440 Self(self.0.saturating_sub(1))
22441 }
22442
22443 fn minus(&self, other: Self) -> u32 {
22444 self.0 - other.0
22445 }
22446}
22447
22448impl RowExt for MultiBufferRow {
22449 fn as_f32(&self) -> f32 {
22450 self.0 as f32
22451 }
22452
22453 fn next_row(&self) -> Self {
22454 Self(self.0 + 1)
22455 }
22456
22457 fn previous_row(&self) -> Self {
22458 Self(self.0.saturating_sub(1))
22459 }
22460
22461 fn minus(&self, other: Self) -> u32 {
22462 self.0 - other.0
22463 }
22464}
22465
22466trait RowRangeExt {
22467 type Row;
22468
22469 fn len(&self) -> usize;
22470
22471 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22472}
22473
22474impl RowRangeExt for Range<MultiBufferRow> {
22475 type Row = MultiBufferRow;
22476
22477 fn len(&self) -> usize {
22478 (self.end.0 - self.start.0) as usize
22479 }
22480
22481 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22482 (self.start.0..self.end.0).map(MultiBufferRow)
22483 }
22484}
22485
22486impl RowRangeExt for Range<DisplayRow> {
22487 type Row = DisplayRow;
22488
22489 fn len(&self) -> usize {
22490 (self.end.0 - self.start.0) as usize
22491 }
22492
22493 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22494 (self.start.0..self.end.0).map(DisplayRow)
22495 }
22496}
22497
22498/// If select range has more than one line, we
22499/// just point the cursor to range.start.
22500fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22501 if range.start.row == range.end.row {
22502 range
22503 } else {
22504 range.start..range.start
22505 }
22506}
22507pub struct KillRing(ClipboardItem);
22508impl Global for KillRing {}
22509
22510const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22511
22512enum BreakpointPromptEditAction {
22513 Log,
22514 Condition,
22515 HitCondition,
22516}
22517
22518struct BreakpointPromptEditor {
22519 pub(crate) prompt: Entity<Editor>,
22520 editor: WeakEntity<Editor>,
22521 breakpoint_anchor: Anchor,
22522 breakpoint: Breakpoint,
22523 edit_action: BreakpointPromptEditAction,
22524 block_ids: HashSet<CustomBlockId>,
22525 editor_margins: Arc<Mutex<EditorMargins>>,
22526 _subscriptions: Vec<Subscription>,
22527}
22528
22529impl BreakpointPromptEditor {
22530 const MAX_LINES: u8 = 4;
22531
22532 fn new(
22533 editor: WeakEntity<Editor>,
22534 breakpoint_anchor: Anchor,
22535 breakpoint: Breakpoint,
22536 edit_action: BreakpointPromptEditAction,
22537 window: &mut Window,
22538 cx: &mut Context<Self>,
22539 ) -> Self {
22540 let base_text = match edit_action {
22541 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22542 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22543 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22544 }
22545 .map(|msg| msg.to_string())
22546 .unwrap_or_default();
22547
22548 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22549 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22550
22551 let prompt = cx.new(|cx| {
22552 let mut prompt = Editor::new(
22553 EditorMode::AutoHeight {
22554 min_lines: 1,
22555 max_lines: Self::MAX_LINES as usize,
22556 },
22557 buffer,
22558 None,
22559 window,
22560 cx,
22561 );
22562 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22563 prompt.set_show_cursor_when_unfocused(false, cx);
22564 prompt.set_placeholder_text(
22565 match edit_action {
22566 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22567 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22568 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22569 },
22570 cx,
22571 );
22572
22573 prompt
22574 });
22575
22576 Self {
22577 prompt,
22578 editor,
22579 breakpoint_anchor,
22580 breakpoint,
22581 edit_action,
22582 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22583 block_ids: Default::default(),
22584 _subscriptions: vec![],
22585 }
22586 }
22587
22588 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22589 self.block_ids.extend(block_ids)
22590 }
22591
22592 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22593 if let Some(editor) = self.editor.upgrade() {
22594 let message = self
22595 .prompt
22596 .read(cx)
22597 .buffer
22598 .read(cx)
22599 .as_singleton()
22600 .expect("A multi buffer in breakpoint prompt isn't possible")
22601 .read(cx)
22602 .as_rope()
22603 .to_string();
22604
22605 editor.update(cx, |editor, cx| {
22606 editor.edit_breakpoint_at_anchor(
22607 self.breakpoint_anchor,
22608 self.breakpoint.clone(),
22609 match self.edit_action {
22610 BreakpointPromptEditAction::Log => {
22611 BreakpointEditAction::EditLogMessage(message.into())
22612 }
22613 BreakpointPromptEditAction::Condition => {
22614 BreakpointEditAction::EditCondition(message.into())
22615 }
22616 BreakpointPromptEditAction::HitCondition => {
22617 BreakpointEditAction::EditHitCondition(message.into())
22618 }
22619 },
22620 cx,
22621 );
22622
22623 editor.remove_blocks(self.block_ids.clone(), None, cx);
22624 cx.focus_self(window);
22625 });
22626 }
22627 }
22628
22629 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22630 self.editor
22631 .update(cx, |editor, cx| {
22632 editor.remove_blocks(self.block_ids.clone(), None, cx);
22633 window.focus(&editor.focus_handle);
22634 })
22635 .log_err();
22636 }
22637
22638 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22639 let settings = ThemeSettings::get_global(cx);
22640 let text_style = TextStyle {
22641 color: if self.prompt.read(cx).read_only(cx) {
22642 cx.theme().colors().text_disabled
22643 } else {
22644 cx.theme().colors().text
22645 },
22646 font_family: settings.buffer_font.family.clone(),
22647 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22648 font_size: settings.buffer_font_size(cx).into(),
22649 font_weight: settings.buffer_font.weight,
22650 line_height: relative(settings.buffer_line_height.value()),
22651 ..Default::default()
22652 };
22653 EditorElement::new(
22654 &self.prompt,
22655 EditorStyle {
22656 background: cx.theme().colors().editor_background,
22657 local_player: cx.theme().players().local(),
22658 text: text_style,
22659 ..Default::default()
22660 },
22661 )
22662 }
22663}
22664
22665impl Render for BreakpointPromptEditor {
22666 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22667 let editor_margins = *self.editor_margins.lock();
22668 let gutter_dimensions = editor_margins.gutter;
22669 h_flex()
22670 .key_context("Editor")
22671 .bg(cx.theme().colors().editor_background)
22672 .border_y_1()
22673 .border_color(cx.theme().status().info_border)
22674 .size_full()
22675 .py(window.line_height() / 2.5)
22676 .on_action(cx.listener(Self::confirm))
22677 .on_action(cx.listener(Self::cancel))
22678 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22679 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22680 }
22681}
22682
22683impl Focusable for BreakpointPromptEditor {
22684 fn focus_handle(&self, cx: &App) -> FocusHandle {
22685 self.prompt.focus_handle(cx)
22686 }
22687}
22688
22689fn all_edits_insertions_or_deletions(
22690 edits: &Vec<(Range<Anchor>, String)>,
22691 snapshot: &MultiBufferSnapshot,
22692) -> bool {
22693 let mut all_insertions = true;
22694 let mut all_deletions = true;
22695
22696 for (range, new_text) in edits.iter() {
22697 let range_is_empty = range.to_offset(&snapshot).is_empty();
22698 let text_is_empty = new_text.is_empty();
22699
22700 if range_is_empty != text_is_empty {
22701 if range_is_empty {
22702 all_deletions = false;
22703 } else {
22704 all_insertions = false;
22705 }
22706 } else {
22707 return false;
22708 }
22709
22710 if !all_insertions && !all_deletions {
22711 return false;
22712 }
22713 }
22714 all_insertions || all_deletions
22715}
22716
22717struct MissingEditPredictionKeybindingTooltip;
22718
22719impl Render for MissingEditPredictionKeybindingTooltip {
22720 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22721 ui::tooltip_container(window, cx, |container, _, cx| {
22722 container
22723 .flex_shrink_0()
22724 .max_w_80()
22725 .min_h(rems_from_px(124.))
22726 .justify_between()
22727 .child(
22728 v_flex()
22729 .flex_1()
22730 .text_ui_sm(cx)
22731 .child(Label::new("Conflict with Accept Keybinding"))
22732 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22733 )
22734 .child(
22735 h_flex()
22736 .pb_1()
22737 .gap_1()
22738 .items_end()
22739 .w_full()
22740 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22741 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22742 }))
22743 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22744 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22745 })),
22746 )
22747 })
22748 }
22749}
22750
22751#[derive(Debug, Clone, Copy, PartialEq)]
22752pub struct LineHighlight {
22753 pub background: Background,
22754 pub border: Option<gpui::Hsla>,
22755 pub include_gutter: bool,
22756 pub type_id: Option<TypeId>,
22757}
22758
22759fn render_diff_hunk_controls(
22760 row: u32,
22761 status: &DiffHunkStatus,
22762 hunk_range: Range<Anchor>,
22763 is_created_file: bool,
22764 line_height: Pixels,
22765 editor: &Entity<Editor>,
22766 _window: &mut Window,
22767 cx: &mut App,
22768) -> AnyElement {
22769 h_flex()
22770 .h(line_height)
22771 .mr_1()
22772 .gap_1()
22773 .px_0p5()
22774 .pb_1()
22775 .border_x_1()
22776 .border_b_1()
22777 .border_color(cx.theme().colors().border_variant)
22778 .rounded_b_lg()
22779 .bg(cx.theme().colors().editor_background)
22780 .gap_1()
22781 .block_mouse_except_scroll()
22782 .shadow_md()
22783 .child(if status.has_secondary_hunk() {
22784 Button::new(("stage", row as u64), "Stage")
22785 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22786 .tooltip({
22787 let focus_handle = editor.focus_handle(cx);
22788 move |window, cx| {
22789 Tooltip::for_action_in(
22790 "Stage Hunk",
22791 &::git::ToggleStaged,
22792 &focus_handle,
22793 window,
22794 cx,
22795 )
22796 }
22797 })
22798 .on_click({
22799 let editor = editor.clone();
22800 move |_event, _window, cx| {
22801 editor.update(cx, |editor, cx| {
22802 editor.stage_or_unstage_diff_hunks(
22803 true,
22804 vec![hunk_range.start..hunk_range.start],
22805 cx,
22806 );
22807 });
22808 }
22809 })
22810 } else {
22811 Button::new(("unstage", row as u64), "Unstage")
22812 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22813 .tooltip({
22814 let focus_handle = editor.focus_handle(cx);
22815 move |window, cx| {
22816 Tooltip::for_action_in(
22817 "Unstage Hunk",
22818 &::git::ToggleStaged,
22819 &focus_handle,
22820 window,
22821 cx,
22822 )
22823 }
22824 })
22825 .on_click({
22826 let editor = editor.clone();
22827 move |_event, _window, cx| {
22828 editor.update(cx, |editor, cx| {
22829 editor.stage_or_unstage_diff_hunks(
22830 false,
22831 vec![hunk_range.start..hunk_range.start],
22832 cx,
22833 );
22834 });
22835 }
22836 })
22837 })
22838 .child(
22839 Button::new(("restore", row as u64), "Restore")
22840 .tooltip({
22841 let focus_handle = editor.focus_handle(cx);
22842 move |window, cx| {
22843 Tooltip::for_action_in(
22844 "Restore Hunk",
22845 &::git::Restore,
22846 &focus_handle,
22847 window,
22848 cx,
22849 )
22850 }
22851 })
22852 .on_click({
22853 let editor = editor.clone();
22854 move |_event, window, cx| {
22855 editor.update(cx, |editor, cx| {
22856 let snapshot = editor.snapshot(window, cx);
22857 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22858 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22859 });
22860 }
22861 })
22862 .disabled(is_created_file),
22863 )
22864 .when(
22865 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22866 |el| {
22867 el.child(
22868 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22869 .shape(IconButtonShape::Square)
22870 .icon_size(IconSize::Small)
22871 // .disabled(!has_multiple_hunks)
22872 .tooltip({
22873 let focus_handle = editor.focus_handle(cx);
22874 move |window, cx| {
22875 Tooltip::for_action_in(
22876 "Next Hunk",
22877 &GoToHunk,
22878 &focus_handle,
22879 window,
22880 cx,
22881 )
22882 }
22883 })
22884 .on_click({
22885 let editor = editor.clone();
22886 move |_event, window, cx| {
22887 editor.update(cx, |editor, cx| {
22888 let snapshot = editor.snapshot(window, cx);
22889 let position =
22890 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22891 editor.go_to_hunk_before_or_after_position(
22892 &snapshot,
22893 position,
22894 Direction::Next,
22895 window,
22896 cx,
22897 );
22898 editor.expand_selected_diff_hunks(cx);
22899 });
22900 }
22901 }),
22902 )
22903 .child(
22904 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22905 .shape(IconButtonShape::Square)
22906 .icon_size(IconSize::Small)
22907 // .disabled(!has_multiple_hunks)
22908 .tooltip({
22909 let focus_handle = editor.focus_handle(cx);
22910 move |window, cx| {
22911 Tooltip::for_action_in(
22912 "Previous Hunk",
22913 &GoToPreviousHunk,
22914 &focus_handle,
22915 window,
22916 cx,
22917 )
22918 }
22919 })
22920 .on_click({
22921 let editor = editor.clone();
22922 move |_event, window, cx| {
22923 editor.update(cx, |editor, cx| {
22924 let snapshot = editor.snapshot(window, cx);
22925 let point =
22926 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22927 editor.go_to_hunk_before_or_after_position(
22928 &snapshot,
22929 point,
22930 Direction::Prev,
22931 window,
22932 cx,
22933 );
22934 editor.expand_selected_diff_hunks(cx);
22935 });
22936 }
22937 }),
22938 )
22939 },
22940 )
22941 .into_any_element()
22942}