1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 show_task: Option<Task<()>>,
903 hide_task: Option<Task<()>>,
904 popover_bounds: Option<Bounds<Pixels>>,
905 popover_state: InlineBlamePopoverState,
906}
907
908enum SelectionDragState {
909 /// State when no drag related activity is detected.
910 None,
911 /// State when the mouse is down on a selection that is about to be dragged.
912 ReadyToDrag {
913 selection: Selection<Anchor>,
914 click_position: gpui::Point<Pixels>,
915 },
916 /// State when the mouse is dragging the selection in the editor.
917 Dragging {
918 selection: Selection<Anchor>,
919 drop_cursor: Selection<Anchor>,
920 hide_drop_cursor: bool,
921 },
922}
923
924/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
925/// a breakpoint on them.
926#[derive(Clone, Copy, Debug, PartialEq, Eq)]
927struct PhantomBreakpointIndicator {
928 display_row: DisplayRow,
929 /// There's a small debounce between hovering over the line and showing the indicator.
930 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
931 is_active: bool,
932 collides_with_existing_breakpoint: bool,
933}
934
935/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
936///
937/// See the [module level documentation](self) for more information.
938pub struct Editor {
939 focus_handle: FocusHandle,
940 last_focused_descendant: Option<WeakFocusHandle>,
941 /// The text buffer being edited
942 buffer: Entity<MultiBuffer>,
943 /// Map of how text in the buffer should be displayed.
944 /// Handles soft wraps, folds, fake inlay text insertions, etc.
945 pub display_map: Entity<DisplayMap>,
946 pub selections: SelectionsCollection,
947 pub scroll_manager: ScrollManager,
948 /// When inline assist editors are linked, they all render cursors because
949 /// typing enters text into each of them, even the ones that aren't focused.
950 pub(crate) show_cursor_when_unfocused: bool,
951 columnar_selection_tail: Option<Anchor>,
952 columnar_display_point: Option<DisplayPoint>,
953 add_selections_state: Option<AddSelectionsState>,
954 select_next_state: Option<SelectNextState>,
955 select_prev_state: Option<SelectNextState>,
956 selection_history: SelectionHistory,
957 defer_selection_effects: bool,
958 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
959 autoclose_regions: Vec<AutocloseRegion>,
960 snippet_stack: InvalidationStack<SnippetState>,
961 select_syntax_node_history: SelectSyntaxNodeHistory,
962 ime_transaction: Option<TransactionId>,
963 pub diagnostics_max_severity: DiagnosticSeverity,
964 active_diagnostics: ActiveDiagnostic,
965 show_inline_diagnostics: bool,
966 inline_diagnostics_update: Task<()>,
967 inline_diagnostics_enabled: bool,
968 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
969 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
970 hard_wrap: Option<usize>,
971
972 // TODO: make this a access method
973 pub project: Option<Entity<Project>>,
974 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
975 completion_provider: Option<Rc<dyn CompletionProvider>>,
976 collaboration_hub: Option<Box<dyn CollaborationHub>>,
977 blink_manager: Entity<BlinkManager>,
978 show_cursor_names: bool,
979 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
980 pub show_local_selections: bool,
981 mode: EditorMode,
982 show_breadcrumbs: bool,
983 show_gutter: bool,
984 show_scrollbars: ScrollbarAxes,
985 minimap_visibility: MinimapVisibility,
986 offset_content: bool,
987 disable_expand_excerpt_buttons: bool,
988 show_line_numbers: Option<bool>,
989 use_relative_line_numbers: Option<bool>,
990 show_git_diff_gutter: Option<bool>,
991 show_code_actions: Option<bool>,
992 show_runnables: Option<bool>,
993 show_breakpoints: Option<bool>,
994 show_wrap_guides: Option<bool>,
995 show_indent_guides: Option<bool>,
996 placeholder_text: Option<Arc<str>>,
997 highlight_order: usize,
998 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
999 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1000 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1001 scrollbar_marker_state: ScrollbarMarkerState,
1002 active_indent_guides_state: ActiveIndentGuidesState,
1003 nav_history: Option<ItemNavHistory>,
1004 context_menu: RefCell<Option<CodeContextMenu>>,
1005 context_menu_options: Option<ContextMenuOptions>,
1006 mouse_context_menu: Option<MouseContextMenu>,
1007 completion_tasks: Vec<(CompletionId, Task<()>)>,
1008 inline_blame_popover: Option<InlineBlamePopover>,
1009 signature_help_state: SignatureHelpState,
1010 auto_signature_help: Option<bool>,
1011 find_all_references_task_sources: Vec<Anchor>,
1012 next_completion_id: CompletionId,
1013 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1014 code_actions_task: Option<Task<Result<()>>>,
1015 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1016 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1017 document_highlights_task: Option<Task<()>>,
1018 linked_editing_range_task: Option<Task<Option<()>>>,
1019 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1020 pending_rename: Option<RenameState>,
1021 searchable: bool,
1022 cursor_shape: CursorShape,
1023 current_line_highlight: Option<CurrentLineHighlight>,
1024 collapse_matches: bool,
1025 autoindent_mode: Option<AutoindentMode>,
1026 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1027 input_enabled: bool,
1028 use_modal_editing: bool,
1029 read_only: bool,
1030 leader_id: Option<CollaboratorId>,
1031 remote_id: Option<ViewId>,
1032 pub hover_state: HoverState,
1033 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1034 gutter_hovered: bool,
1035 hovered_link_state: Option<HoveredLinkState>,
1036 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1037 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1038 active_inline_completion: Option<InlineCompletionState>,
1039 /// Used to prevent flickering as the user types while the menu is open
1040 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1041 edit_prediction_settings: EditPredictionSettings,
1042 inline_completions_hidden_for_vim_mode: bool,
1043 show_inline_completions_override: Option<bool>,
1044 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1045 edit_prediction_preview: EditPredictionPreview,
1046 edit_prediction_indent_conflict: bool,
1047 edit_prediction_requires_modifier_in_indent_conflict: bool,
1048 inlay_hint_cache: InlayHintCache,
1049 next_inlay_id: usize,
1050 _subscriptions: Vec<Subscription>,
1051 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1052 gutter_dimensions: GutterDimensions,
1053 style: Option<EditorStyle>,
1054 text_style_refinement: Option<TextStyleRefinement>,
1055 next_editor_action_id: EditorActionId,
1056 editor_actions: Rc<
1057 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1058 >,
1059 use_autoclose: bool,
1060 use_auto_surround: bool,
1061 auto_replace_emoji_shortcode: bool,
1062 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1063 show_git_blame_gutter: bool,
1064 show_git_blame_inline: bool,
1065 show_git_blame_inline_delay_task: Option<Task<()>>,
1066 git_blame_inline_enabled: bool,
1067 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1068 serialize_dirty_buffers: bool,
1069 show_selection_menu: Option<bool>,
1070 blame: Option<Entity<GitBlame>>,
1071 blame_subscription: Option<Subscription>,
1072 custom_context_menu: Option<
1073 Box<
1074 dyn 'static
1075 + Fn(
1076 &mut Self,
1077 DisplayPoint,
1078 &mut Window,
1079 &mut Context<Self>,
1080 ) -> Option<Entity<ui::ContextMenu>>,
1081 >,
1082 >,
1083 last_bounds: Option<Bounds<Pixels>>,
1084 last_position_map: Option<Rc<PositionMap>>,
1085 expect_bounds_change: Option<Bounds<Pixels>>,
1086 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1087 tasks_update_task: Option<Task<()>>,
1088 breakpoint_store: Option<Entity<BreakpointStore>>,
1089 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1090 pull_diagnostics_task: Task<()>,
1091 in_project_search: bool,
1092 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1093 breadcrumb_header: Option<String>,
1094 focused_block: Option<FocusedBlock>,
1095 next_scroll_position: NextScrollCursorCenterTopBottom,
1096 addons: HashMap<TypeId, Box<dyn Addon>>,
1097 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1098 load_diff_task: Option<Shared<Task<()>>>,
1099 /// Whether we are temporarily displaying a diff other than git's
1100 temporary_diff_override: bool,
1101 selection_mark_mode: bool,
1102 toggle_fold_multiple_buffers: Task<()>,
1103 _scroll_cursor_center_top_bottom_task: Task<()>,
1104 serialize_selections: Task<()>,
1105 serialize_folds: Task<()>,
1106 mouse_cursor_hidden: bool,
1107 minimap: Option<Entity<Self>>,
1108 hide_mouse_mode: HideMouseMode,
1109 pub change_list: ChangeList,
1110 inline_value_cache: InlineValueCache,
1111 selection_drag_state: SelectionDragState,
1112 drag_and_drop_selection_enabled: bool,
1113}
1114
1115#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1116enum NextScrollCursorCenterTopBottom {
1117 #[default]
1118 Center,
1119 Top,
1120 Bottom,
1121}
1122
1123impl NextScrollCursorCenterTopBottom {
1124 fn next(&self) -> Self {
1125 match self {
1126 Self::Center => Self::Top,
1127 Self::Top => Self::Bottom,
1128 Self::Bottom => Self::Center,
1129 }
1130 }
1131}
1132
1133#[derive(Clone)]
1134pub struct EditorSnapshot {
1135 pub mode: EditorMode,
1136 show_gutter: bool,
1137 show_line_numbers: Option<bool>,
1138 show_git_diff_gutter: Option<bool>,
1139 show_code_actions: Option<bool>,
1140 show_runnables: Option<bool>,
1141 show_breakpoints: Option<bool>,
1142 git_blame_gutter_max_author_length: Option<usize>,
1143 pub display_snapshot: DisplaySnapshot,
1144 pub placeholder_text: Option<Arc<str>>,
1145 is_focused: bool,
1146 scroll_anchor: ScrollAnchor,
1147 ongoing_scroll: OngoingScroll,
1148 current_line_highlight: CurrentLineHighlight,
1149 gutter_hovered: bool,
1150}
1151
1152#[derive(Default, Debug, Clone, Copy)]
1153pub struct GutterDimensions {
1154 pub left_padding: Pixels,
1155 pub right_padding: Pixels,
1156 pub width: Pixels,
1157 pub margin: Pixels,
1158 pub git_blame_entries_width: Option<Pixels>,
1159}
1160
1161impl GutterDimensions {
1162 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1163 Self {
1164 margin: Self::default_gutter_margin(font_id, font_size, cx),
1165 ..Default::default()
1166 }
1167 }
1168
1169 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1170 -cx.text_system().descent(font_id, font_size)
1171 }
1172 /// The full width of the space taken up by the gutter.
1173 pub fn full_width(&self) -> Pixels {
1174 self.margin + self.width
1175 }
1176
1177 /// The width of the space reserved for the fold indicators,
1178 /// use alongside 'justify_end' and `gutter_width` to
1179 /// right align content with the line numbers
1180 pub fn fold_area_width(&self) -> Pixels {
1181 self.margin + self.right_padding
1182 }
1183}
1184
1185#[derive(Debug)]
1186pub struct RemoteSelection {
1187 pub replica_id: ReplicaId,
1188 pub selection: Selection<Anchor>,
1189 pub cursor_shape: CursorShape,
1190 pub collaborator_id: CollaboratorId,
1191 pub line_mode: bool,
1192 pub user_name: Option<SharedString>,
1193 pub color: PlayerColor,
1194}
1195
1196#[derive(Clone, Debug)]
1197struct SelectionHistoryEntry {
1198 selections: Arc<[Selection<Anchor>]>,
1199 select_next_state: Option<SelectNextState>,
1200 select_prev_state: Option<SelectNextState>,
1201 add_selections_state: Option<AddSelectionsState>,
1202}
1203
1204#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1205enum SelectionHistoryMode {
1206 Normal,
1207 Undoing,
1208 Redoing,
1209 Skipping,
1210}
1211
1212#[derive(Clone, PartialEq, Eq, Hash)]
1213struct HoveredCursor {
1214 replica_id: u16,
1215 selection_id: usize,
1216}
1217
1218impl Default for SelectionHistoryMode {
1219 fn default() -> Self {
1220 Self::Normal
1221 }
1222}
1223
1224struct DeferredSelectionEffectsState {
1225 changed: bool,
1226 should_update_completions: bool,
1227 autoscroll: Option<Autoscroll>,
1228 old_cursor_position: Anchor,
1229 history_entry: SelectionHistoryEntry,
1230}
1231
1232#[derive(Default)]
1233struct SelectionHistory {
1234 #[allow(clippy::type_complexity)]
1235 selections_by_transaction:
1236 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1237 mode: SelectionHistoryMode,
1238 undo_stack: VecDeque<SelectionHistoryEntry>,
1239 redo_stack: VecDeque<SelectionHistoryEntry>,
1240}
1241
1242impl SelectionHistory {
1243 #[track_caller]
1244 fn insert_transaction(
1245 &mut self,
1246 transaction_id: TransactionId,
1247 selections: Arc<[Selection<Anchor>]>,
1248 ) {
1249 if selections.is_empty() {
1250 log::error!(
1251 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1252 std::panic::Location::caller()
1253 );
1254 return;
1255 }
1256 self.selections_by_transaction
1257 .insert(transaction_id, (selections, None));
1258 }
1259
1260 #[allow(clippy::type_complexity)]
1261 fn transaction(
1262 &self,
1263 transaction_id: TransactionId,
1264 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1265 self.selections_by_transaction.get(&transaction_id)
1266 }
1267
1268 #[allow(clippy::type_complexity)]
1269 fn transaction_mut(
1270 &mut self,
1271 transaction_id: TransactionId,
1272 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1273 self.selections_by_transaction.get_mut(&transaction_id)
1274 }
1275
1276 fn push(&mut self, entry: SelectionHistoryEntry) {
1277 if !entry.selections.is_empty() {
1278 match self.mode {
1279 SelectionHistoryMode::Normal => {
1280 self.push_undo(entry);
1281 self.redo_stack.clear();
1282 }
1283 SelectionHistoryMode::Undoing => self.push_redo(entry),
1284 SelectionHistoryMode::Redoing => self.push_undo(entry),
1285 SelectionHistoryMode::Skipping => {}
1286 }
1287 }
1288 }
1289
1290 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1291 if self
1292 .undo_stack
1293 .back()
1294 .map_or(true, |e| e.selections != entry.selections)
1295 {
1296 self.undo_stack.push_back(entry);
1297 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1298 self.undo_stack.pop_front();
1299 }
1300 }
1301 }
1302
1303 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1304 if self
1305 .redo_stack
1306 .back()
1307 .map_or(true, |e| e.selections != entry.selections)
1308 {
1309 self.redo_stack.push_back(entry);
1310 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1311 self.redo_stack.pop_front();
1312 }
1313 }
1314 }
1315}
1316
1317#[derive(Clone, Copy)]
1318pub struct RowHighlightOptions {
1319 pub autoscroll: bool,
1320 pub include_gutter: bool,
1321}
1322
1323impl Default for RowHighlightOptions {
1324 fn default() -> Self {
1325 Self {
1326 autoscroll: Default::default(),
1327 include_gutter: true,
1328 }
1329 }
1330}
1331
1332struct RowHighlight {
1333 index: usize,
1334 range: Range<Anchor>,
1335 color: Hsla,
1336 options: RowHighlightOptions,
1337 type_id: TypeId,
1338}
1339
1340#[derive(Clone, Debug)]
1341struct AddSelectionsState {
1342 groups: Vec<AddSelectionsGroup>,
1343}
1344
1345#[derive(Clone, Debug)]
1346struct AddSelectionsGroup {
1347 above: bool,
1348 stack: Vec<usize>,
1349}
1350
1351#[derive(Clone)]
1352struct SelectNextState {
1353 query: AhoCorasick,
1354 wordwise: bool,
1355 done: bool,
1356}
1357
1358impl std::fmt::Debug for SelectNextState {
1359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1360 f.debug_struct(std::any::type_name::<Self>())
1361 .field("wordwise", &self.wordwise)
1362 .field("done", &self.done)
1363 .finish()
1364 }
1365}
1366
1367#[derive(Debug)]
1368struct AutocloseRegion {
1369 selection_id: usize,
1370 range: Range<Anchor>,
1371 pair: BracketPair,
1372}
1373
1374#[derive(Debug)]
1375struct SnippetState {
1376 ranges: Vec<Vec<Range<Anchor>>>,
1377 active_index: usize,
1378 choices: Vec<Option<Vec<String>>>,
1379}
1380
1381#[doc(hidden)]
1382pub struct RenameState {
1383 pub range: Range<Anchor>,
1384 pub old_name: Arc<str>,
1385 pub editor: Entity<Editor>,
1386 block_id: CustomBlockId,
1387}
1388
1389struct InvalidationStack<T>(Vec<T>);
1390
1391struct RegisteredInlineCompletionProvider {
1392 provider: Arc<dyn InlineCompletionProviderHandle>,
1393 _subscription: Subscription,
1394}
1395
1396#[derive(Debug, PartialEq, Eq)]
1397pub struct ActiveDiagnosticGroup {
1398 pub active_range: Range<Anchor>,
1399 pub active_message: String,
1400 pub group_id: usize,
1401 pub blocks: HashSet<CustomBlockId>,
1402}
1403
1404#[derive(Debug, PartialEq, Eq)]
1405
1406pub(crate) enum ActiveDiagnostic {
1407 None,
1408 All,
1409 Group(ActiveDiagnosticGroup),
1410}
1411
1412#[derive(Serialize, Deserialize, Clone, Debug)]
1413pub struct ClipboardSelection {
1414 /// The number of bytes in this selection.
1415 pub len: usize,
1416 /// Whether this was a full-line selection.
1417 pub is_entire_line: bool,
1418 /// The indentation of the first line when this content was originally copied.
1419 pub first_line_indent: u32,
1420}
1421
1422// selections, scroll behavior, was newest selection reversed
1423type SelectSyntaxNodeHistoryState = (
1424 Box<[Selection<usize>]>,
1425 SelectSyntaxNodeScrollBehavior,
1426 bool,
1427);
1428
1429#[derive(Default)]
1430struct SelectSyntaxNodeHistory {
1431 stack: Vec<SelectSyntaxNodeHistoryState>,
1432 // disable temporarily to allow changing selections without losing the stack
1433 pub disable_clearing: bool,
1434}
1435
1436impl SelectSyntaxNodeHistory {
1437 pub fn try_clear(&mut self) {
1438 if !self.disable_clearing {
1439 self.stack.clear();
1440 }
1441 }
1442
1443 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1444 self.stack.push(selection);
1445 }
1446
1447 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1448 self.stack.pop()
1449 }
1450}
1451
1452enum SelectSyntaxNodeScrollBehavior {
1453 CursorTop,
1454 FitSelection,
1455 CursorBottom,
1456}
1457
1458#[derive(Debug)]
1459pub(crate) struct NavigationData {
1460 cursor_anchor: Anchor,
1461 cursor_position: Point,
1462 scroll_anchor: ScrollAnchor,
1463 scroll_top_row: u32,
1464}
1465
1466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1467pub enum GotoDefinitionKind {
1468 Symbol,
1469 Declaration,
1470 Type,
1471 Implementation,
1472}
1473
1474#[derive(Debug, Clone)]
1475enum InlayHintRefreshReason {
1476 ModifiersChanged(bool),
1477 Toggle(bool),
1478 SettingsChange(InlayHintSettings),
1479 NewLinesShown,
1480 BufferEdited(HashSet<Arc<Language>>),
1481 RefreshRequested,
1482 ExcerptsRemoved(Vec<ExcerptId>),
1483}
1484
1485impl InlayHintRefreshReason {
1486 fn description(&self) -> &'static str {
1487 match self {
1488 Self::ModifiersChanged(_) => "modifiers changed",
1489 Self::Toggle(_) => "toggle",
1490 Self::SettingsChange(_) => "settings change",
1491 Self::NewLinesShown => "new lines shown",
1492 Self::BufferEdited(_) => "buffer edited",
1493 Self::RefreshRequested => "refresh requested",
1494 Self::ExcerptsRemoved(_) => "excerpts removed",
1495 }
1496 }
1497}
1498
1499pub enum FormatTarget {
1500 Buffers,
1501 Ranges(Vec<Range<MultiBufferPoint>>),
1502}
1503
1504pub(crate) struct FocusedBlock {
1505 id: BlockId,
1506 focus_handle: WeakFocusHandle,
1507}
1508
1509#[derive(Clone)]
1510enum JumpData {
1511 MultiBufferRow {
1512 row: MultiBufferRow,
1513 line_offset_from_top: u32,
1514 },
1515 MultiBufferPoint {
1516 excerpt_id: ExcerptId,
1517 position: Point,
1518 anchor: text::Anchor,
1519 line_offset_from_top: u32,
1520 },
1521}
1522
1523pub enum MultibufferSelectionMode {
1524 First,
1525 All,
1526}
1527
1528#[derive(Clone, Copy, Debug, Default)]
1529pub struct RewrapOptions {
1530 pub override_language_settings: bool,
1531 pub preserve_existing_whitespace: bool,
1532}
1533
1534impl Editor {
1535 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1536 let buffer = cx.new(|cx| Buffer::local("", cx));
1537 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1538 Self::new(
1539 EditorMode::SingleLine { auto_width: false },
1540 buffer,
1541 None,
1542 window,
1543 cx,
1544 )
1545 }
1546
1547 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1548 let buffer = cx.new(|cx| Buffer::local("", cx));
1549 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1550 Self::new(EditorMode::full(), buffer, None, window, cx)
1551 }
1552
1553 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1554 let buffer = cx.new(|cx| Buffer::local("", cx));
1555 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1556 Self::new(
1557 EditorMode::SingleLine { auto_width: true },
1558 buffer,
1559 None,
1560 window,
1561 cx,
1562 )
1563 }
1564
1565 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1566 let buffer = cx.new(|cx| Buffer::local("", cx));
1567 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1568 Self::new(
1569 EditorMode::AutoHeight { max_lines },
1570 buffer,
1571 None,
1572 window,
1573 cx,
1574 )
1575 }
1576
1577 pub fn for_buffer(
1578 buffer: Entity<Buffer>,
1579 project: Option<Entity<Project>>,
1580 window: &mut Window,
1581 cx: &mut Context<Self>,
1582 ) -> Self {
1583 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1584 Self::new(EditorMode::full(), buffer, project, window, cx)
1585 }
1586
1587 pub fn for_multibuffer(
1588 buffer: Entity<MultiBuffer>,
1589 project: Option<Entity<Project>>,
1590 window: &mut Window,
1591 cx: &mut Context<Self>,
1592 ) -> Self {
1593 Self::new(EditorMode::full(), buffer, project, window, cx)
1594 }
1595
1596 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1597 let mut clone = Self::new(
1598 self.mode.clone(),
1599 self.buffer.clone(),
1600 self.project.clone(),
1601 window,
1602 cx,
1603 );
1604 self.display_map.update(cx, |display_map, cx| {
1605 let snapshot = display_map.snapshot(cx);
1606 clone.display_map.update(cx, |display_map, cx| {
1607 display_map.set_state(&snapshot, cx);
1608 });
1609 });
1610 clone.folds_did_change(cx);
1611 clone.selections.clone_state(&self.selections);
1612 clone.scroll_manager.clone_state(&self.scroll_manager);
1613 clone.searchable = self.searchable;
1614 clone.read_only = self.read_only;
1615 clone
1616 }
1617
1618 pub fn new(
1619 mode: EditorMode,
1620 buffer: Entity<MultiBuffer>,
1621 project: Option<Entity<Project>>,
1622 window: &mut Window,
1623 cx: &mut Context<Self>,
1624 ) -> Self {
1625 Editor::new_internal(mode, buffer, project, None, window, cx)
1626 }
1627
1628 fn new_internal(
1629 mode: EditorMode,
1630 buffer: Entity<MultiBuffer>,
1631 project: Option<Entity<Project>>,
1632 display_map: Option<Entity<DisplayMap>>,
1633 window: &mut Window,
1634 cx: &mut Context<Self>,
1635 ) -> Self {
1636 debug_assert!(
1637 display_map.is_none() || mode.is_minimap(),
1638 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1639 );
1640
1641 let full_mode = mode.is_full();
1642 let diagnostics_max_severity = if full_mode {
1643 EditorSettings::get_global(cx)
1644 .diagnostics_max_severity
1645 .unwrap_or(DiagnosticSeverity::Hint)
1646 } else {
1647 DiagnosticSeverity::Off
1648 };
1649 let style = window.text_style();
1650 let font_size = style.font_size.to_pixels(window.rem_size());
1651 let editor = cx.entity().downgrade();
1652 let fold_placeholder = FoldPlaceholder {
1653 constrain_width: true,
1654 render: Arc::new(move |fold_id, fold_range, cx| {
1655 let editor = editor.clone();
1656 div()
1657 .id(fold_id)
1658 .bg(cx.theme().colors().ghost_element_background)
1659 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1660 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1661 .rounded_xs()
1662 .size_full()
1663 .cursor_pointer()
1664 .child("⋯")
1665 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1666 .on_click(move |_, _window, cx| {
1667 editor
1668 .update(cx, |editor, cx| {
1669 editor.unfold_ranges(
1670 &[fold_range.start..fold_range.end],
1671 true,
1672 false,
1673 cx,
1674 );
1675 cx.stop_propagation();
1676 })
1677 .ok();
1678 })
1679 .into_any()
1680 }),
1681 merge_adjacent: true,
1682 ..FoldPlaceholder::default()
1683 };
1684 let display_map = display_map.unwrap_or_else(|| {
1685 cx.new(|cx| {
1686 DisplayMap::new(
1687 buffer.clone(),
1688 style.font(),
1689 font_size,
1690 None,
1691 FILE_HEADER_HEIGHT,
1692 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1693 fold_placeholder,
1694 diagnostics_max_severity,
1695 cx,
1696 )
1697 })
1698 });
1699
1700 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1701
1702 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1703
1704 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1705 .then(|| language_settings::SoftWrap::None);
1706
1707 let mut project_subscriptions = Vec::new();
1708 if mode.is_full() {
1709 if let Some(project) = project.as_ref() {
1710 project_subscriptions.push(cx.subscribe_in(
1711 project,
1712 window,
1713 |editor, _, event, window, cx| match event {
1714 project::Event::RefreshCodeLens => {
1715 // we always query lens with actions, without storing them, always refreshing them
1716 }
1717 project::Event::RefreshInlayHints => {
1718 editor
1719 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1720 }
1721 project::Event::LanguageServerAdded(..)
1722 | project::Event::LanguageServerRemoved(..) => {
1723 if editor.tasks_update_task.is_none() {
1724 editor.tasks_update_task =
1725 Some(editor.refresh_runnables(window, cx));
1726 }
1727 editor.pull_diagnostics(None, window, cx);
1728 }
1729 project::Event::SnippetEdit(id, snippet_edits) => {
1730 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1731 let focus_handle = editor.focus_handle(cx);
1732 if focus_handle.is_focused(window) {
1733 let snapshot = buffer.read(cx).snapshot();
1734 for (range, snippet) in snippet_edits {
1735 let editor_range =
1736 language::range_from_lsp(*range).to_offset(&snapshot);
1737 editor
1738 .insert_snippet(
1739 &[editor_range],
1740 snippet.clone(),
1741 window,
1742 cx,
1743 )
1744 .ok();
1745 }
1746 }
1747 }
1748 }
1749 _ => {}
1750 },
1751 ));
1752 if let Some(task_inventory) = project
1753 .read(cx)
1754 .task_store()
1755 .read(cx)
1756 .task_inventory()
1757 .cloned()
1758 {
1759 project_subscriptions.push(cx.observe_in(
1760 &task_inventory,
1761 window,
1762 |editor, _, window, cx| {
1763 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1764 },
1765 ));
1766 };
1767
1768 project_subscriptions.push(cx.subscribe_in(
1769 &project.read(cx).breakpoint_store(),
1770 window,
1771 |editor, _, event, window, cx| match event {
1772 BreakpointStoreEvent::ClearDebugLines => {
1773 editor.clear_row_highlights::<ActiveDebugLine>();
1774 editor.refresh_inline_values(cx);
1775 }
1776 BreakpointStoreEvent::SetDebugLine => {
1777 if editor.go_to_active_debug_line(window, cx) {
1778 cx.stop_propagation();
1779 }
1780
1781 editor.refresh_inline_values(cx);
1782 }
1783 _ => {}
1784 },
1785 ));
1786 }
1787 }
1788
1789 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1790
1791 let inlay_hint_settings =
1792 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1793 let focus_handle = cx.focus_handle();
1794 cx.on_focus(&focus_handle, window, Self::handle_focus)
1795 .detach();
1796 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1797 .detach();
1798 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1799 .detach();
1800 cx.on_blur(&focus_handle, window, Self::handle_blur)
1801 .detach();
1802 cx.observe_pending_input(window, Self::observe_pending_input)
1803 .detach();
1804
1805 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1806 Some(false)
1807 } else {
1808 None
1809 };
1810
1811 let breakpoint_store = match (&mode, project.as_ref()) {
1812 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1813 _ => None,
1814 };
1815
1816 let mut code_action_providers = Vec::new();
1817 let mut load_uncommitted_diff = None;
1818 if let Some(project) = project.clone() {
1819 load_uncommitted_diff = Some(
1820 update_uncommitted_diff_for_buffer(
1821 cx.entity(),
1822 &project,
1823 buffer.read(cx).all_buffers(),
1824 buffer.clone(),
1825 cx,
1826 )
1827 .shared(),
1828 );
1829 code_action_providers.push(Rc::new(project) as Rc<_>);
1830 }
1831
1832 let mut editor = Self {
1833 focus_handle,
1834 show_cursor_when_unfocused: false,
1835 last_focused_descendant: None,
1836 buffer: buffer.clone(),
1837 display_map: display_map.clone(),
1838 selections,
1839 scroll_manager: ScrollManager::new(cx),
1840 columnar_selection_tail: None,
1841 columnar_display_point: None,
1842 add_selections_state: None,
1843 select_next_state: None,
1844 select_prev_state: None,
1845 selection_history: SelectionHistory::default(),
1846 defer_selection_effects: false,
1847 deferred_selection_effects_state: None,
1848 autoclose_regions: Vec::new(),
1849 snippet_stack: InvalidationStack::default(),
1850 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1851 ime_transaction: None,
1852 active_diagnostics: ActiveDiagnostic::None,
1853 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1854 inline_diagnostics_update: Task::ready(()),
1855 inline_diagnostics: Vec::new(),
1856 soft_wrap_mode_override,
1857 diagnostics_max_severity,
1858 hard_wrap: None,
1859 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1860 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1861 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1862 project,
1863 blink_manager: blink_manager.clone(),
1864 show_local_selections: true,
1865 show_scrollbars: ScrollbarAxes {
1866 horizontal: full_mode,
1867 vertical: full_mode,
1868 },
1869 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1870 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1871 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1872 show_gutter: mode.is_full(),
1873 show_line_numbers: None,
1874 use_relative_line_numbers: None,
1875 disable_expand_excerpt_buttons: false,
1876 show_git_diff_gutter: None,
1877 show_code_actions: None,
1878 show_runnables: None,
1879 show_breakpoints: None,
1880 show_wrap_guides: None,
1881 show_indent_guides,
1882 placeholder_text: None,
1883 highlight_order: 0,
1884 highlighted_rows: HashMap::default(),
1885 background_highlights: TreeMap::default(),
1886 gutter_highlights: TreeMap::default(),
1887 scrollbar_marker_state: ScrollbarMarkerState::default(),
1888 active_indent_guides_state: ActiveIndentGuidesState::default(),
1889 nav_history: None,
1890 context_menu: RefCell::new(None),
1891 context_menu_options: None,
1892 mouse_context_menu: None,
1893 completion_tasks: Vec::new(),
1894 inline_blame_popover: None,
1895 signature_help_state: SignatureHelpState::default(),
1896 auto_signature_help: None,
1897 find_all_references_task_sources: Vec::new(),
1898 next_completion_id: 0,
1899 next_inlay_id: 0,
1900 code_action_providers,
1901 available_code_actions: None,
1902 code_actions_task: None,
1903 quick_selection_highlight_task: None,
1904 debounced_selection_highlight_task: None,
1905 document_highlights_task: None,
1906 linked_editing_range_task: None,
1907 pending_rename: None,
1908 searchable: true,
1909 cursor_shape: EditorSettings::get_global(cx)
1910 .cursor_shape
1911 .unwrap_or_default(),
1912 current_line_highlight: None,
1913 autoindent_mode: Some(AutoindentMode::EachLine),
1914 collapse_matches: false,
1915 workspace: None,
1916 input_enabled: true,
1917 use_modal_editing: mode.is_full(),
1918 read_only: mode.is_minimap(),
1919 use_autoclose: true,
1920 use_auto_surround: true,
1921 auto_replace_emoji_shortcode: false,
1922 jsx_tag_auto_close_enabled_in_any_buffer: false,
1923 leader_id: None,
1924 remote_id: None,
1925 hover_state: HoverState::default(),
1926 pending_mouse_down: None,
1927 hovered_link_state: None,
1928 edit_prediction_provider: None,
1929 active_inline_completion: None,
1930 stale_inline_completion_in_menu: None,
1931 edit_prediction_preview: EditPredictionPreview::Inactive {
1932 released_too_fast: false,
1933 },
1934 inline_diagnostics_enabled: mode.is_full(),
1935 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1936 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1937
1938 gutter_hovered: false,
1939 pixel_position_of_newest_cursor: None,
1940 last_bounds: None,
1941 last_position_map: None,
1942 expect_bounds_change: None,
1943 gutter_dimensions: GutterDimensions::default(),
1944 style: None,
1945 show_cursor_names: false,
1946 hovered_cursors: HashMap::default(),
1947 next_editor_action_id: EditorActionId::default(),
1948 editor_actions: Rc::default(),
1949 inline_completions_hidden_for_vim_mode: false,
1950 show_inline_completions_override: None,
1951 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1952 edit_prediction_settings: EditPredictionSettings::Disabled,
1953 edit_prediction_indent_conflict: false,
1954 edit_prediction_requires_modifier_in_indent_conflict: true,
1955 custom_context_menu: None,
1956 show_git_blame_gutter: false,
1957 show_git_blame_inline: false,
1958 show_selection_menu: None,
1959 show_git_blame_inline_delay_task: None,
1960 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1961 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1962 serialize_dirty_buffers: !mode.is_minimap()
1963 && ProjectSettings::get_global(cx)
1964 .session
1965 .restore_unsaved_buffers,
1966 blame: None,
1967 blame_subscription: None,
1968 tasks: BTreeMap::default(),
1969
1970 breakpoint_store,
1971 gutter_breakpoint_indicator: (None, None),
1972 _subscriptions: vec![
1973 cx.observe(&buffer, Self::on_buffer_changed),
1974 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1975 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1976 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1977 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1978 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1979 cx.observe_window_activation(window, |editor, window, cx| {
1980 let active = window.is_window_active();
1981 editor.blink_manager.update(cx, |blink_manager, cx| {
1982 if active {
1983 blink_manager.enable(cx);
1984 } else {
1985 blink_manager.disable(cx);
1986 }
1987 });
1988 if active {
1989 editor.show_mouse_cursor();
1990 }
1991 }),
1992 ],
1993 tasks_update_task: None,
1994 pull_diagnostics_task: Task::ready(()),
1995 linked_edit_ranges: Default::default(),
1996 in_project_search: false,
1997 previous_search_ranges: None,
1998 breadcrumb_header: None,
1999 focused_block: None,
2000 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2001 addons: HashMap::default(),
2002 registered_buffers: HashMap::default(),
2003 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2004 selection_mark_mode: false,
2005 toggle_fold_multiple_buffers: Task::ready(()),
2006 serialize_selections: Task::ready(()),
2007 serialize_folds: Task::ready(()),
2008 text_style_refinement: None,
2009 load_diff_task: load_uncommitted_diff,
2010 temporary_diff_override: false,
2011 mouse_cursor_hidden: false,
2012 minimap: None,
2013 hide_mouse_mode: EditorSettings::get_global(cx)
2014 .hide_mouse
2015 .unwrap_or_default(),
2016 change_list: ChangeList::new(),
2017 mode,
2018 selection_drag_state: SelectionDragState::None,
2019 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2020 };
2021 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2022 editor
2023 ._subscriptions
2024 .push(cx.observe(breakpoints, |_, _, cx| {
2025 cx.notify();
2026 }));
2027 }
2028 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2029 editor._subscriptions.extend(project_subscriptions);
2030
2031 editor._subscriptions.push(cx.subscribe_in(
2032 &cx.entity(),
2033 window,
2034 |editor, _, e: &EditorEvent, window, cx| match e {
2035 EditorEvent::ScrollPositionChanged { local, .. } => {
2036 if *local {
2037 let new_anchor = editor.scroll_manager.anchor();
2038 let snapshot = editor.snapshot(window, cx);
2039 editor.update_restoration_data(cx, move |data| {
2040 data.scroll_position = (
2041 new_anchor.top_row(&snapshot.buffer_snapshot),
2042 new_anchor.offset,
2043 );
2044 });
2045 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2046 editor.inline_blame_popover.take();
2047 }
2048 }
2049 EditorEvent::Edited { .. } => {
2050 if !vim_enabled(cx) {
2051 let (map, selections) = editor.selections.all_adjusted_display(cx);
2052 let pop_state = editor
2053 .change_list
2054 .last()
2055 .map(|previous| {
2056 previous.len() == selections.len()
2057 && previous.iter().enumerate().all(|(ix, p)| {
2058 p.to_display_point(&map).row()
2059 == selections[ix].head().row()
2060 })
2061 })
2062 .unwrap_or(false);
2063 let new_positions = selections
2064 .into_iter()
2065 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2066 .collect();
2067 editor
2068 .change_list
2069 .push_to_change_list(pop_state, new_positions);
2070 }
2071 }
2072 _ => (),
2073 },
2074 ));
2075
2076 if let Some(dap_store) = editor
2077 .project
2078 .as_ref()
2079 .map(|project| project.read(cx).dap_store())
2080 {
2081 let weak_editor = cx.weak_entity();
2082
2083 editor
2084 ._subscriptions
2085 .push(
2086 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2087 let session_entity = cx.entity();
2088 weak_editor
2089 .update(cx, |editor, cx| {
2090 editor._subscriptions.push(
2091 cx.subscribe(&session_entity, Self::on_debug_session_event),
2092 );
2093 })
2094 .ok();
2095 }),
2096 );
2097
2098 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2099 editor
2100 ._subscriptions
2101 .push(cx.subscribe(&session, Self::on_debug_session_event));
2102 }
2103 }
2104
2105 // skip adding the initial selection to selection history
2106 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2107 editor.end_selection(window, cx);
2108 editor.selection_history.mode = SelectionHistoryMode::Normal;
2109
2110 editor.scroll_manager.show_scrollbars(window, cx);
2111 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2112
2113 if full_mode {
2114 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2115 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2116
2117 if editor.git_blame_inline_enabled {
2118 editor.start_git_blame_inline(false, window, cx);
2119 }
2120
2121 editor.go_to_active_debug_line(window, cx);
2122
2123 if let Some(buffer) = buffer.read(cx).as_singleton() {
2124 if let Some(project) = editor.project.as_ref() {
2125 let handle = project.update(cx, |project, cx| {
2126 project.register_buffer_with_language_servers(&buffer, cx)
2127 });
2128 editor
2129 .registered_buffers
2130 .insert(buffer.read(cx).remote_id(), handle);
2131 }
2132 }
2133
2134 editor.minimap =
2135 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2136 editor.pull_diagnostics(None, window, cx);
2137 }
2138
2139 editor.report_editor_event("Editor Opened", None, cx);
2140 editor
2141 }
2142
2143 pub fn deploy_mouse_context_menu(
2144 &mut self,
2145 position: gpui::Point<Pixels>,
2146 context_menu: Entity<ContextMenu>,
2147 window: &mut Window,
2148 cx: &mut Context<Self>,
2149 ) {
2150 self.mouse_context_menu = Some(MouseContextMenu::new(
2151 self,
2152 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2153 context_menu,
2154 window,
2155 cx,
2156 ));
2157 }
2158
2159 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2160 self.mouse_context_menu
2161 .as_ref()
2162 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2163 }
2164
2165 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2166 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2167 }
2168
2169 fn key_context_internal(
2170 &self,
2171 has_active_edit_prediction: bool,
2172 window: &Window,
2173 cx: &App,
2174 ) -> KeyContext {
2175 let mut key_context = KeyContext::new_with_defaults();
2176 key_context.add("Editor");
2177 let mode = match self.mode {
2178 EditorMode::SingleLine { .. } => "single_line",
2179 EditorMode::AutoHeight { .. } => "auto_height",
2180 EditorMode::Minimap { .. } => "minimap",
2181 EditorMode::Full { .. } => "full",
2182 };
2183
2184 if EditorSettings::jupyter_enabled(cx) {
2185 key_context.add("jupyter");
2186 }
2187
2188 key_context.set("mode", mode);
2189 if self.pending_rename.is_some() {
2190 key_context.add("renaming");
2191 }
2192
2193 match self.context_menu.borrow().as_ref() {
2194 Some(CodeContextMenu::Completions(_)) => {
2195 key_context.add("menu");
2196 key_context.add("showing_completions");
2197 }
2198 Some(CodeContextMenu::CodeActions(_)) => {
2199 key_context.add("menu");
2200 key_context.add("showing_code_actions")
2201 }
2202 None => {}
2203 }
2204
2205 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2206 if !self.focus_handle(cx).contains_focused(window, cx)
2207 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2208 {
2209 for addon in self.addons.values() {
2210 addon.extend_key_context(&mut key_context, cx)
2211 }
2212 }
2213
2214 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2215 if let Some(extension) = singleton_buffer
2216 .read(cx)
2217 .file()
2218 .and_then(|file| file.path().extension()?.to_str())
2219 {
2220 key_context.set("extension", extension.to_string());
2221 }
2222 } else {
2223 key_context.add("multibuffer");
2224 }
2225
2226 if has_active_edit_prediction {
2227 if self.edit_prediction_in_conflict() {
2228 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2229 } else {
2230 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2231 key_context.add("copilot_suggestion");
2232 }
2233 }
2234
2235 if self.selection_mark_mode {
2236 key_context.add("selection_mode");
2237 }
2238
2239 key_context
2240 }
2241
2242 fn show_mouse_cursor(&mut self) {
2243 self.mouse_cursor_hidden = false;
2244 }
2245
2246 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2247 self.mouse_cursor_hidden = match origin {
2248 HideMouseCursorOrigin::TypingAction => {
2249 matches!(
2250 self.hide_mouse_mode,
2251 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2252 )
2253 }
2254 HideMouseCursorOrigin::MovementAction => {
2255 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2256 }
2257 };
2258 }
2259
2260 pub fn edit_prediction_in_conflict(&self) -> bool {
2261 if !self.show_edit_predictions_in_menu() {
2262 return false;
2263 }
2264
2265 let showing_completions = self
2266 .context_menu
2267 .borrow()
2268 .as_ref()
2269 .map_or(false, |context| {
2270 matches!(context, CodeContextMenu::Completions(_))
2271 });
2272
2273 showing_completions
2274 || self.edit_prediction_requires_modifier()
2275 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2276 // bindings to insert tab characters.
2277 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2278 }
2279
2280 pub fn accept_edit_prediction_keybind(
2281 &self,
2282 accept_partial: bool,
2283 window: &Window,
2284 cx: &App,
2285 ) -> AcceptEditPredictionBinding {
2286 let key_context = self.key_context_internal(true, window, cx);
2287 let in_conflict = self.edit_prediction_in_conflict();
2288
2289 let bindings = if accept_partial {
2290 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2291 } else {
2292 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2293 };
2294
2295 // TODO: if the binding contains multiple keystrokes, display all of them, not
2296 // just the first one.
2297 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2298 !in_conflict
2299 || binding
2300 .keystrokes()
2301 .first()
2302 .map_or(false, |keystroke| keystroke.modifiers.modified())
2303 }))
2304 }
2305
2306 pub fn new_file(
2307 workspace: &mut Workspace,
2308 _: &workspace::NewFile,
2309 window: &mut Window,
2310 cx: &mut Context<Workspace>,
2311 ) {
2312 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2313 "Failed to create buffer",
2314 window,
2315 cx,
2316 |e, _, _| match e.error_code() {
2317 ErrorCode::RemoteUpgradeRequired => Some(format!(
2318 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2319 e.error_tag("required").unwrap_or("the latest version")
2320 )),
2321 _ => None,
2322 },
2323 );
2324 }
2325
2326 pub fn new_in_workspace(
2327 workspace: &mut Workspace,
2328 window: &mut Window,
2329 cx: &mut Context<Workspace>,
2330 ) -> Task<Result<Entity<Editor>>> {
2331 let project = workspace.project().clone();
2332 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2333
2334 cx.spawn_in(window, async move |workspace, cx| {
2335 let buffer = create.await?;
2336 workspace.update_in(cx, |workspace, window, cx| {
2337 let editor =
2338 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2339 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2340 editor
2341 })
2342 })
2343 }
2344
2345 fn new_file_vertical(
2346 workspace: &mut Workspace,
2347 _: &workspace::NewFileSplitVertical,
2348 window: &mut Window,
2349 cx: &mut Context<Workspace>,
2350 ) {
2351 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2352 }
2353
2354 fn new_file_horizontal(
2355 workspace: &mut Workspace,
2356 _: &workspace::NewFileSplitHorizontal,
2357 window: &mut Window,
2358 cx: &mut Context<Workspace>,
2359 ) {
2360 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2361 }
2362
2363 fn new_file_in_direction(
2364 workspace: &mut Workspace,
2365 direction: SplitDirection,
2366 window: &mut Window,
2367 cx: &mut Context<Workspace>,
2368 ) {
2369 let project = workspace.project().clone();
2370 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2371
2372 cx.spawn_in(window, async move |workspace, cx| {
2373 let buffer = create.await?;
2374 workspace.update_in(cx, move |workspace, window, cx| {
2375 workspace.split_item(
2376 direction,
2377 Box::new(
2378 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2379 ),
2380 window,
2381 cx,
2382 )
2383 })?;
2384 anyhow::Ok(())
2385 })
2386 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2387 match e.error_code() {
2388 ErrorCode::RemoteUpgradeRequired => Some(format!(
2389 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2390 e.error_tag("required").unwrap_or("the latest version")
2391 )),
2392 _ => None,
2393 }
2394 });
2395 }
2396
2397 pub fn leader_id(&self) -> Option<CollaboratorId> {
2398 self.leader_id
2399 }
2400
2401 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2402 &self.buffer
2403 }
2404
2405 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2406 self.workspace.as_ref()?.0.upgrade()
2407 }
2408
2409 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2410 self.buffer().read(cx).title(cx)
2411 }
2412
2413 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2414 let git_blame_gutter_max_author_length = self
2415 .render_git_blame_gutter(cx)
2416 .then(|| {
2417 if let Some(blame) = self.blame.as_ref() {
2418 let max_author_length =
2419 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2420 Some(max_author_length)
2421 } else {
2422 None
2423 }
2424 })
2425 .flatten();
2426
2427 EditorSnapshot {
2428 mode: self.mode.clone(),
2429 show_gutter: self.show_gutter,
2430 show_line_numbers: self.show_line_numbers,
2431 show_git_diff_gutter: self.show_git_diff_gutter,
2432 show_code_actions: self.show_code_actions,
2433 show_runnables: self.show_runnables,
2434 show_breakpoints: self.show_breakpoints,
2435 git_blame_gutter_max_author_length,
2436 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2437 scroll_anchor: self.scroll_manager.anchor(),
2438 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2439 placeholder_text: self.placeholder_text.clone(),
2440 is_focused: self.focus_handle.is_focused(window),
2441 current_line_highlight: self
2442 .current_line_highlight
2443 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2444 gutter_hovered: self.gutter_hovered,
2445 }
2446 }
2447
2448 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2449 self.buffer.read(cx).language_at(point, cx)
2450 }
2451
2452 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2453 self.buffer.read(cx).read(cx).file_at(point).cloned()
2454 }
2455
2456 pub fn active_excerpt(
2457 &self,
2458 cx: &App,
2459 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2460 self.buffer
2461 .read(cx)
2462 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2463 }
2464
2465 pub fn mode(&self) -> &EditorMode {
2466 &self.mode
2467 }
2468
2469 pub fn set_mode(&mut self, mode: EditorMode) {
2470 self.mode = mode;
2471 }
2472
2473 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2474 self.collaboration_hub.as_deref()
2475 }
2476
2477 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2478 self.collaboration_hub = Some(hub);
2479 }
2480
2481 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2482 self.in_project_search = in_project_search;
2483 }
2484
2485 pub fn set_custom_context_menu(
2486 &mut self,
2487 f: impl 'static
2488 + Fn(
2489 &mut Self,
2490 DisplayPoint,
2491 &mut Window,
2492 &mut Context<Self>,
2493 ) -> Option<Entity<ui::ContextMenu>>,
2494 ) {
2495 self.custom_context_menu = Some(Box::new(f))
2496 }
2497
2498 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2499 self.completion_provider = provider;
2500 }
2501
2502 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2503 self.semantics_provider.clone()
2504 }
2505
2506 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2507 self.semantics_provider = provider;
2508 }
2509
2510 pub fn set_edit_prediction_provider<T>(
2511 &mut self,
2512 provider: Option<Entity<T>>,
2513 window: &mut Window,
2514 cx: &mut Context<Self>,
2515 ) where
2516 T: EditPredictionProvider,
2517 {
2518 self.edit_prediction_provider =
2519 provider.map(|provider| RegisteredInlineCompletionProvider {
2520 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2521 if this.focus_handle.is_focused(window) {
2522 this.update_visible_inline_completion(window, cx);
2523 }
2524 }),
2525 provider: Arc::new(provider),
2526 });
2527 self.update_edit_prediction_settings(cx);
2528 self.refresh_inline_completion(false, false, window, cx);
2529 }
2530
2531 pub fn placeholder_text(&self) -> Option<&str> {
2532 self.placeholder_text.as_deref()
2533 }
2534
2535 pub fn set_placeholder_text(
2536 &mut self,
2537 placeholder_text: impl Into<Arc<str>>,
2538 cx: &mut Context<Self>,
2539 ) {
2540 let placeholder_text = Some(placeholder_text.into());
2541 if self.placeholder_text != placeholder_text {
2542 self.placeholder_text = placeholder_text;
2543 cx.notify();
2544 }
2545 }
2546
2547 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2548 self.cursor_shape = cursor_shape;
2549
2550 // Disrupt blink for immediate user feedback that the cursor shape has changed
2551 self.blink_manager.update(cx, BlinkManager::show_cursor);
2552
2553 cx.notify();
2554 }
2555
2556 pub fn set_current_line_highlight(
2557 &mut self,
2558 current_line_highlight: Option<CurrentLineHighlight>,
2559 ) {
2560 self.current_line_highlight = current_line_highlight;
2561 }
2562
2563 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2564 self.collapse_matches = collapse_matches;
2565 }
2566
2567 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2568 let buffers = self.buffer.read(cx).all_buffers();
2569 let Some(project) = self.project.as_ref() else {
2570 return;
2571 };
2572 project.update(cx, |project, cx| {
2573 for buffer in buffers {
2574 self.registered_buffers
2575 .entry(buffer.read(cx).remote_id())
2576 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2577 }
2578 })
2579 }
2580
2581 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2582 if self.collapse_matches {
2583 return range.start..range.start;
2584 }
2585 range.clone()
2586 }
2587
2588 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2589 if self.display_map.read(cx).clip_at_line_ends != clip {
2590 self.display_map
2591 .update(cx, |map, _| map.clip_at_line_ends = clip);
2592 }
2593 }
2594
2595 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2596 self.input_enabled = input_enabled;
2597 }
2598
2599 pub fn set_inline_completions_hidden_for_vim_mode(
2600 &mut self,
2601 hidden: bool,
2602 window: &mut Window,
2603 cx: &mut Context<Self>,
2604 ) {
2605 if hidden != self.inline_completions_hidden_for_vim_mode {
2606 self.inline_completions_hidden_for_vim_mode = hidden;
2607 if hidden {
2608 self.update_visible_inline_completion(window, cx);
2609 } else {
2610 self.refresh_inline_completion(true, false, window, cx);
2611 }
2612 }
2613 }
2614
2615 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2616 self.menu_inline_completions_policy = value;
2617 }
2618
2619 pub fn set_autoindent(&mut self, autoindent: bool) {
2620 if autoindent {
2621 self.autoindent_mode = Some(AutoindentMode::EachLine);
2622 } else {
2623 self.autoindent_mode = None;
2624 }
2625 }
2626
2627 pub fn read_only(&self, cx: &App) -> bool {
2628 self.read_only || self.buffer.read(cx).read_only()
2629 }
2630
2631 pub fn set_read_only(&mut self, read_only: bool) {
2632 self.read_only = read_only;
2633 }
2634
2635 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2636 self.use_autoclose = autoclose;
2637 }
2638
2639 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2640 self.use_auto_surround = auto_surround;
2641 }
2642
2643 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2644 self.auto_replace_emoji_shortcode = auto_replace;
2645 }
2646
2647 pub fn toggle_edit_predictions(
2648 &mut self,
2649 _: &ToggleEditPrediction,
2650 window: &mut Window,
2651 cx: &mut Context<Self>,
2652 ) {
2653 if self.show_inline_completions_override.is_some() {
2654 self.set_show_edit_predictions(None, window, cx);
2655 } else {
2656 let show_edit_predictions = !self.edit_predictions_enabled();
2657 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2658 }
2659 }
2660
2661 pub fn set_show_edit_predictions(
2662 &mut self,
2663 show_edit_predictions: Option<bool>,
2664 window: &mut Window,
2665 cx: &mut Context<Self>,
2666 ) {
2667 self.show_inline_completions_override = show_edit_predictions;
2668 self.update_edit_prediction_settings(cx);
2669
2670 if let Some(false) = show_edit_predictions {
2671 self.discard_inline_completion(false, cx);
2672 } else {
2673 self.refresh_inline_completion(false, true, window, cx);
2674 }
2675 }
2676
2677 fn inline_completions_disabled_in_scope(
2678 &self,
2679 buffer: &Entity<Buffer>,
2680 buffer_position: language::Anchor,
2681 cx: &App,
2682 ) -> bool {
2683 let snapshot = buffer.read(cx).snapshot();
2684 let settings = snapshot.settings_at(buffer_position, cx);
2685
2686 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2687 return false;
2688 };
2689
2690 scope.override_name().map_or(false, |scope_name| {
2691 settings
2692 .edit_predictions_disabled_in
2693 .iter()
2694 .any(|s| s == scope_name)
2695 })
2696 }
2697
2698 pub fn set_use_modal_editing(&mut self, to: bool) {
2699 self.use_modal_editing = to;
2700 }
2701
2702 pub fn use_modal_editing(&self) -> bool {
2703 self.use_modal_editing
2704 }
2705
2706 fn selections_did_change(
2707 &mut self,
2708 local: bool,
2709 old_cursor_position: &Anchor,
2710 should_update_completions: bool,
2711 window: &mut Window,
2712 cx: &mut Context<Self>,
2713 ) {
2714 window.invalidate_character_coordinates();
2715
2716 // Copy selections to primary selection buffer
2717 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2718 if local {
2719 let selections = self.selections.all::<usize>(cx);
2720 let buffer_handle = self.buffer.read(cx).read(cx);
2721
2722 let mut text = String::new();
2723 for (index, selection) in selections.iter().enumerate() {
2724 let text_for_selection = buffer_handle
2725 .text_for_range(selection.start..selection.end)
2726 .collect::<String>();
2727
2728 text.push_str(&text_for_selection);
2729 if index != selections.len() - 1 {
2730 text.push('\n');
2731 }
2732 }
2733
2734 if !text.is_empty() {
2735 cx.write_to_primary(ClipboardItem::new_string(text));
2736 }
2737 }
2738
2739 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2740 self.buffer.update(cx, |buffer, cx| {
2741 buffer.set_active_selections(
2742 &self.selections.disjoint_anchors(),
2743 self.selections.line_mode,
2744 self.cursor_shape,
2745 cx,
2746 )
2747 });
2748 }
2749 let display_map = self
2750 .display_map
2751 .update(cx, |display_map, cx| display_map.snapshot(cx));
2752 let buffer = &display_map.buffer_snapshot;
2753 if self.selections.count() == 1 {
2754 self.add_selections_state = None;
2755 }
2756 self.select_next_state = None;
2757 self.select_prev_state = None;
2758 self.select_syntax_node_history.try_clear();
2759 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2760 self.snippet_stack
2761 .invalidate(&self.selections.disjoint_anchors(), buffer);
2762 self.take_rename(false, window, cx);
2763
2764 let newest_selection = self.selections.newest_anchor();
2765 let new_cursor_position = newest_selection.head();
2766 let selection_start = newest_selection.start;
2767
2768 self.push_to_nav_history(
2769 *old_cursor_position,
2770 Some(new_cursor_position.to_point(buffer)),
2771 false,
2772 cx,
2773 );
2774
2775 if local {
2776 if let Some(buffer_id) = new_cursor_position.buffer_id {
2777 if !self.registered_buffers.contains_key(&buffer_id) {
2778 if let Some(project) = self.project.as_ref() {
2779 project.update(cx, |project, cx| {
2780 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2781 return;
2782 };
2783 self.registered_buffers.insert(
2784 buffer_id,
2785 project.register_buffer_with_language_servers(&buffer, cx),
2786 );
2787 })
2788 }
2789 }
2790 }
2791
2792 let mut context_menu = self.context_menu.borrow_mut();
2793 let completion_menu = match context_menu.as_ref() {
2794 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2795 Some(CodeContextMenu::CodeActions(_)) => {
2796 *context_menu = None;
2797 None
2798 }
2799 None => None,
2800 };
2801 let completion_position = completion_menu.map(|menu| menu.initial_position);
2802 drop(context_menu);
2803
2804 if should_update_completions {
2805 if let Some(completion_position) = completion_position {
2806 let start_offset = selection_start.to_offset(buffer);
2807 let position_matches = start_offset == completion_position.to_offset(buffer);
2808 let continue_showing = if position_matches {
2809 if self.snippet_stack.is_empty() {
2810 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2811 } else {
2812 // Snippet choices can be shown even when the cursor is in whitespace.
2813 // Dismissing the menu when actions like backspace
2814 true
2815 }
2816 } else {
2817 false
2818 };
2819
2820 if continue_showing {
2821 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2822 } else {
2823 self.hide_context_menu(window, cx);
2824 }
2825 }
2826 }
2827
2828 hide_hover(self, cx);
2829
2830 if old_cursor_position.to_display_point(&display_map).row()
2831 != new_cursor_position.to_display_point(&display_map).row()
2832 {
2833 self.available_code_actions.take();
2834 }
2835 self.refresh_code_actions(window, cx);
2836 self.refresh_document_highlights(cx);
2837 self.refresh_selected_text_highlights(false, window, cx);
2838 refresh_matching_bracket_highlights(self, window, cx);
2839 self.update_visible_inline_completion(window, cx);
2840 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2841 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2842 self.inline_blame_popover.take();
2843 if self.git_blame_inline_enabled {
2844 self.start_inline_blame_timer(window, cx);
2845 }
2846 }
2847
2848 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2849 cx.emit(EditorEvent::SelectionsChanged { local });
2850
2851 let selections = &self.selections.disjoint;
2852 if selections.len() == 1 {
2853 cx.emit(SearchEvent::ActiveMatchChanged)
2854 }
2855 if local {
2856 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2857 let inmemory_selections = selections
2858 .iter()
2859 .map(|s| {
2860 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2861 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2862 })
2863 .collect();
2864 self.update_restoration_data(cx, |data| {
2865 data.selections = inmemory_selections;
2866 });
2867
2868 if WorkspaceSettings::get(None, cx).restore_on_startup
2869 != RestoreOnStartupBehavior::None
2870 {
2871 if let Some(workspace_id) =
2872 self.workspace.as_ref().and_then(|workspace| workspace.1)
2873 {
2874 let snapshot = self.buffer().read(cx).snapshot(cx);
2875 let selections = selections.clone();
2876 let background_executor = cx.background_executor().clone();
2877 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2878 self.serialize_selections = cx.background_spawn(async move {
2879 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2880 let db_selections = selections
2881 .iter()
2882 .map(|selection| {
2883 (
2884 selection.start.to_offset(&snapshot),
2885 selection.end.to_offset(&snapshot),
2886 )
2887 })
2888 .collect();
2889
2890 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2891 .await
2892 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2893 .log_err();
2894 });
2895 }
2896 }
2897 }
2898 }
2899
2900 cx.notify();
2901 }
2902
2903 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2904 use text::ToOffset as _;
2905 use text::ToPoint as _;
2906
2907 if self.mode.is_minimap()
2908 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2909 {
2910 return;
2911 }
2912
2913 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2914 return;
2915 };
2916
2917 let snapshot = singleton.read(cx).snapshot();
2918 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2919 let display_snapshot = display_map.snapshot(cx);
2920
2921 display_snapshot
2922 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2923 .map(|fold| {
2924 fold.range.start.text_anchor.to_point(&snapshot)
2925 ..fold.range.end.text_anchor.to_point(&snapshot)
2926 })
2927 .collect()
2928 });
2929 self.update_restoration_data(cx, |data| {
2930 data.folds = inmemory_folds;
2931 });
2932
2933 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2934 return;
2935 };
2936 let background_executor = cx.background_executor().clone();
2937 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2938 let db_folds = self.display_map.update(cx, |display_map, cx| {
2939 display_map
2940 .snapshot(cx)
2941 .folds_in_range(0..snapshot.len())
2942 .map(|fold| {
2943 (
2944 fold.range.start.text_anchor.to_offset(&snapshot),
2945 fold.range.end.text_anchor.to_offset(&snapshot),
2946 )
2947 })
2948 .collect()
2949 });
2950 self.serialize_folds = cx.background_spawn(async move {
2951 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2952 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2953 .await
2954 .with_context(|| {
2955 format!(
2956 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2957 )
2958 })
2959 .log_err();
2960 });
2961 }
2962
2963 pub fn sync_selections(
2964 &mut self,
2965 other: Entity<Editor>,
2966 cx: &mut Context<Self>,
2967 ) -> gpui::Subscription {
2968 let other_selections = other.read(cx).selections.disjoint.to_vec();
2969 self.selections.change_with(cx, |selections| {
2970 selections.select_anchors(other_selections);
2971 });
2972
2973 let other_subscription =
2974 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2975 EditorEvent::SelectionsChanged { local: true } => {
2976 let other_selections = other.read(cx).selections.disjoint.to_vec();
2977 if other_selections.is_empty() {
2978 return;
2979 }
2980 this.selections.change_with(cx, |selections| {
2981 selections.select_anchors(other_selections);
2982 });
2983 }
2984 _ => {}
2985 });
2986
2987 let this_subscription =
2988 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2989 EditorEvent::SelectionsChanged { local: true } => {
2990 let these_selections = this.selections.disjoint.to_vec();
2991 if these_selections.is_empty() {
2992 return;
2993 }
2994 other.update(cx, |other_editor, cx| {
2995 other_editor.selections.change_with(cx, |selections| {
2996 selections.select_anchors(these_selections);
2997 })
2998 });
2999 }
3000 _ => {}
3001 });
3002
3003 Subscription::join(other_subscription, this_subscription)
3004 }
3005
3006 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3007 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3008 /// effects of selection change occur at the end of the transaction.
3009 pub fn change_selections<R>(
3010 &mut self,
3011 autoscroll: Option<Autoscroll>,
3012 window: &mut Window,
3013 cx: &mut Context<Self>,
3014 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3015 ) -> R {
3016 self.change_selections_inner(true, autoscroll, window, cx, change)
3017 }
3018
3019 pub(crate) fn change_selections_without_updating_completions<R>(
3020 &mut self,
3021 autoscroll: Option<Autoscroll>,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3025 ) -> R {
3026 self.change_selections_inner(false, autoscroll, window, cx, change)
3027 }
3028
3029 fn change_selections_inner<R>(
3030 &mut self,
3031 should_update_completions: bool,
3032 autoscroll: Option<Autoscroll>,
3033 window: &mut Window,
3034 cx: &mut Context<Self>,
3035 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3036 ) -> R {
3037 if let Some(state) = &mut self.deferred_selection_effects_state {
3038 state.autoscroll = autoscroll.or(state.autoscroll);
3039 state.should_update_completions = should_update_completions;
3040 let (changed, result) = self.selections.change_with(cx, change);
3041 state.changed |= changed;
3042 return result;
3043 }
3044 let mut state = DeferredSelectionEffectsState {
3045 changed: false,
3046 should_update_completions,
3047 autoscroll,
3048 old_cursor_position: self.selections.newest_anchor().head(),
3049 history_entry: SelectionHistoryEntry {
3050 selections: self.selections.disjoint_anchors(),
3051 select_next_state: self.select_next_state.clone(),
3052 select_prev_state: self.select_prev_state.clone(),
3053 add_selections_state: self.add_selections_state.clone(),
3054 },
3055 };
3056 let (changed, result) = self.selections.change_with(cx, change);
3057 state.changed = state.changed || changed;
3058 if self.defer_selection_effects {
3059 self.deferred_selection_effects_state = Some(state);
3060 } else {
3061 self.apply_selection_effects(state, window, cx);
3062 }
3063 result
3064 }
3065
3066 /// Defers the effects of selection change, so that the effects of multiple calls to
3067 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3068 /// to selection history and the state of popovers based on selection position aren't
3069 /// erroneously updated.
3070 pub fn with_selection_effects_deferred<R>(
3071 &mut self,
3072 window: &mut Window,
3073 cx: &mut Context<Self>,
3074 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3075 ) -> R {
3076 let already_deferred = self.defer_selection_effects;
3077 self.defer_selection_effects = true;
3078 let result = update(self, window, cx);
3079 if !already_deferred {
3080 self.defer_selection_effects = false;
3081 if let Some(state) = self.deferred_selection_effects_state.take() {
3082 self.apply_selection_effects(state, window, cx);
3083 }
3084 }
3085 result
3086 }
3087
3088 fn apply_selection_effects(
3089 &mut self,
3090 state: DeferredSelectionEffectsState,
3091 window: &mut Window,
3092 cx: &mut Context<Self>,
3093 ) {
3094 if state.changed {
3095 self.selection_history.push(state.history_entry);
3096
3097 if let Some(autoscroll) = state.autoscroll {
3098 self.request_autoscroll(autoscroll, cx);
3099 }
3100
3101 let old_cursor_position = &state.old_cursor_position;
3102
3103 self.selections_did_change(
3104 true,
3105 &old_cursor_position,
3106 state.should_update_completions,
3107 window,
3108 cx,
3109 );
3110
3111 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3112 self.show_signature_help(&ShowSignatureHelp, window, cx);
3113 }
3114 }
3115 }
3116
3117 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3118 where
3119 I: IntoIterator<Item = (Range<S>, T)>,
3120 S: ToOffset,
3121 T: Into<Arc<str>>,
3122 {
3123 if self.read_only(cx) {
3124 return;
3125 }
3126
3127 self.buffer
3128 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3129 }
3130
3131 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3132 where
3133 I: IntoIterator<Item = (Range<S>, T)>,
3134 S: ToOffset,
3135 T: Into<Arc<str>>,
3136 {
3137 if self.read_only(cx) {
3138 return;
3139 }
3140
3141 self.buffer.update(cx, |buffer, cx| {
3142 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3143 });
3144 }
3145
3146 pub fn edit_with_block_indent<I, S, T>(
3147 &mut self,
3148 edits: I,
3149 original_indent_columns: Vec<Option<u32>>,
3150 cx: &mut Context<Self>,
3151 ) where
3152 I: IntoIterator<Item = (Range<S>, T)>,
3153 S: ToOffset,
3154 T: Into<Arc<str>>,
3155 {
3156 if self.read_only(cx) {
3157 return;
3158 }
3159
3160 self.buffer.update(cx, |buffer, cx| {
3161 buffer.edit(
3162 edits,
3163 Some(AutoindentMode::Block {
3164 original_indent_columns,
3165 }),
3166 cx,
3167 )
3168 });
3169 }
3170
3171 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3172 self.hide_context_menu(window, cx);
3173
3174 match phase {
3175 SelectPhase::Begin {
3176 position,
3177 add,
3178 click_count,
3179 } => self.begin_selection(position, add, click_count, window, cx),
3180 SelectPhase::BeginColumnar {
3181 position,
3182 goal_column,
3183 reset,
3184 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3185 SelectPhase::Extend {
3186 position,
3187 click_count,
3188 } => self.extend_selection(position, click_count, window, cx),
3189 SelectPhase::Update {
3190 position,
3191 goal_column,
3192 scroll_delta,
3193 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3194 SelectPhase::End => self.end_selection(window, cx),
3195 }
3196 }
3197
3198 fn extend_selection(
3199 &mut self,
3200 position: DisplayPoint,
3201 click_count: usize,
3202 window: &mut Window,
3203 cx: &mut Context<Self>,
3204 ) {
3205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3206 let tail = self.selections.newest::<usize>(cx).tail();
3207 self.begin_selection(position, false, click_count, window, cx);
3208
3209 let position = position.to_offset(&display_map, Bias::Left);
3210 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3211
3212 let mut pending_selection = self
3213 .selections
3214 .pending_anchor()
3215 .expect("extend_selection not called with pending selection");
3216 if position >= tail {
3217 pending_selection.start = tail_anchor;
3218 } else {
3219 pending_selection.end = tail_anchor;
3220 pending_selection.reversed = true;
3221 }
3222
3223 let mut pending_mode = self.selections.pending_mode().unwrap();
3224 match &mut pending_mode {
3225 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3226 _ => {}
3227 }
3228
3229 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3230
3231 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3232 s.set_pending(pending_selection, pending_mode)
3233 });
3234 }
3235
3236 fn begin_selection(
3237 &mut self,
3238 position: DisplayPoint,
3239 add: bool,
3240 click_count: usize,
3241 window: &mut Window,
3242 cx: &mut Context<Self>,
3243 ) {
3244 if !self.focus_handle.is_focused(window) {
3245 self.last_focused_descendant = None;
3246 window.focus(&self.focus_handle);
3247 }
3248
3249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3250 let buffer = &display_map.buffer_snapshot;
3251 let position = display_map.clip_point(position, Bias::Left);
3252
3253 let start;
3254 let end;
3255 let mode;
3256 let mut auto_scroll;
3257 match click_count {
3258 1 => {
3259 start = buffer.anchor_before(position.to_point(&display_map));
3260 end = start;
3261 mode = SelectMode::Character;
3262 auto_scroll = true;
3263 }
3264 2 => {
3265 let range = movement::surrounding_word(&display_map, position);
3266 start = buffer.anchor_before(range.start.to_point(&display_map));
3267 end = buffer.anchor_before(range.end.to_point(&display_map));
3268 mode = SelectMode::Word(start..end);
3269 auto_scroll = true;
3270 }
3271 3 => {
3272 let position = display_map
3273 .clip_point(position, Bias::Left)
3274 .to_point(&display_map);
3275 let line_start = display_map.prev_line_boundary(position).0;
3276 let next_line_start = buffer.clip_point(
3277 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3278 Bias::Left,
3279 );
3280 start = buffer.anchor_before(line_start);
3281 end = buffer.anchor_before(next_line_start);
3282 mode = SelectMode::Line(start..end);
3283 auto_scroll = true;
3284 }
3285 _ => {
3286 start = buffer.anchor_before(0);
3287 end = buffer.anchor_before(buffer.len());
3288 mode = SelectMode::All;
3289 auto_scroll = false;
3290 }
3291 }
3292 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3293
3294 let point_to_delete: Option<usize> = {
3295 let selected_points: Vec<Selection<Point>> =
3296 self.selections.disjoint_in_range(start..end, cx);
3297
3298 if !add || click_count > 1 {
3299 None
3300 } else if !selected_points.is_empty() {
3301 Some(selected_points[0].id)
3302 } else {
3303 let clicked_point_already_selected =
3304 self.selections.disjoint.iter().find(|selection| {
3305 selection.start.to_point(buffer) == start.to_point(buffer)
3306 || selection.end.to_point(buffer) == end.to_point(buffer)
3307 });
3308
3309 clicked_point_already_selected.map(|selection| selection.id)
3310 }
3311 };
3312
3313 let selections_count = self.selections.count();
3314
3315 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3316 if let Some(point_to_delete) = point_to_delete {
3317 s.delete(point_to_delete);
3318
3319 if selections_count == 1 {
3320 s.set_pending_anchor_range(start..end, mode);
3321 }
3322 } else {
3323 if !add {
3324 s.clear_disjoint();
3325 }
3326
3327 s.set_pending_anchor_range(start..end, mode);
3328 }
3329 });
3330 }
3331
3332 fn begin_columnar_selection(
3333 &mut self,
3334 position: DisplayPoint,
3335 goal_column: u32,
3336 reset: bool,
3337 window: &mut Window,
3338 cx: &mut Context<Self>,
3339 ) {
3340 if !self.focus_handle.is_focused(window) {
3341 self.last_focused_descendant = None;
3342 window.focus(&self.focus_handle);
3343 }
3344
3345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3346
3347 if reset {
3348 let pointer_position = display_map
3349 .buffer_snapshot
3350 .anchor_before(position.to_point(&display_map));
3351
3352 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3353 s.clear_disjoint();
3354 s.set_pending_anchor_range(
3355 pointer_position..pointer_position,
3356 SelectMode::Character,
3357 );
3358 });
3359 if position.column() != goal_column {
3360 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3361 } else {
3362 self.columnar_display_point = None;
3363 }
3364 }
3365
3366 let tail = self.selections.newest::<Point>(cx).tail();
3367 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3368
3369 if !reset {
3370 self.columnar_display_point = None;
3371 self.select_columns(
3372 tail.to_display_point(&display_map),
3373 position,
3374 goal_column,
3375 &display_map,
3376 window,
3377 cx,
3378 );
3379 }
3380 }
3381
3382 fn update_selection(
3383 &mut self,
3384 position: DisplayPoint,
3385 goal_column: u32,
3386 scroll_delta: gpui::Point<f32>,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 ) {
3390 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3391
3392 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3393 let tail = self
3394 .columnar_display_point
3395 .unwrap_or_else(|| tail.to_display_point(&display_map));
3396 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3397 } else if let Some(mut pending) = self.selections.pending_anchor() {
3398 let buffer = self.buffer.read(cx).snapshot(cx);
3399 let head;
3400 let tail;
3401 let mode = self.selections.pending_mode().unwrap();
3402 match &mode {
3403 SelectMode::Character => {
3404 head = position.to_point(&display_map);
3405 tail = pending.tail().to_point(&buffer);
3406 }
3407 SelectMode::Word(original_range) => {
3408 let original_display_range = original_range.start.to_display_point(&display_map)
3409 ..original_range.end.to_display_point(&display_map);
3410 let original_buffer_range = original_display_range.start.to_point(&display_map)
3411 ..original_display_range.end.to_point(&display_map);
3412 if movement::is_inside_word(&display_map, position)
3413 || original_display_range.contains(&position)
3414 {
3415 let word_range = movement::surrounding_word(&display_map, position);
3416 if word_range.start < original_display_range.start {
3417 head = word_range.start.to_point(&display_map);
3418 } else {
3419 head = word_range.end.to_point(&display_map);
3420 }
3421 } else {
3422 head = position.to_point(&display_map);
3423 }
3424
3425 if head <= original_buffer_range.start {
3426 tail = original_buffer_range.end;
3427 } else {
3428 tail = original_buffer_range.start;
3429 }
3430 }
3431 SelectMode::Line(original_range) => {
3432 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3433
3434 let position = display_map
3435 .clip_point(position, Bias::Left)
3436 .to_point(&display_map);
3437 let line_start = display_map.prev_line_boundary(position).0;
3438 let next_line_start = buffer.clip_point(
3439 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3440 Bias::Left,
3441 );
3442
3443 if line_start < original_range.start {
3444 head = line_start
3445 } else {
3446 head = next_line_start
3447 }
3448
3449 if head <= original_range.start {
3450 tail = original_range.end;
3451 } else {
3452 tail = original_range.start;
3453 }
3454 }
3455 SelectMode::All => {
3456 return;
3457 }
3458 };
3459
3460 if head < tail {
3461 pending.start = buffer.anchor_before(head);
3462 pending.end = buffer.anchor_before(tail);
3463 pending.reversed = true;
3464 } else {
3465 pending.start = buffer.anchor_before(tail);
3466 pending.end = buffer.anchor_before(head);
3467 pending.reversed = false;
3468 }
3469
3470 self.change_selections(None, window, cx, |s| {
3471 s.set_pending(pending, mode);
3472 });
3473 } else {
3474 log::error!("update_selection dispatched with no pending selection");
3475 return;
3476 }
3477
3478 self.apply_scroll_delta(scroll_delta, window, cx);
3479 cx.notify();
3480 }
3481
3482 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3483 self.columnar_selection_tail.take();
3484 if self.selections.pending_anchor().is_some() {
3485 let selections = self.selections.all::<usize>(cx);
3486 self.change_selections(None, window, cx, |s| {
3487 s.select(selections);
3488 s.clear_pending();
3489 });
3490 }
3491 }
3492
3493 fn select_columns(
3494 &mut self,
3495 tail: DisplayPoint,
3496 head: DisplayPoint,
3497 goal_column: u32,
3498 display_map: &DisplaySnapshot,
3499 window: &mut Window,
3500 cx: &mut Context<Self>,
3501 ) {
3502 let start_row = cmp::min(tail.row(), head.row());
3503 let end_row = cmp::max(tail.row(), head.row());
3504 let start_column = cmp::min(tail.column(), goal_column);
3505 let end_column = cmp::max(tail.column(), goal_column);
3506 let reversed = start_column < tail.column();
3507
3508 let selection_ranges = (start_row.0..=end_row.0)
3509 .map(DisplayRow)
3510 .filter_map(|row| {
3511 if !display_map.is_block_line(row) {
3512 let start = display_map
3513 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3514 .to_point(display_map);
3515 let end = display_map
3516 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3517 .to_point(display_map);
3518 if reversed {
3519 Some(end..start)
3520 } else {
3521 Some(start..end)
3522 }
3523 } else {
3524 None
3525 }
3526 })
3527 .collect::<Vec<_>>();
3528
3529 let mut non_empty_ranges = selection_ranges
3530 .iter()
3531 .filter(|selection_range| selection_range.start != selection_range.end)
3532 .peekable();
3533
3534 let ranges = if non_empty_ranges.peek().is_some() {
3535 non_empty_ranges.cloned().collect()
3536 } else {
3537 selection_ranges
3538 };
3539
3540 self.change_selections(None, window, cx, |s| {
3541 s.select_ranges(ranges);
3542 });
3543 cx.notify();
3544 }
3545
3546 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3547 self.selections
3548 .all_adjusted(cx)
3549 .iter()
3550 .any(|selection| !selection.is_empty())
3551 }
3552
3553 pub fn has_pending_nonempty_selection(&self) -> bool {
3554 let pending_nonempty_selection = match self.selections.pending_anchor() {
3555 Some(Selection { start, end, .. }) => start != end,
3556 None => false,
3557 };
3558
3559 pending_nonempty_selection
3560 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3561 }
3562
3563 pub fn has_pending_selection(&self) -> bool {
3564 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3565 }
3566
3567 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3568 self.selection_mark_mode = false;
3569 self.selection_drag_state = SelectionDragState::None;
3570
3571 if self.clear_expanded_diff_hunks(cx) {
3572 cx.notify();
3573 return;
3574 }
3575 if self.dismiss_menus_and_popups(true, window, cx) {
3576 return;
3577 }
3578
3579 if self.mode.is_full()
3580 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3581 {
3582 return;
3583 }
3584
3585 cx.propagate();
3586 }
3587
3588 pub fn dismiss_menus_and_popups(
3589 &mut self,
3590 is_user_requested: bool,
3591 window: &mut Window,
3592 cx: &mut Context<Self>,
3593 ) -> bool {
3594 if self.take_rename(false, window, cx).is_some() {
3595 return true;
3596 }
3597
3598 if hide_hover(self, cx) {
3599 return true;
3600 }
3601
3602 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3603 return true;
3604 }
3605
3606 if self.hide_context_menu(window, cx).is_some() {
3607 return true;
3608 }
3609
3610 if self.mouse_context_menu.take().is_some() {
3611 return true;
3612 }
3613
3614 if is_user_requested && self.discard_inline_completion(true, cx) {
3615 return true;
3616 }
3617
3618 if self.snippet_stack.pop().is_some() {
3619 return true;
3620 }
3621
3622 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3623 self.dismiss_diagnostics(cx);
3624 return true;
3625 }
3626
3627 false
3628 }
3629
3630 fn linked_editing_ranges_for(
3631 &self,
3632 selection: Range<text::Anchor>,
3633 cx: &App,
3634 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3635 if self.linked_edit_ranges.is_empty() {
3636 return None;
3637 }
3638 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3639 selection.end.buffer_id.and_then(|end_buffer_id| {
3640 if selection.start.buffer_id != Some(end_buffer_id) {
3641 return None;
3642 }
3643 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3644 let snapshot = buffer.read(cx).snapshot();
3645 self.linked_edit_ranges
3646 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3647 .map(|ranges| (ranges, snapshot, buffer))
3648 })?;
3649 use text::ToOffset as TO;
3650 // find offset from the start of current range to current cursor position
3651 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3652
3653 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3654 let start_difference = start_offset - start_byte_offset;
3655 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3656 let end_difference = end_offset - start_byte_offset;
3657 // Current range has associated linked ranges.
3658 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3659 for range in linked_ranges.iter() {
3660 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3661 let end_offset = start_offset + end_difference;
3662 let start_offset = start_offset + start_difference;
3663 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3664 continue;
3665 }
3666 if self.selections.disjoint_anchor_ranges().any(|s| {
3667 if s.start.buffer_id != selection.start.buffer_id
3668 || s.end.buffer_id != selection.end.buffer_id
3669 {
3670 return false;
3671 }
3672 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3673 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3674 }) {
3675 continue;
3676 }
3677 let start = buffer_snapshot.anchor_after(start_offset);
3678 let end = buffer_snapshot.anchor_after(end_offset);
3679 linked_edits
3680 .entry(buffer.clone())
3681 .or_default()
3682 .push(start..end);
3683 }
3684 Some(linked_edits)
3685 }
3686
3687 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3688 let text: Arc<str> = text.into();
3689
3690 if self.read_only(cx) {
3691 return;
3692 }
3693
3694 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3695
3696 let selections = self.selections.all_adjusted(cx);
3697 let mut bracket_inserted = false;
3698 let mut edits = Vec::new();
3699 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3700 let mut new_selections = Vec::with_capacity(selections.len());
3701 let mut new_autoclose_regions = Vec::new();
3702 let snapshot = self.buffer.read(cx).read(cx);
3703 let mut clear_linked_edit_ranges = false;
3704
3705 for (selection, autoclose_region) in
3706 self.selections_with_autoclose_regions(selections, &snapshot)
3707 {
3708 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3709 // Determine if the inserted text matches the opening or closing
3710 // bracket of any of this language's bracket pairs.
3711 let mut bracket_pair = None;
3712 let mut is_bracket_pair_start = false;
3713 let mut is_bracket_pair_end = false;
3714 if !text.is_empty() {
3715 let mut bracket_pair_matching_end = None;
3716 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3717 // and they are removing the character that triggered IME popup.
3718 for (pair, enabled) in scope.brackets() {
3719 if !pair.close && !pair.surround {
3720 continue;
3721 }
3722
3723 if enabled && pair.start.ends_with(text.as_ref()) {
3724 let prefix_len = pair.start.len() - text.len();
3725 let preceding_text_matches_prefix = prefix_len == 0
3726 || (selection.start.column >= (prefix_len as u32)
3727 && snapshot.contains_str_at(
3728 Point::new(
3729 selection.start.row,
3730 selection.start.column - (prefix_len as u32),
3731 ),
3732 &pair.start[..prefix_len],
3733 ));
3734 if preceding_text_matches_prefix {
3735 bracket_pair = Some(pair.clone());
3736 is_bracket_pair_start = true;
3737 break;
3738 }
3739 }
3740 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3741 {
3742 // take first bracket pair matching end, but don't break in case a later bracket
3743 // pair matches start
3744 bracket_pair_matching_end = Some(pair.clone());
3745 }
3746 }
3747 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3748 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3749 is_bracket_pair_end = true;
3750 }
3751 }
3752
3753 if let Some(bracket_pair) = bracket_pair {
3754 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3755 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3756 let auto_surround =
3757 self.use_auto_surround && snapshot_settings.use_auto_surround;
3758 if selection.is_empty() {
3759 if is_bracket_pair_start {
3760 // If the inserted text is a suffix of an opening bracket and the
3761 // selection is preceded by the rest of the opening bracket, then
3762 // insert the closing bracket.
3763 let following_text_allows_autoclose = snapshot
3764 .chars_at(selection.start)
3765 .next()
3766 .map_or(true, |c| scope.should_autoclose_before(c));
3767
3768 let preceding_text_allows_autoclose = selection.start.column == 0
3769 || snapshot.reversed_chars_at(selection.start).next().map_or(
3770 true,
3771 |c| {
3772 bracket_pair.start != bracket_pair.end
3773 || !snapshot
3774 .char_classifier_at(selection.start)
3775 .is_word(c)
3776 },
3777 );
3778
3779 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3780 && bracket_pair.start.len() == 1
3781 {
3782 let target = bracket_pair.start.chars().next().unwrap();
3783 let current_line_count = snapshot
3784 .reversed_chars_at(selection.start)
3785 .take_while(|&c| c != '\n')
3786 .filter(|&c| c == target)
3787 .count();
3788 current_line_count % 2 == 1
3789 } else {
3790 false
3791 };
3792
3793 if autoclose
3794 && bracket_pair.close
3795 && following_text_allows_autoclose
3796 && preceding_text_allows_autoclose
3797 && !is_closing_quote
3798 {
3799 let anchor = snapshot.anchor_before(selection.end);
3800 new_selections.push((selection.map(|_| anchor), text.len()));
3801 new_autoclose_regions.push((
3802 anchor,
3803 text.len(),
3804 selection.id,
3805 bracket_pair.clone(),
3806 ));
3807 edits.push((
3808 selection.range(),
3809 format!("{}{}", text, bracket_pair.end).into(),
3810 ));
3811 bracket_inserted = true;
3812 continue;
3813 }
3814 }
3815
3816 if let Some(region) = autoclose_region {
3817 // If the selection is followed by an auto-inserted closing bracket,
3818 // then don't insert that closing bracket again; just move the selection
3819 // past the closing bracket.
3820 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3821 && text.as_ref() == region.pair.end.as_str();
3822 if should_skip {
3823 let anchor = snapshot.anchor_after(selection.end);
3824 new_selections
3825 .push((selection.map(|_| anchor), region.pair.end.len()));
3826 continue;
3827 }
3828 }
3829
3830 let always_treat_brackets_as_autoclosed = snapshot
3831 .language_settings_at(selection.start, cx)
3832 .always_treat_brackets_as_autoclosed;
3833 if always_treat_brackets_as_autoclosed
3834 && is_bracket_pair_end
3835 && snapshot.contains_str_at(selection.end, text.as_ref())
3836 {
3837 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3838 // and the inserted text is a closing bracket and the selection is followed
3839 // by the closing bracket then move the selection past the closing bracket.
3840 let anchor = snapshot.anchor_after(selection.end);
3841 new_selections.push((selection.map(|_| anchor), text.len()));
3842 continue;
3843 }
3844 }
3845 // If an opening bracket is 1 character long and is typed while
3846 // text is selected, then surround that text with the bracket pair.
3847 else if auto_surround
3848 && bracket_pair.surround
3849 && is_bracket_pair_start
3850 && bracket_pair.start.chars().count() == 1
3851 {
3852 edits.push((selection.start..selection.start, text.clone()));
3853 edits.push((
3854 selection.end..selection.end,
3855 bracket_pair.end.as_str().into(),
3856 ));
3857 bracket_inserted = true;
3858 new_selections.push((
3859 Selection {
3860 id: selection.id,
3861 start: snapshot.anchor_after(selection.start),
3862 end: snapshot.anchor_before(selection.end),
3863 reversed: selection.reversed,
3864 goal: selection.goal,
3865 },
3866 0,
3867 ));
3868 continue;
3869 }
3870 }
3871 }
3872
3873 if self.auto_replace_emoji_shortcode
3874 && selection.is_empty()
3875 && text.as_ref().ends_with(':')
3876 {
3877 if let Some(possible_emoji_short_code) =
3878 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3879 {
3880 if !possible_emoji_short_code.is_empty() {
3881 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3882 let emoji_shortcode_start = Point::new(
3883 selection.start.row,
3884 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3885 );
3886
3887 // Remove shortcode from buffer
3888 edits.push((
3889 emoji_shortcode_start..selection.start,
3890 "".to_string().into(),
3891 ));
3892 new_selections.push((
3893 Selection {
3894 id: selection.id,
3895 start: snapshot.anchor_after(emoji_shortcode_start),
3896 end: snapshot.anchor_before(selection.start),
3897 reversed: selection.reversed,
3898 goal: selection.goal,
3899 },
3900 0,
3901 ));
3902
3903 // Insert emoji
3904 let selection_start_anchor = snapshot.anchor_after(selection.start);
3905 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3906 edits.push((selection.start..selection.end, emoji.to_string().into()));
3907
3908 continue;
3909 }
3910 }
3911 }
3912 }
3913
3914 // If not handling any auto-close operation, then just replace the selected
3915 // text with the given input and move the selection to the end of the
3916 // newly inserted text.
3917 let anchor = snapshot.anchor_after(selection.end);
3918 if !self.linked_edit_ranges.is_empty() {
3919 let start_anchor = snapshot.anchor_before(selection.start);
3920
3921 let is_word_char = text.chars().next().map_or(true, |char| {
3922 let classifier = snapshot
3923 .char_classifier_at(start_anchor.to_offset(&snapshot))
3924 .ignore_punctuation(true);
3925 classifier.is_word(char)
3926 });
3927
3928 if is_word_char {
3929 if let Some(ranges) = self
3930 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3931 {
3932 for (buffer, edits) in ranges {
3933 linked_edits
3934 .entry(buffer.clone())
3935 .or_default()
3936 .extend(edits.into_iter().map(|range| (range, text.clone())));
3937 }
3938 }
3939 } else {
3940 clear_linked_edit_ranges = true;
3941 }
3942 }
3943
3944 new_selections.push((selection.map(|_| anchor), 0));
3945 edits.push((selection.start..selection.end, text.clone()));
3946 }
3947
3948 drop(snapshot);
3949
3950 self.transact(window, cx, |this, window, cx| {
3951 if clear_linked_edit_ranges {
3952 this.linked_edit_ranges.clear();
3953 }
3954 let initial_buffer_versions =
3955 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3956
3957 this.buffer.update(cx, |buffer, cx| {
3958 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3959 });
3960 for (buffer, edits) in linked_edits {
3961 buffer.update(cx, |buffer, cx| {
3962 let snapshot = buffer.snapshot();
3963 let edits = edits
3964 .into_iter()
3965 .map(|(range, text)| {
3966 use text::ToPoint as TP;
3967 let end_point = TP::to_point(&range.end, &snapshot);
3968 let start_point = TP::to_point(&range.start, &snapshot);
3969 (start_point..end_point, text)
3970 })
3971 .sorted_by_key(|(range, _)| range.start);
3972 buffer.edit(edits, None, cx);
3973 })
3974 }
3975 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3976 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3977 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3978 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3979 .zip(new_selection_deltas)
3980 .map(|(selection, delta)| Selection {
3981 id: selection.id,
3982 start: selection.start + delta,
3983 end: selection.end + delta,
3984 reversed: selection.reversed,
3985 goal: SelectionGoal::None,
3986 })
3987 .collect::<Vec<_>>();
3988
3989 let mut i = 0;
3990 for (position, delta, selection_id, pair) in new_autoclose_regions {
3991 let position = position.to_offset(&map.buffer_snapshot) + delta;
3992 let start = map.buffer_snapshot.anchor_before(position);
3993 let end = map.buffer_snapshot.anchor_after(position);
3994 while let Some(existing_state) = this.autoclose_regions.get(i) {
3995 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3996 Ordering::Less => i += 1,
3997 Ordering::Greater => break,
3998 Ordering::Equal => {
3999 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4000 Ordering::Less => i += 1,
4001 Ordering::Equal => break,
4002 Ordering::Greater => break,
4003 }
4004 }
4005 }
4006 }
4007 this.autoclose_regions.insert(
4008 i,
4009 AutocloseRegion {
4010 selection_id,
4011 range: start..end,
4012 pair,
4013 },
4014 );
4015 }
4016
4017 let had_active_inline_completion = this.has_active_inline_completion();
4018 this.change_selections_without_updating_completions(
4019 Some(Autoscroll::fit()),
4020 window,
4021 cx,
4022 |s| s.select(new_selections),
4023 );
4024
4025 if !bracket_inserted {
4026 if let Some(on_type_format_task) =
4027 this.trigger_on_type_formatting(text.to_string(), window, cx)
4028 {
4029 on_type_format_task.detach_and_log_err(cx);
4030 }
4031 }
4032
4033 let editor_settings = EditorSettings::get_global(cx);
4034 if bracket_inserted
4035 && (editor_settings.auto_signature_help
4036 || editor_settings.show_signature_help_after_edits)
4037 {
4038 this.show_signature_help(&ShowSignatureHelp, window, cx);
4039 }
4040
4041 let trigger_in_words =
4042 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4043 if this.hard_wrap.is_some() {
4044 let latest: Range<Point> = this.selections.newest(cx).range();
4045 if latest.is_empty()
4046 && this
4047 .buffer()
4048 .read(cx)
4049 .snapshot(cx)
4050 .line_len(MultiBufferRow(latest.start.row))
4051 == latest.start.column
4052 {
4053 this.rewrap_impl(
4054 RewrapOptions {
4055 override_language_settings: true,
4056 preserve_existing_whitespace: true,
4057 },
4058 cx,
4059 )
4060 }
4061 }
4062 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4063 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4064 this.refresh_inline_completion(true, false, window, cx);
4065 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4066 });
4067 }
4068
4069 fn find_possible_emoji_shortcode_at_position(
4070 snapshot: &MultiBufferSnapshot,
4071 position: Point,
4072 ) -> Option<String> {
4073 let mut chars = Vec::new();
4074 let mut found_colon = false;
4075 for char in snapshot.reversed_chars_at(position).take(100) {
4076 // Found a possible emoji shortcode in the middle of the buffer
4077 if found_colon {
4078 if char.is_whitespace() {
4079 chars.reverse();
4080 return Some(chars.iter().collect());
4081 }
4082 // If the previous character is not a whitespace, we are in the middle of a word
4083 // and we only want to complete the shortcode if the word is made up of other emojis
4084 let mut containing_word = String::new();
4085 for ch in snapshot
4086 .reversed_chars_at(position)
4087 .skip(chars.len() + 1)
4088 .take(100)
4089 {
4090 if ch.is_whitespace() {
4091 break;
4092 }
4093 containing_word.push(ch);
4094 }
4095 let containing_word = containing_word.chars().rev().collect::<String>();
4096 if util::word_consists_of_emojis(containing_word.as_str()) {
4097 chars.reverse();
4098 return Some(chars.iter().collect());
4099 }
4100 }
4101
4102 if char.is_whitespace() || !char.is_ascii() {
4103 return None;
4104 }
4105 if char == ':' {
4106 found_colon = true;
4107 } else {
4108 chars.push(char);
4109 }
4110 }
4111 // Found a possible emoji shortcode at the beginning of the buffer
4112 chars.reverse();
4113 Some(chars.iter().collect())
4114 }
4115
4116 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4118 self.transact(window, cx, |this, window, cx| {
4119 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4120 let selections = this.selections.all::<usize>(cx);
4121 let multi_buffer = this.buffer.read(cx);
4122 let buffer = multi_buffer.snapshot(cx);
4123 selections
4124 .iter()
4125 .map(|selection| {
4126 let start_point = selection.start.to_point(&buffer);
4127 let mut existing_indent =
4128 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4129 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4130 let start = selection.start;
4131 let end = selection.end;
4132 let selection_is_empty = start == end;
4133 let language_scope = buffer.language_scope_at(start);
4134 let (
4135 comment_delimiter,
4136 doc_delimiter,
4137 insert_extra_newline,
4138 indent_on_newline,
4139 indent_on_extra_newline,
4140 ) = if let Some(language) = &language_scope {
4141 let mut insert_extra_newline =
4142 insert_extra_newline_brackets(&buffer, start..end, language)
4143 || insert_extra_newline_tree_sitter(&buffer, start..end);
4144
4145 // Comment extension on newline is allowed only for cursor selections
4146 let comment_delimiter = maybe!({
4147 if !selection_is_empty {
4148 return None;
4149 }
4150
4151 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4152 return None;
4153 }
4154
4155 let delimiters = language.line_comment_prefixes();
4156 let max_len_of_delimiter =
4157 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4158 let (snapshot, range) =
4159 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4160
4161 let num_of_whitespaces = snapshot
4162 .chars_for_range(range.clone())
4163 .take_while(|c| c.is_whitespace())
4164 .count();
4165 let comment_candidate = snapshot
4166 .chars_for_range(range)
4167 .skip(num_of_whitespaces)
4168 .take(max_len_of_delimiter)
4169 .collect::<String>();
4170 let (delimiter, trimmed_len) = delimiters
4171 .iter()
4172 .filter_map(|delimiter| {
4173 let prefix = delimiter.trim_end();
4174 if comment_candidate.starts_with(prefix) {
4175 Some((delimiter, prefix.len()))
4176 } else {
4177 None
4178 }
4179 })
4180 .max_by_key(|(_, len)| *len)?;
4181
4182 let cursor_is_placed_after_comment_marker =
4183 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4184 if cursor_is_placed_after_comment_marker {
4185 Some(delimiter.clone())
4186 } else {
4187 None
4188 }
4189 });
4190
4191 let mut indent_on_newline = IndentSize::spaces(0);
4192 let mut indent_on_extra_newline = IndentSize::spaces(0);
4193
4194 let doc_delimiter = maybe!({
4195 if !selection_is_empty {
4196 return None;
4197 }
4198
4199 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4200 return None;
4201 }
4202
4203 let DocumentationConfig {
4204 start: start_tag,
4205 end: end_tag,
4206 prefix: delimiter,
4207 tab_size: len,
4208 } = language.documentation()?;
4209
4210 let is_within_block_comment = buffer
4211 .language_scope_at(start_point)
4212 .is_some_and(|scope| scope.override_name() == Some("comment"));
4213 if !is_within_block_comment {
4214 return None;
4215 }
4216
4217 let (snapshot, range) =
4218 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4219
4220 let num_of_whitespaces = snapshot
4221 .chars_for_range(range.clone())
4222 .take_while(|c| c.is_whitespace())
4223 .count();
4224
4225 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4226 let column = start_point.column;
4227 let cursor_is_after_start_tag = {
4228 let start_tag_len = start_tag.len();
4229 let start_tag_line = snapshot
4230 .chars_for_range(range.clone())
4231 .skip(num_of_whitespaces)
4232 .take(start_tag_len)
4233 .collect::<String>();
4234 if start_tag_line.starts_with(start_tag.as_ref()) {
4235 num_of_whitespaces + start_tag_len <= column as usize
4236 } else {
4237 false
4238 }
4239 };
4240
4241 let cursor_is_after_delimiter = {
4242 let delimiter_trim = delimiter.trim_end();
4243 let delimiter_line = snapshot
4244 .chars_for_range(range.clone())
4245 .skip(num_of_whitespaces)
4246 .take(delimiter_trim.len())
4247 .collect::<String>();
4248 if delimiter_line.starts_with(delimiter_trim) {
4249 num_of_whitespaces + delimiter_trim.len() <= column as usize
4250 } else {
4251 false
4252 }
4253 };
4254
4255 let cursor_is_before_end_tag_if_exists = {
4256 let mut char_position = 0u32;
4257 let mut end_tag_offset = None;
4258
4259 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4260 if let Some(byte_pos) = chunk.find(&**end_tag) {
4261 let chars_before_match =
4262 chunk[..byte_pos].chars().count() as u32;
4263 end_tag_offset =
4264 Some(char_position + chars_before_match);
4265 break 'outer;
4266 }
4267 char_position += chunk.chars().count() as u32;
4268 }
4269
4270 if let Some(end_tag_offset) = end_tag_offset {
4271 let cursor_is_before_end_tag = column <= end_tag_offset;
4272 if cursor_is_after_start_tag {
4273 if cursor_is_before_end_tag {
4274 insert_extra_newline = true;
4275 }
4276 let cursor_is_at_start_of_end_tag =
4277 column == end_tag_offset;
4278 if cursor_is_at_start_of_end_tag {
4279 indent_on_extra_newline.len = (*len).into();
4280 }
4281 }
4282 cursor_is_before_end_tag
4283 } else {
4284 true
4285 }
4286 };
4287
4288 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4289 && cursor_is_before_end_tag_if_exists
4290 {
4291 if cursor_is_after_start_tag {
4292 indent_on_newline.len = (*len).into();
4293 }
4294 Some(delimiter.clone())
4295 } else {
4296 None
4297 }
4298 });
4299
4300 (
4301 comment_delimiter,
4302 doc_delimiter,
4303 insert_extra_newline,
4304 indent_on_newline,
4305 indent_on_extra_newline,
4306 )
4307 } else {
4308 (
4309 None,
4310 None,
4311 false,
4312 IndentSize::default(),
4313 IndentSize::default(),
4314 )
4315 };
4316
4317 let prevent_auto_indent = doc_delimiter.is_some();
4318 let delimiter = comment_delimiter.or(doc_delimiter);
4319
4320 let capacity_for_delimiter =
4321 delimiter.as_deref().map(str::len).unwrap_or_default();
4322 let mut new_text = String::with_capacity(
4323 1 + capacity_for_delimiter
4324 + existing_indent.len as usize
4325 + indent_on_newline.len as usize
4326 + indent_on_extra_newline.len as usize,
4327 );
4328 new_text.push('\n');
4329 new_text.extend(existing_indent.chars());
4330 new_text.extend(indent_on_newline.chars());
4331
4332 if let Some(delimiter) = &delimiter {
4333 new_text.push_str(delimiter);
4334 }
4335
4336 if insert_extra_newline {
4337 new_text.push('\n');
4338 new_text.extend(existing_indent.chars());
4339 new_text.extend(indent_on_extra_newline.chars());
4340 }
4341
4342 let anchor = buffer.anchor_after(end);
4343 let new_selection = selection.map(|_| anchor);
4344 (
4345 ((start..end, new_text), prevent_auto_indent),
4346 (insert_extra_newline, new_selection),
4347 )
4348 })
4349 .unzip()
4350 };
4351
4352 let mut auto_indent_edits = Vec::new();
4353 let mut edits = Vec::new();
4354 for (edit, prevent_auto_indent) in edits_with_flags {
4355 if prevent_auto_indent {
4356 edits.push(edit);
4357 } else {
4358 auto_indent_edits.push(edit);
4359 }
4360 }
4361 if !edits.is_empty() {
4362 this.edit(edits, cx);
4363 }
4364 if !auto_indent_edits.is_empty() {
4365 this.edit_with_autoindent(auto_indent_edits, cx);
4366 }
4367
4368 let buffer = this.buffer.read(cx).snapshot(cx);
4369 let new_selections = selection_info
4370 .into_iter()
4371 .map(|(extra_newline_inserted, new_selection)| {
4372 let mut cursor = new_selection.end.to_point(&buffer);
4373 if extra_newline_inserted {
4374 cursor.row -= 1;
4375 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4376 }
4377 new_selection.map(|_| cursor)
4378 })
4379 .collect();
4380
4381 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4382 s.select(new_selections)
4383 });
4384 this.refresh_inline_completion(true, false, window, cx);
4385 });
4386 }
4387
4388 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4389 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4390
4391 let buffer = self.buffer.read(cx);
4392 let snapshot = buffer.snapshot(cx);
4393
4394 let mut edits = Vec::new();
4395 let mut rows = Vec::new();
4396
4397 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4398 let cursor = selection.head();
4399 let row = cursor.row;
4400
4401 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4402
4403 let newline = "\n".to_string();
4404 edits.push((start_of_line..start_of_line, newline));
4405
4406 rows.push(row + rows_inserted as u32);
4407 }
4408
4409 self.transact(window, cx, |editor, window, cx| {
4410 editor.edit(edits, cx);
4411
4412 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4413 let mut index = 0;
4414 s.move_cursors_with(|map, _, _| {
4415 let row = rows[index];
4416 index += 1;
4417
4418 let point = Point::new(row, 0);
4419 let boundary = map.next_line_boundary(point).1;
4420 let clipped = map.clip_point(boundary, Bias::Left);
4421
4422 (clipped, SelectionGoal::None)
4423 });
4424 });
4425
4426 let mut indent_edits = Vec::new();
4427 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4428 for row in rows {
4429 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4430 for (row, indent) in indents {
4431 if indent.len == 0 {
4432 continue;
4433 }
4434
4435 let text = match indent.kind {
4436 IndentKind::Space => " ".repeat(indent.len as usize),
4437 IndentKind::Tab => "\t".repeat(indent.len as usize),
4438 };
4439 let point = Point::new(row.0, 0);
4440 indent_edits.push((point..point, text));
4441 }
4442 }
4443 editor.edit(indent_edits, cx);
4444 });
4445 }
4446
4447 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4448 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4449
4450 let buffer = self.buffer.read(cx);
4451 let snapshot = buffer.snapshot(cx);
4452
4453 let mut edits = Vec::new();
4454 let mut rows = Vec::new();
4455 let mut rows_inserted = 0;
4456
4457 for selection in self.selections.all_adjusted(cx) {
4458 let cursor = selection.head();
4459 let row = cursor.row;
4460
4461 let point = Point::new(row + 1, 0);
4462 let start_of_line = snapshot.clip_point(point, Bias::Left);
4463
4464 let newline = "\n".to_string();
4465 edits.push((start_of_line..start_of_line, newline));
4466
4467 rows_inserted += 1;
4468 rows.push(row + rows_inserted);
4469 }
4470
4471 self.transact(window, cx, |editor, window, cx| {
4472 editor.edit(edits, cx);
4473
4474 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4475 let mut index = 0;
4476 s.move_cursors_with(|map, _, _| {
4477 let row = rows[index];
4478 index += 1;
4479
4480 let point = Point::new(row, 0);
4481 let boundary = map.next_line_boundary(point).1;
4482 let clipped = map.clip_point(boundary, Bias::Left);
4483
4484 (clipped, SelectionGoal::None)
4485 });
4486 });
4487
4488 let mut indent_edits = Vec::new();
4489 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4490 for row in rows {
4491 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4492 for (row, indent) in indents {
4493 if indent.len == 0 {
4494 continue;
4495 }
4496
4497 let text = match indent.kind {
4498 IndentKind::Space => " ".repeat(indent.len as usize),
4499 IndentKind::Tab => "\t".repeat(indent.len as usize),
4500 };
4501 let point = Point::new(row.0, 0);
4502 indent_edits.push((point..point, text));
4503 }
4504 }
4505 editor.edit(indent_edits, cx);
4506 });
4507 }
4508
4509 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4510 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4511 original_indent_columns: Vec::new(),
4512 });
4513 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4514 }
4515
4516 fn insert_with_autoindent_mode(
4517 &mut self,
4518 text: &str,
4519 autoindent_mode: Option<AutoindentMode>,
4520 window: &mut Window,
4521 cx: &mut Context<Self>,
4522 ) {
4523 if self.read_only(cx) {
4524 return;
4525 }
4526
4527 let text: Arc<str> = text.into();
4528 self.transact(window, cx, |this, window, cx| {
4529 let old_selections = this.selections.all_adjusted(cx);
4530 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4531 let anchors = {
4532 let snapshot = buffer.read(cx);
4533 old_selections
4534 .iter()
4535 .map(|s| {
4536 let anchor = snapshot.anchor_after(s.head());
4537 s.map(|_| anchor)
4538 })
4539 .collect::<Vec<_>>()
4540 };
4541 buffer.edit(
4542 old_selections
4543 .iter()
4544 .map(|s| (s.start..s.end, text.clone())),
4545 autoindent_mode,
4546 cx,
4547 );
4548 anchors
4549 });
4550
4551 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4552 s.select_anchors(selection_anchors);
4553 });
4554
4555 cx.notify();
4556 });
4557 }
4558
4559 fn trigger_completion_on_input(
4560 &mut self,
4561 text: &str,
4562 trigger_in_words: bool,
4563 window: &mut Window,
4564 cx: &mut Context<Self>,
4565 ) {
4566 let completions_source = self
4567 .context_menu
4568 .borrow()
4569 .as_ref()
4570 .and_then(|menu| match menu {
4571 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4572 CodeContextMenu::CodeActions(_) => None,
4573 });
4574
4575 match completions_source {
4576 Some(CompletionsMenuSource::Words) => {
4577 self.show_word_completions(&ShowWordCompletions, window, cx)
4578 }
4579 Some(CompletionsMenuSource::Normal)
4580 | Some(CompletionsMenuSource::SnippetChoices)
4581 | None
4582 if self.is_completion_trigger(
4583 text,
4584 trigger_in_words,
4585 completions_source.is_some(),
4586 cx,
4587 ) =>
4588 {
4589 self.show_completions(
4590 &ShowCompletions {
4591 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4592 },
4593 window,
4594 cx,
4595 )
4596 }
4597 _ => {
4598 self.hide_context_menu(window, cx);
4599 }
4600 }
4601 }
4602
4603 fn is_completion_trigger(
4604 &self,
4605 text: &str,
4606 trigger_in_words: bool,
4607 menu_is_open: bool,
4608 cx: &mut Context<Self>,
4609 ) -> bool {
4610 let position = self.selections.newest_anchor().head();
4611 let multibuffer = self.buffer.read(cx);
4612 let Some(buffer) = position
4613 .buffer_id
4614 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4615 else {
4616 return false;
4617 };
4618
4619 if let Some(completion_provider) = &self.completion_provider {
4620 completion_provider.is_completion_trigger(
4621 &buffer,
4622 position.text_anchor,
4623 text,
4624 trigger_in_words,
4625 menu_is_open,
4626 cx,
4627 )
4628 } else {
4629 false
4630 }
4631 }
4632
4633 /// If any empty selections is touching the start of its innermost containing autoclose
4634 /// region, expand it to select the brackets.
4635 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4636 let selections = self.selections.all::<usize>(cx);
4637 let buffer = self.buffer.read(cx).read(cx);
4638 let new_selections = self
4639 .selections_with_autoclose_regions(selections, &buffer)
4640 .map(|(mut selection, region)| {
4641 if !selection.is_empty() {
4642 return selection;
4643 }
4644
4645 if let Some(region) = region {
4646 let mut range = region.range.to_offset(&buffer);
4647 if selection.start == range.start && range.start >= region.pair.start.len() {
4648 range.start -= region.pair.start.len();
4649 if buffer.contains_str_at(range.start, ®ion.pair.start)
4650 && buffer.contains_str_at(range.end, ®ion.pair.end)
4651 {
4652 range.end += region.pair.end.len();
4653 selection.start = range.start;
4654 selection.end = range.end;
4655
4656 return selection;
4657 }
4658 }
4659 }
4660
4661 let always_treat_brackets_as_autoclosed = buffer
4662 .language_settings_at(selection.start, cx)
4663 .always_treat_brackets_as_autoclosed;
4664
4665 if !always_treat_brackets_as_autoclosed {
4666 return selection;
4667 }
4668
4669 if let Some(scope) = buffer.language_scope_at(selection.start) {
4670 for (pair, enabled) in scope.brackets() {
4671 if !enabled || !pair.close {
4672 continue;
4673 }
4674
4675 if buffer.contains_str_at(selection.start, &pair.end) {
4676 let pair_start_len = pair.start.len();
4677 if buffer.contains_str_at(
4678 selection.start.saturating_sub(pair_start_len),
4679 &pair.start,
4680 ) {
4681 selection.start -= pair_start_len;
4682 selection.end += pair.end.len();
4683
4684 return selection;
4685 }
4686 }
4687 }
4688 }
4689
4690 selection
4691 })
4692 .collect();
4693
4694 drop(buffer);
4695 self.change_selections(None, window, cx, |selections| {
4696 selections.select(new_selections)
4697 });
4698 }
4699
4700 /// Iterate the given selections, and for each one, find the smallest surrounding
4701 /// autoclose region. This uses the ordering of the selections and the autoclose
4702 /// regions to avoid repeated comparisons.
4703 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4704 &'a self,
4705 selections: impl IntoIterator<Item = Selection<D>>,
4706 buffer: &'a MultiBufferSnapshot,
4707 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4708 let mut i = 0;
4709 let mut regions = self.autoclose_regions.as_slice();
4710 selections.into_iter().map(move |selection| {
4711 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4712
4713 let mut enclosing = None;
4714 while let Some(pair_state) = regions.get(i) {
4715 if pair_state.range.end.to_offset(buffer) < range.start {
4716 regions = ®ions[i + 1..];
4717 i = 0;
4718 } else if pair_state.range.start.to_offset(buffer) > range.end {
4719 break;
4720 } else {
4721 if pair_state.selection_id == selection.id {
4722 enclosing = Some(pair_state);
4723 }
4724 i += 1;
4725 }
4726 }
4727
4728 (selection, enclosing)
4729 })
4730 }
4731
4732 /// Remove any autoclose regions that no longer contain their selection.
4733 fn invalidate_autoclose_regions(
4734 &mut self,
4735 mut selections: &[Selection<Anchor>],
4736 buffer: &MultiBufferSnapshot,
4737 ) {
4738 self.autoclose_regions.retain(|state| {
4739 let mut i = 0;
4740 while let Some(selection) = selections.get(i) {
4741 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4742 selections = &selections[1..];
4743 continue;
4744 }
4745 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4746 break;
4747 }
4748 if selection.id == state.selection_id {
4749 return true;
4750 } else {
4751 i += 1;
4752 }
4753 }
4754 false
4755 });
4756 }
4757
4758 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4759 let offset = position.to_offset(buffer);
4760 let (word_range, kind) = buffer.surrounding_word(offset, true);
4761 if offset > word_range.start && kind == Some(CharKind::Word) {
4762 Some(
4763 buffer
4764 .text_for_range(word_range.start..offset)
4765 .collect::<String>(),
4766 )
4767 } else {
4768 None
4769 }
4770 }
4771
4772 pub fn toggle_inline_values(
4773 &mut self,
4774 _: &ToggleInlineValues,
4775 _: &mut Window,
4776 cx: &mut Context<Self>,
4777 ) {
4778 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4779
4780 self.refresh_inline_values(cx);
4781 }
4782
4783 pub fn toggle_inlay_hints(
4784 &mut self,
4785 _: &ToggleInlayHints,
4786 _: &mut Window,
4787 cx: &mut Context<Self>,
4788 ) {
4789 self.refresh_inlay_hints(
4790 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4791 cx,
4792 );
4793 }
4794
4795 pub fn inlay_hints_enabled(&self) -> bool {
4796 self.inlay_hint_cache.enabled
4797 }
4798
4799 pub fn inline_values_enabled(&self) -> bool {
4800 self.inline_value_cache.enabled
4801 }
4802
4803 #[cfg(any(test, feature = "test-support"))]
4804 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4805 self.display_map
4806 .read(cx)
4807 .current_inlays()
4808 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4809 .cloned()
4810 .collect()
4811 }
4812
4813 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4814 if self.semantics_provider.is_none() || !self.mode.is_full() {
4815 return;
4816 }
4817
4818 let reason_description = reason.description();
4819 let ignore_debounce = matches!(
4820 reason,
4821 InlayHintRefreshReason::SettingsChange(_)
4822 | InlayHintRefreshReason::Toggle(_)
4823 | InlayHintRefreshReason::ExcerptsRemoved(_)
4824 | InlayHintRefreshReason::ModifiersChanged(_)
4825 );
4826 let (invalidate_cache, required_languages) = match reason {
4827 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4828 match self.inlay_hint_cache.modifiers_override(enabled) {
4829 Some(enabled) => {
4830 if enabled {
4831 (InvalidationStrategy::RefreshRequested, None)
4832 } else {
4833 self.splice_inlays(
4834 &self
4835 .visible_inlay_hints(cx)
4836 .iter()
4837 .map(|inlay| inlay.id)
4838 .collect::<Vec<InlayId>>(),
4839 Vec::new(),
4840 cx,
4841 );
4842 return;
4843 }
4844 }
4845 None => return,
4846 }
4847 }
4848 InlayHintRefreshReason::Toggle(enabled) => {
4849 if self.inlay_hint_cache.toggle(enabled) {
4850 if enabled {
4851 (InvalidationStrategy::RefreshRequested, None)
4852 } else {
4853 self.splice_inlays(
4854 &self
4855 .visible_inlay_hints(cx)
4856 .iter()
4857 .map(|inlay| inlay.id)
4858 .collect::<Vec<InlayId>>(),
4859 Vec::new(),
4860 cx,
4861 );
4862 return;
4863 }
4864 } else {
4865 return;
4866 }
4867 }
4868 InlayHintRefreshReason::SettingsChange(new_settings) => {
4869 match self.inlay_hint_cache.update_settings(
4870 &self.buffer,
4871 new_settings,
4872 self.visible_inlay_hints(cx),
4873 cx,
4874 ) {
4875 ControlFlow::Break(Some(InlaySplice {
4876 to_remove,
4877 to_insert,
4878 })) => {
4879 self.splice_inlays(&to_remove, to_insert, cx);
4880 return;
4881 }
4882 ControlFlow::Break(None) => return,
4883 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4884 }
4885 }
4886 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4887 if let Some(InlaySplice {
4888 to_remove,
4889 to_insert,
4890 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4891 {
4892 self.splice_inlays(&to_remove, to_insert, cx);
4893 }
4894 self.display_map.update(cx, |display_map, _| {
4895 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4896 });
4897 return;
4898 }
4899 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4900 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4901 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4902 }
4903 InlayHintRefreshReason::RefreshRequested => {
4904 (InvalidationStrategy::RefreshRequested, None)
4905 }
4906 };
4907
4908 if let Some(InlaySplice {
4909 to_remove,
4910 to_insert,
4911 }) = self.inlay_hint_cache.spawn_hint_refresh(
4912 reason_description,
4913 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4914 invalidate_cache,
4915 ignore_debounce,
4916 cx,
4917 ) {
4918 self.splice_inlays(&to_remove, to_insert, cx);
4919 }
4920 }
4921
4922 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4923 self.display_map
4924 .read(cx)
4925 .current_inlays()
4926 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4927 .cloned()
4928 .collect()
4929 }
4930
4931 pub fn excerpts_for_inlay_hints_query(
4932 &self,
4933 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4934 cx: &mut Context<Editor>,
4935 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4936 let Some(project) = self.project.as_ref() else {
4937 return HashMap::default();
4938 };
4939 let project = project.read(cx);
4940 let multi_buffer = self.buffer().read(cx);
4941 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4942 let multi_buffer_visible_start = self
4943 .scroll_manager
4944 .anchor()
4945 .anchor
4946 .to_point(&multi_buffer_snapshot);
4947 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4948 multi_buffer_visible_start
4949 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4950 Bias::Left,
4951 );
4952 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4953 multi_buffer_snapshot
4954 .range_to_buffer_ranges(multi_buffer_visible_range)
4955 .into_iter()
4956 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4957 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4958 let buffer_file = project::File::from_dyn(buffer.file())?;
4959 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4960 let worktree_entry = buffer_worktree
4961 .read(cx)
4962 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4963 if worktree_entry.is_ignored {
4964 return None;
4965 }
4966
4967 let language = buffer.language()?;
4968 if let Some(restrict_to_languages) = restrict_to_languages {
4969 if !restrict_to_languages.contains(language) {
4970 return None;
4971 }
4972 }
4973 Some((
4974 excerpt_id,
4975 (
4976 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4977 buffer.version().clone(),
4978 excerpt_visible_range,
4979 ),
4980 ))
4981 })
4982 .collect()
4983 }
4984
4985 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4986 TextLayoutDetails {
4987 text_system: window.text_system().clone(),
4988 editor_style: self.style.clone().unwrap(),
4989 rem_size: window.rem_size(),
4990 scroll_anchor: self.scroll_manager.anchor(),
4991 visible_rows: self.visible_line_count(),
4992 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4993 }
4994 }
4995
4996 pub fn splice_inlays(
4997 &self,
4998 to_remove: &[InlayId],
4999 to_insert: Vec<Inlay>,
5000 cx: &mut Context<Self>,
5001 ) {
5002 self.display_map.update(cx, |display_map, cx| {
5003 display_map.splice_inlays(to_remove, to_insert, cx)
5004 });
5005 cx.notify();
5006 }
5007
5008 fn trigger_on_type_formatting(
5009 &self,
5010 input: String,
5011 window: &mut Window,
5012 cx: &mut Context<Self>,
5013 ) -> Option<Task<Result<()>>> {
5014 if input.len() != 1 {
5015 return None;
5016 }
5017
5018 let project = self.project.as_ref()?;
5019 let position = self.selections.newest_anchor().head();
5020 let (buffer, buffer_position) = self
5021 .buffer
5022 .read(cx)
5023 .text_anchor_for_position(position, cx)?;
5024
5025 let settings = language_settings::language_settings(
5026 buffer
5027 .read(cx)
5028 .language_at(buffer_position)
5029 .map(|l| l.name()),
5030 buffer.read(cx).file(),
5031 cx,
5032 );
5033 if !settings.use_on_type_format {
5034 return None;
5035 }
5036
5037 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5038 // hence we do LSP request & edit on host side only — add formats to host's history.
5039 let push_to_lsp_host_history = true;
5040 // If this is not the host, append its history with new edits.
5041 let push_to_client_history = project.read(cx).is_via_collab();
5042
5043 let on_type_formatting = project.update(cx, |project, cx| {
5044 project.on_type_format(
5045 buffer.clone(),
5046 buffer_position,
5047 input,
5048 push_to_lsp_host_history,
5049 cx,
5050 )
5051 });
5052 Some(cx.spawn_in(window, async move |editor, cx| {
5053 if let Some(transaction) = on_type_formatting.await? {
5054 if push_to_client_history {
5055 buffer
5056 .update(cx, |buffer, _| {
5057 buffer.push_transaction(transaction, Instant::now());
5058 buffer.finalize_last_transaction();
5059 })
5060 .ok();
5061 }
5062 editor.update(cx, |editor, cx| {
5063 editor.refresh_document_highlights(cx);
5064 })?;
5065 }
5066 Ok(())
5067 }))
5068 }
5069
5070 pub fn show_word_completions(
5071 &mut self,
5072 _: &ShowWordCompletions,
5073 window: &mut Window,
5074 cx: &mut Context<Self>,
5075 ) {
5076 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5077 }
5078
5079 pub fn show_completions(
5080 &mut self,
5081 options: &ShowCompletions,
5082 window: &mut Window,
5083 cx: &mut Context<Self>,
5084 ) {
5085 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5086 }
5087
5088 fn open_or_update_completions_menu(
5089 &mut self,
5090 requested_source: Option<CompletionsMenuSource>,
5091 trigger: Option<&str>,
5092 window: &mut Window,
5093 cx: &mut Context<Self>,
5094 ) {
5095 if self.pending_rename.is_some() {
5096 return;
5097 }
5098
5099 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5100
5101 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5102 // inserted and selected. To handle that case, the start of the selection is used so that
5103 // the menu starts with all choices.
5104 let position = self
5105 .selections
5106 .newest_anchor()
5107 .start
5108 .bias_right(&multibuffer_snapshot);
5109 if position.diff_base_anchor.is_some() {
5110 return;
5111 }
5112 let (buffer, buffer_position) =
5113 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5114 output
5115 } else {
5116 return;
5117 };
5118 let buffer_snapshot = buffer.read(cx).snapshot();
5119
5120 let query: Option<Arc<String>> =
5121 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5122
5123 drop(multibuffer_snapshot);
5124
5125 let provider = match requested_source {
5126 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5127 Some(CompletionsMenuSource::Words) => None,
5128 Some(CompletionsMenuSource::SnippetChoices) => {
5129 log::error!("bug: SnippetChoices requested_source is not handled");
5130 None
5131 }
5132 };
5133
5134 let sort_completions = provider
5135 .as_ref()
5136 .map_or(false, |provider| provider.sort_completions());
5137
5138 let filter_completions = provider
5139 .as_ref()
5140 .map_or(true, |provider| provider.filter_completions());
5141
5142 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5143 if filter_completions {
5144 menu.filter(query.clone(), provider.clone(), window, cx);
5145 }
5146 // When `is_incomplete` is false, no need to re-query completions when the current query
5147 // is a suffix of the initial query.
5148 if !menu.is_incomplete {
5149 // If the new query is a suffix of the old query (typing more characters) and
5150 // the previous result was complete, the existing completions can be filtered.
5151 //
5152 // Note that this is always true for snippet completions.
5153 let query_matches = match (&menu.initial_query, &query) {
5154 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5155 (None, _) => true,
5156 _ => false,
5157 };
5158 if query_matches {
5159 let position_matches = if menu.initial_position == position {
5160 true
5161 } else {
5162 let snapshot = self.buffer.read(cx).read(cx);
5163 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5164 };
5165 if position_matches {
5166 return;
5167 }
5168 }
5169 }
5170 };
5171
5172 let trigger_kind = match trigger {
5173 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5174 CompletionTriggerKind::TRIGGER_CHARACTER
5175 }
5176 _ => CompletionTriggerKind::INVOKED,
5177 };
5178 let completion_context = CompletionContext {
5179 trigger_character: trigger.and_then(|trigger| {
5180 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5181 Some(String::from(trigger))
5182 } else {
5183 None
5184 }
5185 }),
5186 trigger_kind,
5187 };
5188
5189 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5190 buffer_snapshot.surrounding_word(buffer_position)
5191 {
5192 let word_to_exclude = buffer_snapshot
5193 .text_for_range(word_range.clone())
5194 .collect::<String>();
5195 (
5196 buffer_snapshot.anchor_before(word_range.start)
5197 ..buffer_snapshot.anchor_after(buffer_position),
5198 Some(word_to_exclude),
5199 )
5200 } else {
5201 (buffer_position..buffer_position, None)
5202 };
5203
5204 let language = buffer_snapshot
5205 .language_at(buffer_position)
5206 .map(|language| language.name());
5207
5208 let completion_settings =
5209 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5210
5211 let show_completion_documentation = buffer_snapshot
5212 .settings_at(buffer_position, cx)
5213 .show_completion_documentation;
5214
5215 // The document can be large, so stay in reasonable bounds when searching for words,
5216 // otherwise completion pop-up might be slow to appear.
5217 const WORD_LOOKUP_ROWS: u32 = 5_000;
5218 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5219 let min_word_search = buffer_snapshot.clip_point(
5220 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5221 Bias::Left,
5222 );
5223 let max_word_search = buffer_snapshot.clip_point(
5224 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5225 Bias::Right,
5226 );
5227 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5228 ..buffer_snapshot.point_to_offset(max_word_search);
5229
5230 let skip_digits = query
5231 .as_ref()
5232 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5233
5234 let (mut words, provider_responses) = match &provider {
5235 Some(provider) => {
5236 let provider_responses = provider.completions(
5237 position.excerpt_id,
5238 &buffer,
5239 buffer_position,
5240 completion_context,
5241 window,
5242 cx,
5243 );
5244
5245 let words = match completion_settings.words {
5246 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5247 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5248 .background_spawn(async move {
5249 buffer_snapshot.words_in_range(WordsQuery {
5250 fuzzy_contents: None,
5251 range: word_search_range,
5252 skip_digits,
5253 })
5254 }),
5255 };
5256
5257 (words, provider_responses)
5258 }
5259 None => (
5260 cx.background_spawn(async move {
5261 buffer_snapshot.words_in_range(WordsQuery {
5262 fuzzy_contents: None,
5263 range: word_search_range,
5264 skip_digits,
5265 })
5266 }),
5267 Task::ready(Ok(Vec::new())),
5268 ),
5269 };
5270
5271 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5272
5273 let id = post_inc(&mut self.next_completion_id);
5274 let task = cx.spawn_in(window, async move |editor, cx| {
5275 let Ok(()) = editor.update(cx, |this, _| {
5276 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5277 }) else {
5278 return;
5279 };
5280
5281 // TODO: Ideally completions from different sources would be selectively re-queried, so
5282 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5283 let mut completions = Vec::new();
5284 let mut is_incomplete = false;
5285 if let Some(provider_responses) = provider_responses.await.log_err() {
5286 if !provider_responses.is_empty() {
5287 for response in provider_responses {
5288 completions.extend(response.completions);
5289 is_incomplete = is_incomplete || response.is_incomplete;
5290 }
5291 if completion_settings.words == WordsCompletionMode::Fallback {
5292 words = Task::ready(BTreeMap::default());
5293 }
5294 }
5295 }
5296
5297 let mut words = words.await;
5298 if let Some(word_to_exclude) = &word_to_exclude {
5299 words.remove(word_to_exclude);
5300 }
5301 for lsp_completion in &completions {
5302 words.remove(&lsp_completion.new_text);
5303 }
5304 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5305 replace_range: word_replace_range.clone(),
5306 new_text: word.clone(),
5307 label: CodeLabel::plain(word, None),
5308 icon_path: None,
5309 documentation: None,
5310 source: CompletionSource::BufferWord {
5311 word_range,
5312 resolved: false,
5313 },
5314 insert_text_mode: Some(InsertTextMode::AS_IS),
5315 confirm: None,
5316 }));
5317
5318 let menu = if completions.is_empty() {
5319 None
5320 } else {
5321 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5322 let languages = editor
5323 .workspace
5324 .as_ref()
5325 .and_then(|(workspace, _)| workspace.upgrade())
5326 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5327 let menu = CompletionsMenu::new(
5328 id,
5329 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5330 sort_completions,
5331 show_completion_documentation,
5332 position,
5333 query.clone(),
5334 is_incomplete,
5335 buffer.clone(),
5336 completions.into(),
5337 snippet_sort_order,
5338 languages,
5339 language,
5340 cx,
5341 );
5342
5343 let query = if filter_completions { query } else { None };
5344 let matches_task = if let Some(query) = query {
5345 menu.do_async_filtering(query, cx)
5346 } else {
5347 Task::ready(menu.unfiltered_matches())
5348 };
5349 (menu, matches_task)
5350 }) else {
5351 return;
5352 };
5353
5354 let matches = matches_task.await;
5355
5356 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5357 // Newer menu already set, so exit.
5358 match editor.context_menu.borrow().as_ref() {
5359 Some(CodeContextMenu::Completions(prev_menu)) => {
5360 if prev_menu.id > id {
5361 return;
5362 }
5363 }
5364 _ => {}
5365 };
5366
5367 // Only valid to take prev_menu because it the new menu is immediately set
5368 // below, or the menu is hidden.
5369 match editor.context_menu.borrow_mut().take() {
5370 Some(CodeContextMenu::Completions(prev_menu)) => {
5371 let position_matches =
5372 if prev_menu.initial_position == menu.initial_position {
5373 true
5374 } else {
5375 let snapshot = editor.buffer.read(cx).read(cx);
5376 prev_menu.initial_position.to_offset(&snapshot)
5377 == menu.initial_position.to_offset(&snapshot)
5378 };
5379 if position_matches {
5380 // Preserve markdown cache before `set_filter_results` because it will
5381 // try to populate the documentation cache.
5382 menu.preserve_markdown_cache(prev_menu);
5383 }
5384 }
5385 _ => {}
5386 };
5387
5388 menu.set_filter_results(matches, provider, window, cx);
5389 }) else {
5390 return;
5391 };
5392
5393 menu.visible().then_some(menu)
5394 };
5395
5396 editor
5397 .update_in(cx, |editor, window, cx| {
5398 if editor.focus_handle.is_focused(window) {
5399 if let Some(menu) = menu {
5400 *editor.context_menu.borrow_mut() =
5401 Some(CodeContextMenu::Completions(menu));
5402
5403 crate::hover_popover::hide_hover(editor, cx);
5404 if editor.show_edit_predictions_in_menu() {
5405 editor.update_visible_inline_completion(window, cx);
5406 } else {
5407 editor.discard_inline_completion(false, cx);
5408 }
5409
5410 cx.notify();
5411 return;
5412 }
5413 }
5414
5415 if editor.completion_tasks.len() <= 1 {
5416 // If there are no more completion tasks and the last menu was empty, we should hide it.
5417 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5418 // If it was already hidden and we don't show inline completions in the menu, we should
5419 // also show the inline-completion when available.
5420 if was_hidden && editor.show_edit_predictions_in_menu() {
5421 editor.update_visible_inline_completion(window, cx);
5422 }
5423 }
5424 })
5425 .ok();
5426 });
5427
5428 self.completion_tasks.push((id, task));
5429 }
5430
5431 #[cfg(feature = "test-support")]
5432 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5433 let menu = self.context_menu.borrow();
5434 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5435 let completions = menu.completions.borrow();
5436 Some(completions.to_vec())
5437 } else {
5438 None
5439 }
5440 }
5441
5442 pub fn with_completions_menu_matching_id<R>(
5443 &self,
5444 id: CompletionId,
5445 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5446 ) -> R {
5447 let mut context_menu = self.context_menu.borrow_mut();
5448 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5449 return f(None);
5450 };
5451 if completions_menu.id != id {
5452 return f(None);
5453 }
5454 f(Some(completions_menu))
5455 }
5456
5457 pub fn confirm_completion(
5458 &mut self,
5459 action: &ConfirmCompletion,
5460 window: &mut Window,
5461 cx: &mut Context<Self>,
5462 ) -> Option<Task<Result<()>>> {
5463 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5464 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5465 }
5466
5467 pub fn confirm_completion_insert(
5468 &mut self,
5469 _: &ConfirmCompletionInsert,
5470 window: &mut Window,
5471 cx: &mut Context<Self>,
5472 ) -> Option<Task<Result<()>>> {
5473 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5474 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5475 }
5476
5477 pub fn confirm_completion_replace(
5478 &mut self,
5479 _: &ConfirmCompletionReplace,
5480 window: &mut Window,
5481 cx: &mut Context<Self>,
5482 ) -> Option<Task<Result<()>>> {
5483 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5484 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5485 }
5486
5487 pub fn compose_completion(
5488 &mut self,
5489 action: &ComposeCompletion,
5490 window: &mut Window,
5491 cx: &mut Context<Self>,
5492 ) -> Option<Task<Result<()>>> {
5493 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5494 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5495 }
5496
5497 fn do_completion(
5498 &mut self,
5499 item_ix: Option<usize>,
5500 intent: CompletionIntent,
5501 window: &mut Window,
5502 cx: &mut Context<Editor>,
5503 ) -> Option<Task<Result<()>>> {
5504 use language::ToOffset as _;
5505
5506 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5507 else {
5508 return None;
5509 };
5510
5511 let candidate_id = {
5512 let entries = completions_menu.entries.borrow();
5513 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5514 if self.show_edit_predictions_in_menu() {
5515 self.discard_inline_completion(true, cx);
5516 }
5517 mat.candidate_id
5518 };
5519
5520 let completion = completions_menu
5521 .completions
5522 .borrow()
5523 .get(candidate_id)?
5524 .clone();
5525 cx.stop_propagation();
5526
5527 let buffer_handle = completions_menu.buffer.clone();
5528
5529 let CompletionEdit {
5530 new_text,
5531 snippet,
5532 replace_range,
5533 } = process_completion_for_edit(
5534 &completion,
5535 intent,
5536 &buffer_handle,
5537 &completions_menu.initial_position.text_anchor,
5538 cx,
5539 );
5540
5541 let buffer = buffer_handle.read(cx);
5542 let snapshot = self.buffer.read(cx).snapshot(cx);
5543 let newest_anchor = self.selections.newest_anchor();
5544 let replace_range_multibuffer = {
5545 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5546 let multibuffer_anchor = snapshot
5547 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5548 .unwrap()
5549 ..snapshot
5550 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5551 .unwrap();
5552 multibuffer_anchor.start.to_offset(&snapshot)
5553 ..multibuffer_anchor.end.to_offset(&snapshot)
5554 };
5555 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5556 return None;
5557 }
5558
5559 let old_text = buffer
5560 .text_for_range(replace_range.clone())
5561 .collect::<String>();
5562 let lookbehind = newest_anchor
5563 .start
5564 .text_anchor
5565 .to_offset(buffer)
5566 .saturating_sub(replace_range.start);
5567 let lookahead = replace_range
5568 .end
5569 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5570 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5571 let suffix = &old_text[lookbehind.min(old_text.len())..];
5572
5573 let selections = self.selections.all::<usize>(cx);
5574 let mut ranges = Vec::new();
5575 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5576
5577 for selection in &selections {
5578 let range = if selection.id == newest_anchor.id {
5579 replace_range_multibuffer.clone()
5580 } else {
5581 let mut range = selection.range();
5582
5583 // if prefix is present, don't duplicate it
5584 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5585 range.start = range.start.saturating_sub(lookbehind);
5586
5587 // if suffix is also present, mimic the newest cursor and replace it
5588 if selection.id != newest_anchor.id
5589 && snapshot.contains_str_at(range.end, suffix)
5590 {
5591 range.end += lookahead;
5592 }
5593 }
5594 range
5595 };
5596
5597 ranges.push(range.clone());
5598
5599 if !self.linked_edit_ranges.is_empty() {
5600 let start_anchor = snapshot.anchor_before(range.start);
5601 let end_anchor = snapshot.anchor_after(range.end);
5602 if let Some(ranges) = self
5603 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5604 {
5605 for (buffer, edits) in ranges {
5606 linked_edits
5607 .entry(buffer.clone())
5608 .or_default()
5609 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5610 }
5611 }
5612 }
5613 }
5614
5615 let common_prefix_len = old_text
5616 .chars()
5617 .zip(new_text.chars())
5618 .take_while(|(a, b)| a == b)
5619 .map(|(a, _)| a.len_utf8())
5620 .sum::<usize>();
5621
5622 cx.emit(EditorEvent::InputHandled {
5623 utf16_range_to_replace: None,
5624 text: new_text[common_prefix_len..].into(),
5625 });
5626
5627 self.transact(window, cx, |this, window, cx| {
5628 if let Some(mut snippet) = snippet {
5629 snippet.text = new_text.to_string();
5630 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5631 } else {
5632 this.buffer.update(cx, |buffer, cx| {
5633 let auto_indent = match completion.insert_text_mode {
5634 Some(InsertTextMode::AS_IS) => None,
5635 _ => this.autoindent_mode.clone(),
5636 };
5637 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5638 buffer.edit(edits, auto_indent, cx);
5639 });
5640 }
5641 for (buffer, edits) in linked_edits {
5642 buffer.update(cx, |buffer, cx| {
5643 let snapshot = buffer.snapshot();
5644 let edits = edits
5645 .into_iter()
5646 .map(|(range, text)| {
5647 use text::ToPoint as TP;
5648 let end_point = TP::to_point(&range.end, &snapshot);
5649 let start_point = TP::to_point(&range.start, &snapshot);
5650 (start_point..end_point, text)
5651 })
5652 .sorted_by_key(|(range, _)| range.start);
5653 buffer.edit(edits, None, cx);
5654 })
5655 }
5656
5657 this.refresh_inline_completion(true, false, window, cx);
5658 });
5659
5660 let show_new_completions_on_confirm = completion
5661 .confirm
5662 .as_ref()
5663 .map_or(false, |confirm| confirm(intent, window, cx));
5664 if show_new_completions_on_confirm {
5665 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5666 }
5667
5668 let provider = self.completion_provider.as_ref()?;
5669 drop(completion);
5670 let apply_edits = provider.apply_additional_edits_for_completion(
5671 buffer_handle,
5672 completions_menu.completions.clone(),
5673 candidate_id,
5674 true,
5675 cx,
5676 );
5677
5678 let editor_settings = EditorSettings::get_global(cx);
5679 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5680 // After the code completion is finished, users often want to know what signatures are needed.
5681 // so we should automatically call signature_help
5682 self.show_signature_help(&ShowSignatureHelp, window, cx);
5683 }
5684
5685 Some(cx.foreground_executor().spawn(async move {
5686 apply_edits.await?;
5687 Ok(())
5688 }))
5689 }
5690
5691 pub fn toggle_code_actions(
5692 &mut self,
5693 action: &ToggleCodeActions,
5694 window: &mut Window,
5695 cx: &mut Context<Self>,
5696 ) {
5697 let quick_launch = action.quick_launch;
5698 let mut context_menu = self.context_menu.borrow_mut();
5699 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5700 if code_actions.deployed_from == action.deployed_from {
5701 // Toggle if we're selecting the same one
5702 *context_menu = None;
5703 cx.notify();
5704 return;
5705 } else {
5706 // Otherwise, clear it and start a new one
5707 *context_menu = None;
5708 cx.notify();
5709 }
5710 }
5711 drop(context_menu);
5712 let snapshot = self.snapshot(window, cx);
5713 let deployed_from = action.deployed_from.clone();
5714 let action = action.clone();
5715 self.completion_tasks.clear();
5716 self.discard_inline_completion(false, cx);
5717
5718 let multibuffer_point = match &action.deployed_from {
5719 Some(CodeActionSource::Indicator(row)) => {
5720 DisplayPoint::new(*row, 0).to_point(&snapshot)
5721 }
5722 _ => self.selections.newest::<Point>(cx).head(),
5723 };
5724 let Some((buffer, buffer_row)) = snapshot
5725 .buffer_snapshot
5726 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5727 .and_then(|(buffer_snapshot, range)| {
5728 self.buffer()
5729 .read(cx)
5730 .buffer(buffer_snapshot.remote_id())
5731 .map(|buffer| (buffer, range.start.row))
5732 })
5733 else {
5734 return;
5735 };
5736 let buffer_id = buffer.read(cx).remote_id();
5737 let tasks = self
5738 .tasks
5739 .get(&(buffer_id, buffer_row))
5740 .map(|t| Arc::new(t.to_owned()));
5741
5742 if !self.focus_handle.is_focused(window) {
5743 return;
5744 }
5745 let project = self.project.clone();
5746
5747 let code_actions_task = match deployed_from {
5748 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5749 _ => self.code_actions(buffer_row, window, cx),
5750 };
5751
5752 let runnable_task = match deployed_from {
5753 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5754 _ => {
5755 let mut task_context_task = Task::ready(None);
5756 if let Some(tasks) = &tasks {
5757 if let Some(project) = project {
5758 task_context_task =
5759 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5760 }
5761 }
5762
5763 cx.spawn_in(window, {
5764 let buffer = buffer.clone();
5765 async move |editor, cx| {
5766 let task_context = task_context_task.await;
5767
5768 let resolved_tasks =
5769 tasks
5770 .zip(task_context.clone())
5771 .map(|(tasks, task_context)| ResolvedTasks {
5772 templates: tasks.resolve(&task_context).collect(),
5773 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5774 multibuffer_point.row,
5775 tasks.column,
5776 )),
5777 });
5778 let debug_scenarios = editor.update(cx, |editor, cx| {
5779 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5780 })?;
5781 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5782 }
5783 })
5784 }
5785 };
5786
5787 cx.spawn_in(window, async move |editor, cx| {
5788 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5789 let code_actions = code_actions_task.await;
5790 let spawn_straight_away = quick_launch
5791 && resolved_tasks
5792 .as_ref()
5793 .map_or(false, |tasks| tasks.templates.len() == 1)
5794 && code_actions
5795 .as_ref()
5796 .map_or(true, |actions| actions.is_empty())
5797 && debug_scenarios.is_empty();
5798
5799 editor.update_in(cx, |editor, window, cx| {
5800 crate::hover_popover::hide_hover(editor, cx);
5801 *editor.context_menu.borrow_mut() =
5802 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5803 buffer,
5804 actions: CodeActionContents::new(
5805 resolved_tasks,
5806 code_actions,
5807 debug_scenarios,
5808 task_context.unwrap_or_default(),
5809 ),
5810 selected_item: Default::default(),
5811 scroll_handle: UniformListScrollHandle::default(),
5812 deployed_from,
5813 }));
5814 if spawn_straight_away {
5815 if let Some(task) = editor.confirm_code_action(
5816 &ConfirmCodeAction { item_ix: Some(0) },
5817 window,
5818 cx,
5819 ) {
5820 cx.notify();
5821 return task;
5822 }
5823 }
5824
5825 Task::ready(Ok(()))
5826 })
5827 })
5828 .detach_and_log_err(cx);
5829 }
5830
5831 fn debug_scenarios(
5832 &mut self,
5833 resolved_tasks: &Option<ResolvedTasks>,
5834 buffer: &Entity<Buffer>,
5835 cx: &mut App,
5836 ) -> Vec<task::DebugScenario> {
5837 if cx.has_flag::<DebuggerFeatureFlag>() {
5838 maybe!({
5839 let project = self.project.as_ref()?;
5840 let dap_store = project.read(cx).dap_store();
5841 let mut scenarios = vec![];
5842 let resolved_tasks = resolved_tasks.as_ref()?;
5843 let buffer = buffer.read(cx);
5844 let language = buffer.language()?;
5845 let file = buffer.file();
5846 let debug_adapter = language_settings(language.name().into(), file, cx)
5847 .debuggers
5848 .first()
5849 .map(SharedString::from)
5850 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5851
5852 dap_store.update(cx, |dap_store, cx| {
5853 for (_, task) in &resolved_tasks.templates {
5854 if let Some(scenario) = dap_store.debug_scenario_for_build_task(
5855 task.original_task().clone(),
5856 debug_adapter.clone().into(),
5857 task.display_label().to_owned().into(),
5858 cx,
5859 ) {
5860 scenarios.push(scenario);
5861 }
5862 }
5863 });
5864 Some(scenarios)
5865 })
5866 .unwrap_or_default()
5867 } else {
5868 vec![]
5869 }
5870 }
5871
5872 fn code_actions(
5873 &mut self,
5874 buffer_row: u32,
5875 window: &mut Window,
5876 cx: &mut Context<Self>,
5877 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5878 let mut task = self.code_actions_task.take();
5879 cx.spawn_in(window, async move |editor, cx| {
5880 while let Some(prev_task) = task {
5881 prev_task.await.log_err();
5882 task = editor
5883 .update(cx, |this, _| this.code_actions_task.take())
5884 .ok()?;
5885 }
5886
5887 editor
5888 .update(cx, |editor, cx| {
5889 editor
5890 .available_code_actions
5891 .clone()
5892 .and_then(|(location, code_actions)| {
5893 let snapshot = location.buffer.read(cx).snapshot();
5894 let point_range = location.range.to_point(&snapshot);
5895 let point_range = point_range.start.row..=point_range.end.row;
5896 if point_range.contains(&buffer_row) {
5897 Some(code_actions)
5898 } else {
5899 None
5900 }
5901 })
5902 })
5903 .ok()
5904 .flatten()
5905 })
5906 }
5907
5908 pub fn confirm_code_action(
5909 &mut self,
5910 action: &ConfirmCodeAction,
5911 window: &mut Window,
5912 cx: &mut Context<Self>,
5913 ) -> Option<Task<Result<()>>> {
5914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5915
5916 let actions_menu =
5917 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5918 menu
5919 } else {
5920 return None;
5921 };
5922
5923 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5924 let action = actions_menu.actions.get(action_ix)?;
5925 let title = action.label();
5926 let buffer = actions_menu.buffer;
5927 let workspace = self.workspace()?;
5928
5929 match action {
5930 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5931 workspace.update(cx, |workspace, cx| {
5932 workspace.schedule_resolved_task(
5933 task_source_kind,
5934 resolved_task,
5935 false,
5936 window,
5937 cx,
5938 );
5939
5940 Some(Task::ready(Ok(())))
5941 })
5942 }
5943 CodeActionsItem::CodeAction {
5944 excerpt_id,
5945 action,
5946 provider,
5947 } => {
5948 let apply_code_action =
5949 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5950 let workspace = workspace.downgrade();
5951 Some(cx.spawn_in(window, async move |editor, cx| {
5952 let project_transaction = apply_code_action.await?;
5953 Self::open_project_transaction(
5954 &editor,
5955 workspace,
5956 project_transaction,
5957 title,
5958 cx,
5959 )
5960 .await
5961 }))
5962 }
5963 CodeActionsItem::DebugScenario(scenario) => {
5964 let context = actions_menu.actions.context.clone();
5965
5966 workspace.update(cx, |workspace, cx| {
5967 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5968 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5969 });
5970 Some(Task::ready(Ok(())))
5971 }
5972 }
5973 }
5974
5975 pub async fn open_project_transaction(
5976 this: &WeakEntity<Editor>,
5977 workspace: WeakEntity<Workspace>,
5978 transaction: ProjectTransaction,
5979 title: String,
5980 cx: &mut AsyncWindowContext,
5981 ) -> Result<()> {
5982 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5983 cx.update(|_, cx| {
5984 entries.sort_unstable_by_key(|(buffer, _)| {
5985 buffer.read(cx).file().map(|f| f.path().clone())
5986 });
5987 })?;
5988
5989 // If the project transaction's edits are all contained within this editor, then
5990 // avoid opening a new editor to display them.
5991
5992 if let Some((buffer, transaction)) = entries.first() {
5993 if entries.len() == 1 {
5994 let excerpt = this.update(cx, |editor, cx| {
5995 editor
5996 .buffer()
5997 .read(cx)
5998 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5999 })?;
6000 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6001 if excerpted_buffer == *buffer {
6002 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6003 let excerpt_range = excerpt_range.to_offset(buffer);
6004 buffer
6005 .edited_ranges_for_transaction::<usize>(transaction)
6006 .all(|range| {
6007 excerpt_range.start <= range.start
6008 && excerpt_range.end >= range.end
6009 })
6010 })?;
6011
6012 if all_edits_within_excerpt {
6013 return Ok(());
6014 }
6015 }
6016 }
6017 }
6018 } else {
6019 return Ok(());
6020 }
6021
6022 let mut ranges_to_highlight = Vec::new();
6023 let excerpt_buffer = cx.new(|cx| {
6024 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6025 for (buffer_handle, transaction) in &entries {
6026 let edited_ranges = buffer_handle
6027 .read(cx)
6028 .edited_ranges_for_transaction::<Point>(transaction)
6029 .collect::<Vec<_>>();
6030 let (ranges, _) = multibuffer.set_excerpts_for_path(
6031 PathKey::for_buffer(buffer_handle, cx),
6032 buffer_handle.clone(),
6033 edited_ranges,
6034 DEFAULT_MULTIBUFFER_CONTEXT,
6035 cx,
6036 );
6037
6038 ranges_to_highlight.extend(ranges);
6039 }
6040 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6041 multibuffer
6042 })?;
6043
6044 workspace.update_in(cx, |workspace, window, cx| {
6045 let project = workspace.project().clone();
6046 let editor =
6047 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6048 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6049 editor.update(cx, |editor, cx| {
6050 editor.highlight_background::<Self>(
6051 &ranges_to_highlight,
6052 |theme| theme.editor_highlighted_line_background,
6053 cx,
6054 );
6055 });
6056 })?;
6057
6058 Ok(())
6059 }
6060
6061 pub fn clear_code_action_providers(&mut self) {
6062 self.code_action_providers.clear();
6063 self.available_code_actions.take();
6064 }
6065
6066 pub fn add_code_action_provider(
6067 &mut self,
6068 provider: Rc<dyn CodeActionProvider>,
6069 window: &mut Window,
6070 cx: &mut Context<Self>,
6071 ) {
6072 if self
6073 .code_action_providers
6074 .iter()
6075 .any(|existing_provider| existing_provider.id() == provider.id())
6076 {
6077 return;
6078 }
6079
6080 self.code_action_providers.push(provider);
6081 self.refresh_code_actions(window, cx);
6082 }
6083
6084 pub fn remove_code_action_provider(
6085 &mut self,
6086 id: Arc<str>,
6087 window: &mut Window,
6088 cx: &mut Context<Self>,
6089 ) {
6090 self.code_action_providers
6091 .retain(|provider| provider.id() != id);
6092 self.refresh_code_actions(window, cx);
6093 }
6094
6095 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6096 !self.code_action_providers.is_empty()
6097 && EditorSettings::get_global(cx).toolbar.code_actions
6098 }
6099
6100 pub fn has_available_code_actions(&self) -> bool {
6101 self.available_code_actions
6102 .as_ref()
6103 .is_some_and(|(_, actions)| !actions.is_empty())
6104 }
6105
6106 fn render_inline_code_actions(
6107 &self,
6108 icon_size: ui::IconSize,
6109 display_row: DisplayRow,
6110 is_active: bool,
6111 cx: &mut Context<Self>,
6112 ) -> AnyElement {
6113 let show_tooltip = !self.context_menu_visible();
6114 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6115 .icon_size(icon_size)
6116 .shape(ui::IconButtonShape::Square)
6117 .style(ButtonStyle::Transparent)
6118 .icon_color(ui::Color::Hidden)
6119 .toggle_state(is_active)
6120 .when(show_tooltip, |this| {
6121 this.tooltip({
6122 let focus_handle = self.focus_handle.clone();
6123 move |window, cx| {
6124 Tooltip::for_action_in(
6125 "Toggle Code Actions",
6126 &ToggleCodeActions {
6127 deployed_from: None,
6128 quick_launch: false,
6129 },
6130 &focus_handle,
6131 window,
6132 cx,
6133 )
6134 }
6135 })
6136 })
6137 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6138 window.focus(&editor.focus_handle(cx));
6139 editor.toggle_code_actions(
6140 &crate::actions::ToggleCodeActions {
6141 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6142 display_row,
6143 )),
6144 quick_launch: false,
6145 },
6146 window,
6147 cx,
6148 );
6149 }))
6150 .into_any_element()
6151 }
6152
6153 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6154 &self.context_menu
6155 }
6156
6157 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6158 let newest_selection = self.selections.newest_anchor().clone();
6159 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6160 let buffer = self.buffer.read(cx);
6161 if newest_selection.head().diff_base_anchor.is_some() {
6162 return None;
6163 }
6164 let (start_buffer, start) =
6165 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6166 let (end_buffer, end) =
6167 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6168 if start_buffer != end_buffer {
6169 return None;
6170 }
6171
6172 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6173 cx.background_executor()
6174 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6175 .await;
6176
6177 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6178 let providers = this.code_action_providers.clone();
6179 let tasks = this
6180 .code_action_providers
6181 .iter()
6182 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6183 .collect::<Vec<_>>();
6184 (providers, tasks)
6185 })?;
6186
6187 let mut actions = Vec::new();
6188 for (provider, provider_actions) in
6189 providers.into_iter().zip(future::join_all(tasks).await)
6190 {
6191 if let Some(provider_actions) = provider_actions.log_err() {
6192 actions.extend(provider_actions.into_iter().map(|action| {
6193 AvailableCodeAction {
6194 excerpt_id: newest_selection.start.excerpt_id,
6195 action,
6196 provider: provider.clone(),
6197 }
6198 }));
6199 }
6200 }
6201
6202 this.update(cx, |this, cx| {
6203 this.available_code_actions = if actions.is_empty() {
6204 None
6205 } else {
6206 Some((
6207 Location {
6208 buffer: start_buffer,
6209 range: start..end,
6210 },
6211 actions.into(),
6212 ))
6213 };
6214 cx.notify();
6215 })
6216 }));
6217 None
6218 }
6219
6220 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6221 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6222 self.show_git_blame_inline = false;
6223
6224 self.show_git_blame_inline_delay_task =
6225 Some(cx.spawn_in(window, async move |this, cx| {
6226 cx.background_executor().timer(delay).await;
6227
6228 this.update(cx, |this, cx| {
6229 this.show_git_blame_inline = true;
6230 cx.notify();
6231 })
6232 .log_err();
6233 }));
6234 }
6235 }
6236
6237 fn show_blame_popover(
6238 &mut self,
6239 blame_entry: &BlameEntry,
6240 position: gpui::Point<Pixels>,
6241 cx: &mut Context<Self>,
6242 ) {
6243 if let Some(state) = &mut self.inline_blame_popover {
6244 state.hide_task.take();
6245 cx.notify();
6246 } else {
6247 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6248 let show_task = cx.spawn(async move |editor, cx| {
6249 cx.background_executor()
6250 .timer(std::time::Duration::from_millis(delay))
6251 .await;
6252 editor
6253 .update(cx, |editor, cx| {
6254 if let Some(state) = &mut editor.inline_blame_popover {
6255 state.show_task = None;
6256 cx.notify();
6257 }
6258 })
6259 .ok();
6260 });
6261 let Some(blame) = self.blame.as_ref() else {
6262 return;
6263 };
6264 let blame = blame.read(cx);
6265 let details = blame.details_for_entry(&blame_entry);
6266 let markdown = cx.new(|cx| {
6267 Markdown::new(
6268 details
6269 .as_ref()
6270 .map(|message| message.message.clone())
6271 .unwrap_or_default(),
6272 None,
6273 None,
6274 cx,
6275 )
6276 });
6277 self.inline_blame_popover = Some(InlineBlamePopover {
6278 position,
6279 show_task: Some(show_task),
6280 hide_task: None,
6281 popover_bounds: None,
6282 popover_state: InlineBlamePopoverState {
6283 scroll_handle: ScrollHandle::new(),
6284 commit_message: details,
6285 markdown,
6286 },
6287 });
6288 }
6289 }
6290
6291 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6292 if let Some(state) = &mut self.inline_blame_popover {
6293 if state.show_task.is_some() {
6294 self.inline_blame_popover.take();
6295 cx.notify();
6296 } else {
6297 let hide_task = cx.spawn(async move |editor, cx| {
6298 cx.background_executor()
6299 .timer(std::time::Duration::from_millis(100))
6300 .await;
6301 editor
6302 .update(cx, |editor, cx| {
6303 editor.inline_blame_popover.take();
6304 cx.notify();
6305 })
6306 .ok();
6307 });
6308 state.hide_task = Some(hide_task);
6309 }
6310 }
6311 }
6312
6313 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6314 if self.pending_rename.is_some() {
6315 return None;
6316 }
6317
6318 let provider = self.semantics_provider.clone()?;
6319 let buffer = self.buffer.read(cx);
6320 let newest_selection = self.selections.newest_anchor().clone();
6321 let cursor_position = newest_selection.head();
6322 let (cursor_buffer, cursor_buffer_position) =
6323 buffer.text_anchor_for_position(cursor_position, cx)?;
6324 let (tail_buffer, tail_buffer_position) =
6325 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6326 if cursor_buffer != tail_buffer {
6327 return None;
6328 }
6329
6330 let snapshot = cursor_buffer.read(cx).snapshot();
6331 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6332 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6333 if start_word_range != end_word_range {
6334 self.document_highlights_task.take();
6335 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6336 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6337 return None;
6338 }
6339
6340 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6341 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6342 cx.background_executor()
6343 .timer(Duration::from_millis(debounce))
6344 .await;
6345
6346 let highlights = if let Some(highlights) = cx
6347 .update(|cx| {
6348 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6349 })
6350 .ok()
6351 .flatten()
6352 {
6353 highlights.await.log_err()
6354 } else {
6355 None
6356 };
6357
6358 if let Some(highlights) = highlights {
6359 this.update(cx, |this, cx| {
6360 if this.pending_rename.is_some() {
6361 return;
6362 }
6363
6364 let buffer_id = cursor_position.buffer_id;
6365 let buffer = this.buffer.read(cx);
6366 if !buffer
6367 .text_anchor_for_position(cursor_position, cx)
6368 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6369 {
6370 return;
6371 }
6372
6373 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6374 let mut write_ranges = Vec::new();
6375 let mut read_ranges = Vec::new();
6376 for highlight in highlights {
6377 for (excerpt_id, excerpt_range) in
6378 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6379 {
6380 let start = highlight
6381 .range
6382 .start
6383 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6384 let end = highlight
6385 .range
6386 .end
6387 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6388 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6389 continue;
6390 }
6391
6392 let range = Anchor {
6393 buffer_id,
6394 excerpt_id,
6395 text_anchor: start,
6396 diff_base_anchor: None,
6397 }..Anchor {
6398 buffer_id,
6399 excerpt_id,
6400 text_anchor: end,
6401 diff_base_anchor: None,
6402 };
6403 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6404 write_ranges.push(range);
6405 } else {
6406 read_ranges.push(range);
6407 }
6408 }
6409 }
6410
6411 this.highlight_background::<DocumentHighlightRead>(
6412 &read_ranges,
6413 |theme| theme.editor_document_highlight_read_background,
6414 cx,
6415 );
6416 this.highlight_background::<DocumentHighlightWrite>(
6417 &write_ranges,
6418 |theme| theme.editor_document_highlight_write_background,
6419 cx,
6420 );
6421 cx.notify();
6422 })
6423 .log_err();
6424 }
6425 }));
6426 None
6427 }
6428
6429 fn prepare_highlight_query_from_selection(
6430 &mut self,
6431 cx: &mut Context<Editor>,
6432 ) -> Option<(String, Range<Anchor>)> {
6433 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6434 return None;
6435 }
6436 if !EditorSettings::get_global(cx).selection_highlight {
6437 return None;
6438 }
6439 if self.selections.count() != 1 || self.selections.line_mode {
6440 return None;
6441 }
6442 let selection = self.selections.newest::<Point>(cx);
6443 if selection.is_empty() || selection.start.row != selection.end.row {
6444 return None;
6445 }
6446 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6447 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6448 let query = multi_buffer_snapshot
6449 .text_for_range(selection_anchor_range.clone())
6450 .collect::<String>();
6451 if query.trim().is_empty() {
6452 return None;
6453 }
6454 Some((query, selection_anchor_range))
6455 }
6456
6457 fn update_selection_occurrence_highlights(
6458 &mut self,
6459 query_text: String,
6460 query_range: Range<Anchor>,
6461 multi_buffer_range_to_query: Range<Point>,
6462 use_debounce: bool,
6463 window: &mut Window,
6464 cx: &mut Context<Editor>,
6465 ) -> Task<()> {
6466 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6467 cx.spawn_in(window, async move |editor, cx| {
6468 if use_debounce {
6469 cx.background_executor()
6470 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6471 .await;
6472 }
6473 let match_task = cx.background_spawn(async move {
6474 let buffer_ranges = multi_buffer_snapshot
6475 .range_to_buffer_ranges(multi_buffer_range_to_query)
6476 .into_iter()
6477 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6478 let mut match_ranges = Vec::new();
6479 let Ok(regex) = project::search::SearchQuery::text(
6480 query_text.clone(),
6481 false,
6482 false,
6483 false,
6484 Default::default(),
6485 Default::default(),
6486 false,
6487 None,
6488 ) else {
6489 return Vec::default();
6490 };
6491 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6492 match_ranges.extend(
6493 regex
6494 .search(&buffer_snapshot, Some(search_range.clone()))
6495 .await
6496 .into_iter()
6497 .filter_map(|match_range| {
6498 let match_start = buffer_snapshot
6499 .anchor_after(search_range.start + match_range.start);
6500 let match_end = buffer_snapshot
6501 .anchor_before(search_range.start + match_range.end);
6502 let match_anchor_range = Anchor::range_in_buffer(
6503 excerpt_id,
6504 buffer_snapshot.remote_id(),
6505 match_start..match_end,
6506 );
6507 (match_anchor_range != query_range).then_some(match_anchor_range)
6508 }),
6509 );
6510 }
6511 match_ranges
6512 });
6513 let match_ranges = match_task.await;
6514 editor
6515 .update_in(cx, |editor, _, cx| {
6516 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6517 if !match_ranges.is_empty() {
6518 editor.highlight_background::<SelectedTextHighlight>(
6519 &match_ranges,
6520 |theme| theme.editor_document_highlight_bracket_background,
6521 cx,
6522 )
6523 }
6524 })
6525 .log_err();
6526 })
6527 }
6528
6529 fn refresh_selected_text_highlights(
6530 &mut self,
6531 on_buffer_edit: bool,
6532 window: &mut Window,
6533 cx: &mut Context<Editor>,
6534 ) {
6535 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6536 else {
6537 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6538 self.quick_selection_highlight_task.take();
6539 self.debounced_selection_highlight_task.take();
6540 return;
6541 };
6542 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6543 if on_buffer_edit
6544 || self
6545 .quick_selection_highlight_task
6546 .as_ref()
6547 .map_or(true, |(prev_anchor_range, _)| {
6548 prev_anchor_range != &query_range
6549 })
6550 {
6551 let multi_buffer_visible_start = self
6552 .scroll_manager
6553 .anchor()
6554 .anchor
6555 .to_point(&multi_buffer_snapshot);
6556 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6557 multi_buffer_visible_start
6558 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6559 Bias::Left,
6560 );
6561 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6562 self.quick_selection_highlight_task = Some((
6563 query_range.clone(),
6564 self.update_selection_occurrence_highlights(
6565 query_text.clone(),
6566 query_range.clone(),
6567 multi_buffer_visible_range,
6568 false,
6569 window,
6570 cx,
6571 ),
6572 ));
6573 }
6574 if on_buffer_edit
6575 || self
6576 .debounced_selection_highlight_task
6577 .as_ref()
6578 .map_or(true, |(prev_anchor_range, _)| {
6579 prev_anchor_range != &query_range
6580 })
6581 {
6582 let multi_buffer_start = multi_buffer_snapshot
6583 .anchor_before(0)
6584 .to_point(&multi_buffer_snapshot);
6585 let multi_buffer_end = multi_buffer_snapshot
6586 .anchor_after(multi_buffer_snapshot.len())
6587 .to_point(&multi_buffer_snapshot);
6588 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6589 self.debounced_selection_highlight_task = Some((
6590 query_range.clone(),
6591 self.update_selection_occurrence_highlights(
6592 query_text,
6593 query_range,
6594 multi_buffer_full_range,
6595 true,
6596 window,
6597 cx,
6598 ),
6599 ));
6600 }
6601 }
6602
6603 pub fn refresh_inline_completion(
6604 &mut self,
6605 debounce: bool,
6606 user_requested: bool,
6607 window: &mut Window,
6608 cx: &mut Context<Self>,
6609 ) -> Option<()> {
6610 let provider = self.edit_prediction_provider()?;
6611 let cursor = self.selections.newest_anchor().head();
6612 let (buffer, cursor_buffer_position) =
6613 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6614
6615 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6616 self.discard_inline_completion(false, cx);
6617 return None;
6618 }
6619
6620 if !user_requested
6621 && (!self.should_show_edit_predictions()
6622 || !self.is_focused(window)
6623 || buffer.read(cx).is_empty())
6624 {
6625 self.discard_inline_completion(false, cx);
6626 return None;
6627 }
6628
6629 self.update_visible_inline_completion(window, cx);
6630 provider.refresh(
6631 self.project.clone(),
6632 buffer,
6633 cursor_buffer_position,
6634 debounce,
6635 cx,
6636 );
6637 Some(())
6638 }
6639
6640 fn show_edit_predictions_in_menu(&self) -> bool {
6641 match self.edit_prediction_settings {
6642 EditPredictionSettings::Disabled => false,
6643 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6644 }
6645 }
6646
6647 pub fn edit_predictions_enabled(&self) -> bool {
6648 match self.edit_prediction_settings {
6649 EditPredictionSettings::Disabled => false,
6650 EditPredictionSettings::Enabled { .. } => true,
6651 }
6652 }
6653
6654 fn edit_prediction_requires_modifier(&self) -> bool {
6655 match self.edit_prediction_settings {
6656 EditPredictionSettings::Disabled => false,
6657 EditPredictionSettings::Enabled {
6658 preview_requires_modifier,
6659 ..
6660 } => preview_requires_modifier,
6661 }
6662 }
6663
6664 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6665 if self.edit_prediction_provider.is_none() {
6666 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6667 } else {
6668 let selection = self.selections.newest_anchor();
6669 let cursor = selection.head();
6670
6671 if let Some((buffer, cursor_buffer_position)) =
6672 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6673 {
6674 self.edit_prediction_settings =
6675 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6676 }
6677 }
6678 }
6679
6680 fn edit_prediction_settings_at_position(
6681 &self,
6682 buffer: &Entity<Buffer>,
6683 buffer_position: language::Anchor,
6684 cx: &App,
6685 ) -> EditPredictionSettings {
6686 if !self.mode.is_full()
6687 || !self.show_inline_completions_override.unwrap_or(true)
6688 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6689 {
6690 return EditPredictionSettings::Disabled;
6691 }
6692
6693 let buffer = buffer.read(cx);
6694
6695 let file = buffer.file();
6696
6697 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6698 return EditPredictionSettings::Disabled;
6699 };
6700
6701 let by_provider = matches!(
6702 self.menu_inline_completions_policy,
6703 MenuInlineCompletionsPolicy::ByProvider
6704 );
6705
6706 let show_in_menu = by_provider
6707 && self
6708 .edit_prediction_provider
6709 .as_ref()
6710 .map_or(false, |provider| {
6711 provider.provider.show_completions_in_menu()
6712 });
6713
6714 let preview_requires_modifier =
6715 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6716
6717 EditPredictionSettings::Enabled {
6718 show_in_menu,
6719 preview_requires_modifier,
6720 }
6721 }
6722
6723 fn should_show_edit_predictions(&self) -> bool {
6724 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6725 }
6726
6727 pub fn edit_prediction_preview_is_active(&self) -> bool {
6728 matches!(
6729 self.edit_prediction_preview,
6730 EditPredictionPreview::Active { .. }
6731 )
6732 }
6733
6734 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6735 let cursor = self.selections.newest_anchor().head();
6736 if let Some((buffer, cursor_position)) =
6737 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6738 {
6739 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6740 } else {
6741 false
6742 }
6743 }
6744
6745 pub fn supports_minimap(&self, cx: &App) -> bool {
6746 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6747 }
6748
6749 fn edit_predictions_enabled_in_buffer(
6750 &self,
6751 buffer: &Entity<Buffer>,
6752 buffer_position: language::Anchor,
6753 cx: &App,
6754 ) -> bool {
6755 maybe!({
6756 if self.read_only(cx) {
6757 return Some(false);
6758 }
6759 let provider = self.edit_prediction_provider()?;
6760 if !provider.is_enabled(&buffer, buffer_position, cx) {
6761 return Some(false);
6762 }
6763 let buffer = buffer.read(cx);
6764 let Some(file) = buffer.file() else {
6765 return Some(true);
6766 };
6767 let settings = all_language_settings(Some(file), cx);
6768 Some(settings.edit_predictions_enabled_for_file(file, cx))
6769 })
6770 .unwrap_or(false)
6771 }
6772
6773 fn cycle_inline_completion(
6774 &mut self,
6775 direction: Direction,
6776 window: &mut Window,
6777 cx: &mut Context<Self>,
6778 ) -> Option<()> {
6779 let provider = self.edit_prediction_provider()?;
6780 let cursor = self.selections.newest_anchor().head();
6781 let (buffer, cursor_buffer_position) =
6782 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6783 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6784 return None;
6785 }
6786
6787 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6788 self.update_visible_inline_completion(window, cx);
6789
6790 Some(())
6791 }
6792
6793 pub fn show_inline_completion(
6794 &mut self,
6795 _: &ShowEditPrediction,
6796 window: &mut Window,
6797 cx: &mut Context<Self>,
6798 ) {
6799 if !self.has_active_inline_completion() {
6800 self.refresh_inline_completion(false, true, window, cx);
6801 return;
6802 }
6803
6804 self.update_visible_inline_completion(window, cx);
6805 }
6806
6807 pub fn display_cursor_names(
6808 &mut self,
6809 _: &DisplayCursorNames,
6810 window: &mut Window,
6811 cx: &mut Context<Self>,
6812 ) {
6813 self.show_cursor_names(window, cx);
6814 }
6815
6816 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6817 self.show_cursor_names = true;
6818 cx.notify();
6819 cx.spawn_in(window, async move |this, cx| {
6820 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6821 this.update(cx, |this, cx| {
6822 this.show_cursor_names = false;
6823 cx.notify()
6824 })
6825 .ok()
6826 })
6827 .detach();
6828 }
6829
6830 pub fn next_edit_prediction(
6831 &mut self,
6832 _: &NextEditPrediction,
6833 window: &mut Window,
6834 cx: &mut Context<Self>,
6835 ) {
6836 if self.has_active_inline_completion() {
6837 self.cycle_inline_completion(Direction::Next, window, cx);
6838 } else {
6839 let is_copilot_disabled = self
6840 .refresh_inline_completion(false, true, window, cx)
6841 .is_none();
6842 if is_copilot_disabled {
6843 cx.propagate();
6844 }
6845 }
6846 }
6847
6848 pub fn previous_edit_prediction(
6849 &mut self,
6850 _: &PreviousEditPrediction,
6851 window: &mut Window,
6852 cx: &mut Context<Self>,
6853 ) {
6854 if self.has_active_inline_completion() {
6855 self.cycle_inline_completion(Direction::Prev, window, cx);
6856 } else {
6857 let is_copilot_disabled = self
6858 .refresh_inline_completion(false, true, window, cx)
6859 .is_none();
6860 if is_copilot_disabled {
6861 cx.propagate();
6862 }
6863 }
6864 }
6865
6866 pub fn accept_edit_prediction(
6867 &mut self,
6868 _: &AcceptEditPrediction,
6869 window: &mut Window,
6870 cx: &mut Context<Self>,
6871 ) {
6872 if self.show_edit_predictions_in_menu() {
6873 self.hide_context_menu(window, cx);
6874 }
6875
6876 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6877 return;
6878 };
6879
6880 self.report_inline_completion_event(
6881 active_inline_completion.completion_id.clone(),
6882 true,
6883 cx,
6884 );
6885
6886 match &active_inline_completion.completion {
6887 InlineCompletion::Move { target, .. } => {
6888 let target = *target;
6889
6890 if let Some(position_map) = &self.last_position_map {
6891 if position_map
6892 .visible_row_range
6893 .contains(&target.to_display_point(&position_map.snapshot).row())
6894 || !self.edit_prediction_requires_modifier()
6895 {
6896 self.unfold_ranges(&[target..target], true, false, cx);
6897 // Note that this is also done in vim's handler of the Tab action.
6898 self.change_selections(
6899 Some(Autoscroll::newest()),
6900 window,
6901 cx,
6902 |selections| {
6903 selections.select_anchor_ranges([target..target]);
6904 },
6905 );
6906 self.clear_row_highlights::<EditPredictionPreview>();
6907
6908 self.edit_prediction_preview
6909 .set_previous_scroll_position(None);
6910 } else {
6911 self.edit_prediction_preview
6912 .set_previous_scroll_position(Some(
6913 position_map.snapshot.scroll_anchor,
6914 ));
6915
6916 self.highlight_rows::<EditPredictionPreview>(
6917 target..target,
6918 cx.theme().colors().editor_highlighted_line_background,
6919 RowHighlightOptions {
6920 autoscroll: true,
6921 ..Default::default()
6922 },
6923 cx,
6924 );
6925 self.request_autoscroll(Autoscroll::fit(), cx);
6926 }
6927 }
6928 }
6929 InlineCompletion::Edit { edits, .. } => {
6930 if let Some(provider) = self.edit_prediction_provider() {
6931 provider.accept(cx);
6932 }
6933
6934 // Store the transaction ID and selections before applying the edit
6935 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6936
6937 let snapshot = self.buffer.read(cx).snapshot(cx);
6938 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6939
6940 self.buffer.update(cx, |buffer, cx| {
6941 buffer.edit(edits.iter().cloned(), None, cx)
6942 });
6943
6944 self.change_selections(None, window, cx, |s| {
6945 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6946 });
6947
6948 let selections = self.selections.disjoint_anchors();
6949 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6950 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6951 if has_new_transaction {
6952 self.selection_history
6953 .insert_transaction(transaction_id_now, selections);
6954 }
6955 }
6956
6957 self.update_visible_inline_completion(window, cx);
6958 if self.active_inline_completion.is_none() {
6959 self.refresh_inline_completion(true, true, window, cx);
6960 }
6961
6962 cx.notify();
6963 }
6964 }
6965
6966 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6967 }
6968
6969 pub fn accept_partial_inline_completion(
6970 &mut self,
6971 _: &AcceptPartialEditPrediction,
6972 window: &mut Window,
6973 cx: &mut Context<Self>,
6974 ) {
6975 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6976 return;
6977 };
6978 if self.selections.count() != 1 {
6979 return;
6980 }
6981
6982 self.report_inline_completion_event(
6983 active_inline_completion.completion_id.clone(),
6984 true,
6985 cx,
6986 );
6987
6988 match &active_inline_completion.completion {
6989 InlineCompletion::Move { target, .. } => {
6990 let target = *target;
6991 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6992 selections.select_anchor_ranges([target..target]);
6993 });
6994 }
6995 InlineCompletion::Edit { edits, .. } => {
6996 // Find an insertion that starts at the cursor position.
6997 let snapshot = self.buffer.read(cx).snapshot(cx);
6998 let cursor_offset = self.selections.newest::<usize>(cx).head();
6999 let insertion = edits.iter().find_map(|(range, text)| {
7000 let range = range.to_offset(&snapshot);
7001 if range.is_empty() && range.start == cursor_offset {
7002 Some(text)
7003 } else {
7004 None
7005 }
7006 });
7007
7008 if let Some(text) = insertion {
7009 let mut partial_completion = text
7010 .chars()
7011 .by_ref()
7012 .take_while(|c| c.is_alphabetic())
7013 .collect::<String>();
7014 if partial_completion.is_empty() {
7015 partial_completion = text
7016 .chars()
7017 .by_ref()
7018 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7019 .collect::<String>();
7020 }
7021
7022 cx.emit(EditorEvent::InputHandled {
7023 utf16_range_to_replace: None,
7024 text: partial_completion.clone().into(),
7025 });
7026
7027 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7028
7029 self.refresh_inline_completion(true, true, window, cx);
7030 cx.notify();
7031 } else {
7032 self.accept_edit_prediction(&Default::default(), window, cx);
7033 }
7034 }
7035 }
7036 }
7037
7038 fn discard_inline_completion(
7039 &mut self,
7040 should_report_inline_completion_event: bool,
7041 cx: &mut Context<Self>,
7042 ) -> bool {
7043 if should_report_inline_completion_event {
7044 let completion_id = self
7045 .active_inline_completion
7046 .as_ref()
7047 .and_then(|active_completion| active_completion.completion_id.clone());
7048
7049 self.report_inline_completion_event(completion_id, false, cx);
7050 }
7051
7052 if let Some(provider) = self.edit_prediction_provider() {
7053 provider.discard(cx);
7054 }
7055
7056 self.take_active_inline_completion(cx)
7057 }
7058
7059 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7060 let Some(provider) = self.edit_prediction_provider() else {
7061 return;
7062 };
7063
7064 let Some((_, buffer, _)) = self
7065 .buffer
7066 .read(cx)
7067 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7068 else {
7069 return;
7070 };
7071
7072 let extension = buffer
7073 .read(cx)
7074 .file()
7075 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7076
7077 let event_type = match accepted {
7078 true => "Edit Prediction Accepted",
7079 false => "Edit Prediction Discarded",
7080 };
7081 telemetry::event!(
7082 event_type,
7083 provider = provider.name(),
7084 prediction_id = id,
7085 suggestion_accepted = accepted,
7086 file_extension = extension,
7087 );
7088 }
7089
7090 pub fn has_active_inline_completion(&self) -> bool {
7091 self.active_inline_completion.is_some()
7092 }
7093
7094 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7095 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7096 return false;
7097 };
7098
7099 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7100 self.clear_highlights::<InlineCompletionHighlight>(cx);
7101 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7102 true
7103 }
7104
7105 /// Returns true when we're displaying the edit prediction popover below the cursor
7106 /// like we are not previewing and the LSP autocomplete menu is visible
7107 /// or we are in `when_holding_modifier` mode.
7108 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7109 if self.edit_prediction_preview_is_active()
7110 || !self.show_edit_predictions_in_menu()
7111 || !self.edit_predictions_enabled()
7112 {
7113 return false;
7114 }
7115
7116 if self.has_visible_completions_menu() {
7117 return true;
7118 }
7119
7120 has_completion && self.edit_prediction_requires_modifier()
7121 }
7122
7123 fn handle_modifiers_changed(
7124 &mut self,
7125 modifiers: Modifiers,
7126 position_map: &PositionMap,
7127 window: &mut Window,
7128 cx: &mut Context<Self>,
7129 ) {
7130 if self.show_edit_predictions_in_menu() {
7131 self.update_edit_prediction_preview(&modifiers, window, cx);
7132 }
7133
7134 self.update_selection_mode(&modifiers, position_map, window, cx);
7135
7136 let mouse_position = window.mouse_position();
7137 if !position_map.text_hitbox.is_hovered(window) {
7138 return;
7139 }
7140
7141 self.update_hovered_link(
7142 position_map.point_for_position(mouse_position),
7143 &position_map.snapshot,
7144 modifiers,
7145 window,
7146 cx,
7147 )
7148 }
7149
7150 fn multi_cursor_modifier(
7151 cursor_event: bool,
7152 modifiers: &Modifiers,
7153 cx: &mut Context<Self>,
7154 ) -> bool {
7155 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7156 if cursor_event {
7157 match multi_cursor_setting {
7158 MultiCursorModifier::Alt => modifiers.alt,
7159 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7160 }
7161 } else {
7162 match multi_cursor_setting {
7163 MultiCursorModifier::Alt => modifiers.secondary(),
7164 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7165 }
7166 }
7167 }
7168
7169 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7170 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7171 }
7172
7173 fn update_selection_mode(
7174 &mut self,
7175 modifiers: &Modifiers,
7176 position_map: &PositionMap,
7177 window: &mut Window,
7178 cx: &mut Context<Self>,
7179 ) {
7180 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7181 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7182 || self.selections.pending.is_none()
7183 {
7184 return;
7185 }
7186
7187 let mouse_position = window.mouse_position();
7188 let point_for_position = position_map.point_for_position(mouse_position);
7189 let position = point_for_position.previous_valid;
7190
7191 self.select(
7192 SelectPhase::BeginColumnar {
7193 position,
7194 reset: false,
7195 goal_column: point_for_position.exact_unclipped.column(),
7196 },
7197 window,
7198 cx,
7199 );
7200 }
7201
7202 fn update_edit_prediction_preview(
7203 &mut self,
7204 modifiers: &Modifiers,
7205 window: &mut Window,
7206 cx: &mut Context<Self>,
7207 ) {
7208 let mut modifiers_held = false;
7209 if let Some(accept_keystroke) = self
7210 .accept_edit_prediction_keybind(false, window, cx)
7211 .keystroke()
7212 {
7213 modifiers_held = modifiers_held
7214 || (&accept_keystroke.modifiers == modifiers
7215 && accept_keystroke.modifiers.modified());
7216 };
7217 if let Some(accept_partial_keystroke) = self
7218 .accept_edit_prediction_keybind(true, window, cx)
7219 .keystroke()
7220 {
7221 modifiers_held = modifiers_held
7222 || (&accept_partial_keystroke.modifiers == modifiers
7223 && accept_partial_keystroke.modifiers.modified());
7224 }
7225
7226 if modifiers_held {
7227 if matches!(
7228 self.edit_prediction_preview,
7229 EditPredictionPreview::Inactive { .. }
7230 ) {
7231 self.edit_prediction_preview = EditPredictionPreview::Active {
7232 previous_scroll_position: None,
7233 since: Instant::now(),
7234 };
7235
7236 self.update_visible_inline_completion(window, cx);
7237 cx.notify();
7238 }
7239 } else if let EditPredictionPreview::Active {
7240 previous_scroll_position,
7241 since,
7242 } = self.edit_prediction_preview
7243 {
7244 if let (Some(previous_scroll_position), Some(position_map)) =
7245 (previous_scroll_position, self.last_position_map.as_ref())
7246 {
7247 self.set_scroll_position(
7248 previous_scroll_position
7249 .scroll_position(&position_map.snapshot.display_snapshot),
7250 window,
7251 cx,
7252 );
7253 }
7254
7255 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7256 released_too_fast: since.elapsed() < Duration::from_millis(200),
7257 };
7258 self.clear_row_highlights::<EditPredictionPreview>();
7259 self.update_visible_inline_completion(window, cx);
7260 cx.notify();
7261 }
7262 }
7263
7264 fn update_visible_inline_completion(
7265 &mut self,
7266 _window: &mut Window,
7267 cx: &mut Context<Self>,
7268 ) -> Option<()> {
7269 let selection = self.selections.newest_anchor();
7270 let cursor = selection.head();
7271 let multibuffer = self.buffer.read(cx).snapshot(cx);
7272 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7273 let excerpt_id = cursor.excerpt_id;
7274
7275 let show_in_menu = self.show_edit_predictions_in_menu();
7276 let completions_menu_has_precedence = !show_in_menu
7277 && (self.context_menu.borrow().is_some()
7278 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7279
7280 if completions_menu_has_precedence
7281 || !offset_selection.is_empty()
7282 || self
7283 .active_inline_completion
7284 .as_ref()
7285 .map_or(false, |completion| {
7286 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7287 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7288 !invalidation_range.contains(&offset_selection.head())
7289 })
7290 {
7291 self.discard_inline_completion(false, cx);
7292 return None;
7293 }
7294
7295 self.take_active_inline_completion(cx);
7296 let Some(provider) = self.edit_prediction_provider() else {
7297 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7298 return None;
7299 };
7300
7301 let (buffer, cursor_buffer_position) =
7302 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7303
7304 self.edit_prediction_settings =
7305 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7306
7307 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7308
7309 if self.edit_prediction_indent_conflict {
7310 let cursor_point = cursor.to_point(&multibuffer);
7311
7312 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7313
7314 if let Some((_, indent)) = indents.iter().next() {
7315 if indent.len == cursor_point.column {
7316 self.edit_prediction_indent_conflict = false;
7317 }
7318 }
7319 }
7320
7321 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7322 let edits = inline_completion
7323 .edits
7324 .into_iter()
7325 .flat_map(|(range, new_text)| {
7326 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7327 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7328 Some((start..end, new_text))
7329 })
7330 .collect::<Vec<_>>();
7331 if edits.is_empty() {
7332 return None;
7333 }
7334
7335 let first_edit_start = edits.first().unwrap().0.start;
7336 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7337 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7338
7339 let last_edit_end = edits.last().unwrap().0.end;
7340 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7341 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7342
7343 let cursor_row = cursor.to_point(&multibuffer).row;
7344
7345 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7346
7347 let mut inlay_ids = Vec::new();
7348 let invalidation_row_range;
7349 let move_invalidation_row_range = if cursor_row < edit_start_row {
7350 Some(cursor_row..edit_end_row)
7351 } else if cursor_row > edit_end_row {
7352 Some(edit_start_row..cursor_row)
7353 } else {
7354 None
7355 };
7356 let is_move =
7357 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7358 let completion = if is_move {
7359 invalidation_row_range =
7360 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7361 let target = first_edit_start;
7362 InlineCompletion::Move { target, snapshot }
7363 } else {
7364 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7365 && !self.inline_completions_hidden_for_vim_mode;
7366
7367 if show_completions_in_buffer {
7368 if edits
7369 .iter()
7370 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7371 {
7372 let mut inlays = Vec::new();
7373 for (range, new_text) in &edits {
7374 let inlay = Inlay::inline_completion(
7375 post_inc(&mut self.next_inlay_id),
7376 range.start,
7377 new_text.as_str(),
7378 );
7379 inlay_ids.push(inlay.id);
7380 inlays.push(inlay);
7381 }
7382
7383 self.splice_inlays(&[], inlays, cx);
7384 } else {
7385 let background_color = cx.theme().status().deleted_background;
7386 self.highlight_text::<InlineCompletionHighlight>(
7387 edits.iter().map(|(range, _)| range.clone()).collect(),
7388 HighlightStyle {
7389 background_color: Some(background_color),
7390 ..Default::default()
7391 },
7392 cx,
7393 );
7394 }
7395 }
7396
7397 invalidation_row_range = edit_start_row..edit_end_row;
7398
7399 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7400 if provider.show_tab_accept_marker() {
7401 EditDisplayMode::TabAccept
7402 } else {
7403 EditDisplayMode::Inline
7404 }
7405 } else {
7406 EditDisplayMode::DiffPopover
7407 };
7408
7409 InlineCompletion::Edit {
7410 edits,
7411 edit_preview: inline_completion.edit_preview,
7412 display_mode,
7413 snapshot,
7414 }
7415 };
7416
7417 let invalidation_range = multibuffer
7418 .anchor_before(Point::new(invalidation_row_range.start, 0))
7419 ..multibuffer.anchor_after(Point::new(
7420 invalidation_row_range.end,
7421 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7422 ));
7423
7424 self.stale_inline_completion_in_menu = None;
7425 self.active_inline_completion = Some(InlineCompletionState {
7426 inlay_ids,
7427 completion,
7428 completion_id: inline_completion.id,
7429 invalidation_range,
7430 });
7431
7432 cx.notify();
7433
7434 Some(())
7435 }
7436
7437 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7438 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7439 }
7440
7441 fn clear_tasks(&mut self) {
7442 self.tasks.clear()
7443 }
7444
7445 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7446 if self.tasks.insert(key, value).is_some() {
7447 // This case should hopefully be rare, but just in case...
7448 log::error!(
7449 "multiple different run targets found on a single line, only the last target will be rendered"
7450 )
7451 }
7452 }
7453
7454 /// Get all display points of breakpoints that will be rendered within editor
7455 ///
7456 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7457 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7458 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7459 fn active_breakpoints(
7460 &self,
7461 range: Range<DisplayRow>,
7462 window: &mut Window,
7463 cx: &mut Context<Self>,
7464 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7465 let mut breakpoint_display_points = HashMap::default();
7466
7467 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7468 return breakpoint_display_points;
7469 };
7470
7471 let snapshot = self.snapshot(window, cx);
7472
7473 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7474 let Some(project) = self.project.as_ref() else {
7475 return breakpoint_display_points;
7476 };
7477
7478 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7479 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7480
7481 for (buffer_snapshot, range, excerpt_id) in
7482 multi_buffer_snapshot.range_to_buffer_ranges(range)
7483 {
7484 let Some(buffer) = project
7485 .read(cx)
7486 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7487 else {
7488 continue;
7489 };
7490 let breakpoints = breakpoint_store.read(cx).breakpoints(
7491 &buffer,
7492 Some(
7493 buffer_snapshot.anchor_before(range.start)
7494 ..buffer_snapshot.anchor_after(range.end),
7495 ),
7496 buffer_snapshot,
7497 cx,
7498 );
7499 for (breakpoint, state) in breakpoints {
7500 let multi_buffer_anchor =
7501 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7502 let position = multi_buffer_anchor
7503 .to_point(&multi_buffer_snapshot)
7504 .to_display_point(&snapshot);
7505
7506 breakpoint_display_points.insert(
7507 position.row(),
7508 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7509 );
7510 }
7511 }
7512
7513 breakpoint_display_points
7514 }
7515
7516 fn breakpoint_context_menu(
7517 &self,
7518 anchor: Anchor,
7519 window: &mut Window,
7520 cx: &mut Context<Self>,
7521 ) -> Entity<ui::ContextMenu> {
7522 let weak_editor = cx.weak_entity();
7523 let focus_handle = self.focus_handle(cx);
7524
7525 let row = self
7526 .buffer
7527 .read(cx)
7528 .snapshot(cx)
7529 .summary_for_anchor::<Point>(&anchor)
7530 .row;
7531
7532 let breakpoint = self
7533 .breakpoint_at_row(row, window, cx)
7534 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7535
7536 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7537 "Edit Log Breakpoint"
7538 } else {
7539 "Set Log Breakpoint"
7540 };
7541
7542 let condition_breakpoint_msg = if breakpoint
7543 .as_ref()
7544 .is_some_and(|bp| bp.1.condition.is_some())
7545 {
7546 "Edit Condition Breakpoint"
7547 } else {
7548 "Set Condition Breakpoint"
7549 };
7550
7551 let hit_condition_breakpoint_msg = if breakpoint
7552 .as_ref()
7553 .is_some_and(|bp| bp.1.hit_condition.is_some())
7554 {
7555 "Edit Hit Condition Breakpoint"
7556 } else {
7557 "Set Hit Condition Breakpoint"
7558 };
7559
7560 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7561 "Unset Breakpoint"
7562 } else {
7563 "Set Breakpoint"
7564 };
7565
7566 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7567
7568 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7569 BreakpointState::Enabled => Some("Disable"),
7570 BreakpointState::Disabled => Some("Enable"),
7571 });
7572
7573 let (anchor, breakpoint) =
7574 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7575
7576 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7577 menu.on_blur_subscription(Subscription::new(|| {}))
7578 .context(focus_handle)
7579 .when(run_to_cursor, |this| {
7580 let weak_editor = weak_editor.clone();
7581 this.entry("Run to cursor", None, move |window, cx| {
7582 weak_editor
7583 .update(cx, |editor, cx| {
7584 editor.change_selections(None, window, cx, |s| {
7585 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7586 });
7587 })
7588 .ok();
7589
7590 window.dispatch_action(Box::new(RunToCursor), cx);
7591 })
7592 .separator()
7593 })
7594 .when_some(toggle_state_msg, |this, msg| {
7595 this.entry(msg, None, {
7596 let weak_editor = weak_editor.clone();
7597 let breakpoint = breakpoint.clone();
7598 move |_window, cx| {
7599 weak_editor
7600 .update(cx, |this, cx| {
7601 this.edit_breakpoint_at_anchor(
7602 anchor,
7603 breakpoint.as_ref().clone(),
7604 BreakpointEditAction::InvertState,
7605 cx,
7606 );
7607 })
7608 .log_err();
7609 }
7610 })
7611 })
7612 .entry(set_breakpoint_msg, None, {
7613 let weak_editor = weak_editor.clone();
7614 let breakpoint = breakpoint.clone();
7615 move |_window, cx| {
7616 weak_editor
7617 .update(cx, |this, cx| {
7618 this.edit_breakpoint_at_anchor(
7619 anchor,
7620 breakpoint.as_ref().clone(),
7621 BreakpointEditAction::Toggle,
7622 cx,
7623 );
7624 })
7625 .log_err();
7626 }
7627 })
7628 .entry(log_breakpoint_msg, None, {
7629 let breakpoint = breakpoint.clone();
7630 let weak_editor = weak_editor.clone();
7631 move |window, cx| {
7632 weak_editor
7633 .update(cx, |this, cx| {
7634 this.add_edit_breakpoint_block(
7635 anchor,
7636 breakpoint.as_ref(),
7637 BreakpointPromptEditAction::Log,
7638 window,
7639 cx,
7640 );
7641 })
7642 .log_err();
7643 }
7644 })
7645 .entry(condition_breakpoint_msg, None, {
7646 let breakpoint = breakpoint.clone();
7647 let weak_editor = weak_editor.clone();
7648 move |window, cx| {
7649 weak_editor
7650 .update(cx, |this, cx| {
7651 this.add_edit_breakpoint_block(
7652 anchor,
7653 breakpoint.as_ref(),
7654 BreakpointPromptEditAction::Condition,
7655 window,
7656 cx,
7657 );
7658 })
7659 .log_err();
7660 }
7661 })
7662 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7663 weak_editor
7664 .update(cx, |this, cx| {
7665 this.add_edit_breakpoint_block(
7666 anchor,
7667 breakpoint.as_ref(),
7668 BreakpointPromptEditAction::HitCondition,
7669 window,
7670 cx,
7671 );
7672 })
7673 .log_err();
7674 })
7675 })
7676 }
7677
7678 fn render_breakpoint(
7679 &self,
7680 position: Anchor,
7681 row: DisplayRow,
7682 breakpoint: &Breakpoint,
7683 state: Option<BreakpointSessionState>,
7684 cx: &mut Context<Self>,
7685 ) -> IconButton {
7686 let is_rejected = state.is_some_and(|s| !s.verified);
7687 // Is it a breakpoint that shows up when hovering over gutter?
7688 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7689 (false, false),
7690 |PhantomBreakpointIndicator {
7691 is_active,
7692 display_row,
7693 collides_with_existing_breakpoint,
7694 }| {
7695 (
7696 is_active && display_row == row,
7697 collides_with_existing_breakpoint,
7698 )
7699 },
7700 );
7701
7702 let (color, icon) = {
7703 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7704 (false, false) => ui::IconName::DebugBreakpoint,
7705 (true, false) => ui::IconName::DebugLogBreakpoint,
7706 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7707 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7708 };
7709
7710 let color = if is_phantom {
7711 Color::Hint
7712 } else if is_rejected {
7713 Color::Disabled
7714 } else {
7715 Color::Debugger
7716 };
7717
7718 (color, icon)
7719 };
7720
7721 let breakpoint = Arc::from(breakpoint.clone());
7722
7723 let alt_as_text = gpui::Keystroke {
7724 modifiers: Modifiers::secondary_key(),
7725 ..Default::default()
7726 };
7727 let primary_action_text = if breakpoint.is_disabled() {
7728 "Enable breakpoint"
7729 } else if is_phantom && !collides_with_existing {
7730 "Set breakpoint"
7731 } else {
7732 "Unset breakpoint"
7733 };
7734 let focus_handle = self.focus_handle.clone();
7735
7736 let meta = if is_rejected {
7737 SharedString::from("No executable code is associated with this line.")
7738 } else if collides_with_existing && !breakpoint.is_disabled() {
7739 SharedString::from(format!(
7740 "{alt_as_text}-click to disable,\nright-click for more options."
7741 ))
7742 } else {
7743 SharedString::from("Right-click for more options.")
7744 };
7745 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7746 .icon_size(IconSize::XSmall)
7747 .size(ui::ButtonSize::None)
7748 .when(is_rejected, |this| {
7749 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7750 })
7751 .icon_color(color)
7752 .style(ButtonStyle::Transparent)
7753 .on_click(cx.listener({
7754 let breakpoint = breakpoint.clone();
7755
7756 move |editor, event: &ClickEvent, window, cx| {
7757 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7758 BreakpointEditAction::InvertState
7759 } else {
7760 BreakpointEditAction::Toggle
7761 };
7762
7763 window.focus(&editor.focus_handle(cx));
7764 editor.edit_breakpoint_at_anchor(
7765 position,
7766 breakpoint.as_ref().clone(),
7767 edit_action,
7768 cx,
7769 );
7770 }
7771 }))
7772 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7773 editor.set_breakpoint_context_menu(
7774 row,
7775 Some(position),
7776 event.down.position,
7777 window,
7778 cx,
7779 );
7780 }))
7781 .tooltip(move |window, cx| {
7782 Tooltip::with_meta_in(
7783 primary_action_text,
7784 Some(&ToggleBreakpoint),
7785 meta.clone(),
7786 &focus_handle,
7787 window,
7788 cx,
7789 )
7790 })
7791 }
7792
7793 fn build_tasks_context(
7794 project: &Entity<Project>,
7795 buffer: &Entity<Buffer>,
7796 buffer_row: u32,
7797 tasks: &Arc<RunnableTasks>,
7798 cx: &mut Context<Self>,
7799 ) -> Task<Option<task::TaskContext>> {
7800 let position = Point::new(buffer_row, tasks.column);
7801 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7802 let location = Location {
7803 buffer: buffer.clone(),
7804 range: range_start..range_start,
7805 };
7806 // Fill in the environmental variables from the tree-sitter captures
7807 let mut captured_task_variables = TaskVariables::default();
7808 for (capture_name, value) in tasks.extra_variables.clone() {
7809 captured_task_variables.insert(
7810 task::VariableName::Custom(capture_name.into()),
7811 value.clone(),
7812 );
7813 }
7814 project.update(cx, |project, cx| {
7815 project.task_store().update(cx, |task_store, cx| {
7816 task_store.task_context_for_location(captured_task_variables, location, cx)
7817 })
7818 })
7819 }
7820
7821 pub fn spawn_nearest_task(
7822 &mut self,
7823 action: &SpawnNearestTask,
7824 window: &mut Window,
7825 cx: &mut Context<Self>,
7826 ) {
7827 let Some((workspace, _)) = self.workspace.clone() else {
7828 return;
7829 };
7830 let Some(project) = self.project.clone() else {
7831 return;
7832 };
7833
7834 // Try to find a closest, enclosing node using tree-sitter that has a
7835 // task
7836 let Some((buffer, buffer_row, tasks)) = self
7837 .find_enclosing_node_task(cx)
7838 // Or find the task that's closest in row-distance.
7839 .or_else(|| self.find_closest_task(cx))
7840 else {
7841 return;
7842 };
7843
7844 let reveal_strategy = action.reveal;
7845 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7846 cx.spawn_in(window, async move |_, cx| {
7847 let context = task_context.await?;
7848 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7849
7850 let resolved = &mut resolved_task.resolved;
7851 resolved.reveal = reveal_strategy;
7852
7853 workspace
7854 .update_in(cx, |workspace, window, cx| {
7855 workspace.schedule_resolved_task(
7856 task_source_kind,
7857 resolved_task,
7858 false,
7859 window,
7860 cx,
7861 );
7862 })
7863 .ok()
7864 })
7865 .detach();
7866 }
7867
7868 fn find_closest_task(
7869 &mut self,
7870 cx: &mut Context<Self>,
7871 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7872 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7873
7874 let ((buffer_id, row), tasks) = self
7875 .tasks
7876 .iter()
7877 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7878
7879 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7880 let tasks = Arc::new(tasks.to_owned());
7881 Some((buffer, *row, tasks))
7882 }
7883
7884 fn find_enclosing_node_task(
7885 &mut self,
7886 cx: &mut Context<Self>,
7887 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7888 let snapshot = self.buffer.read(cx).snapshot(cx);
7889 let offset = self.selections.newest::<usize>(cx).head();
7890 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7891 let buffer_id = excerpt.buffer().remote_id();
7892
7893 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7894 let mut cursor = layer.node().walk();
7895
7896 while cursor.goto_first_child_for_byte(offset).is_some() {
7897 if cursor.node().end_byte() == offset {
7898 cursor.goto_next_sibling();
7899 }
7900 }
7901
7902 // Ascend to the smallest ancestor that contains the range and has a task.
7903 loop {
7904 let node = cursor.node();
7905 let node_range = node.byte_range();
7906 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7907
7908 // Check if this node contains our offset
7909 if node_range.start <= offset && node_range.end >= offset {
7910 // If it contains offset, check for task
7911 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7912 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7913 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7914 }
7915 }
7916
7917 if !cursor.goto_parent() {
7918 break;
7919 }
7920 }
7921 None
7922 }
7923
7924 fn render_run_indicator(
7925 &self,
7926 _style: &EditorStyle,
7927 is_active: bool,
7928 row: DisplayRow,
7929 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7930 cx: &mut Context<Self>,
7931 ) -> IconButton {
7932 let color = Color::Muted;
7933 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7934
7935 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7936 .shape(ui::IconButtonShape::Square)
7937 .icon_size(IconSize::XSmall)
7938 .icon_color(color)
7939 .toggle_state(is_active)
7940 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7941 let quick_launch = e.down.button == MouseButton::Left;
7942 window.focus(&editor.focus_handle(cx));
7943 editor.toggle_code_actions(
7944 &ToggleCodeActions {
7945 deployed_from: Some(CodeActionSource::RunMenu(row)),
7946 quick_launch,
7947 },
7948 window,
7949 cx,
7950 );
7951 }))
7952 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7953 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7954 }))
7955 }
7956
7957 pub fn context_menu_visible(&self) -> bool {
7958 !self.edit_prediction_preview_is_active()
7959 && self
7960 .context_menu
7961 .borrow()
7962 .as_ref()
7963 .map_or(false, |menu| menu.visible())
7964 }
7965
7966 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7967 self.context_menu
7968 .borrow()
7969 .as_ref()
7970 .map(|menu| menu.origin())
7971 }
7972
7973 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7974 self.context_menu_options = Some(options);
7975 }
7976
7977 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7978 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7979
7980 fn render_edit_prediction_popover(
7981 &mut self,
7982 text_bounds: &Bounds<Pixels>,
7983 content_origin: gpui::Point<Pixels>,
7984 right_margin: Pixels,
7985 editor_snapshot: &EditorSnapshot,
7986 visible_row_range: Range<DisplayRow>,
7987 scroll_top: f32,
7988 scroll_bottom: f32,
7989 line_layouts: &[LineWithInvisibles],
7990 line_height: Pixels,
7991 scroll_pixel_position: gpui::Point<Pixels>,
7992 newest_selection_head: Option<DisplayPoint>,
7993 editor_width: Pixels,
7994 style: &EditorStyle,
7995 window: &mut Window,
7996 cx: &mut App,
7997 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7998 if self.mode().is_minimap() {
7999 return None;
8000 }
8001 let active_inline_completion = self.active_inline_completion.as_ref()?;
8002
8003 if self.edit_prediction_visible_in_cursor_popover(true) {
8004 return None;
8005 }
8006
8007 match &active_inline_completion.completion {
8008 InlineCompletion::Move { target, .. } => {
8009 let target_display_point = target.to_display_point(editor_snapshot);
8010
8011 if self.edit_prediction_requires_modifier() {
8012 if !self.edit_prediction_preview_is_active() {
8013 return None;
8014 }
8015
8016 self.render_edit_prediction_modifier_jump_popover(
8017 text_bounds,
8018 content_origin,
8019 visible_row_range,
8020 line_layouts,
8021 line_height,
8022 scroll_pixel_position,
8023 newest_selection_head,
8024 target_display_point,
8025 window,
8026 cx,
8027 )
8028 } else {
8029 self.render_edit_prediction_eager_jump_popover(
8030 text_bounds,
8031 content_origin,
8032 editor_snapshot,
8033 visible_row_range,
8034 scroll_top,
8035 scroll_bottom,
8036 line_height,
8037 scroll_pixel_position,
8038 target_display_point,
8039 editor_width,
8040 window,
8041 cx,
8042 )
8043 }
8044 }
8045 InlineCompletion::Edit {
8046 display_mode: EditDisplayMode::Inline,
8047 ..
8048 } => None,
8049 InlineCompletion::Edit {
8050 display_mode: EditDisplayMode::TabAccept,
8051 edits,
8052 ..
8053 } => {
8054 let range = &edits.first()?.0;
8055 let target_display_point = range.end.to_display_point(editor_snapshot);
8056
8057 self.render_edit_prediction_end_of_line_popover(
8058 "Accept",
8059 editor_snapshot,
8060 visible_row_range,
8061 target_display_point,
8062 line_height,
8063 scroll_pixel_position,
8064 content_origin,
8065 editor_width,
8066 window,
8067 cx,
8068 )
8069 }
8070 InlineCompletion::Edit {
8071 edits,
8072 edit_preview,
8073 display_mode: EditDisplayMode::DiffPopover,
8074 snapshot,
8075 } => self.render_edit_prediction_diff_popover(
8076 text_bounds,
8077 content_origin,
8078 right_margin,
8079 editor_snapshot,
8080 visible_row_range,
8081 line_layouts,
8082 line_height,
8083 scroll_pixel_position,
8084 newest_selection_head,
8085 editor_width,
8086 style,
8087 edits,
8088 edit_preview,
8089 snapshot,
8090 window,
8091 cx,
8092 ),
8093 }
8094 }
8095
8096 fn render_edit_prediction_modifier_jump_popover(
8097 &mut self,
8098 text_bounds: &Bounds<Pixels>,
8099 content_origin: gpui::Point<Pixels>,
8100 visible_row_range: Range<DisplayRow>,
8101 line_layouts: &[LineWithInvisibles],
8102 line_height: Pixels,
8103 scroll_pixel_position: gpui::Point<Pixels>,
8104 newest_selection_head: Option<DisplayPoint>,
8105 target_display_point: DisplayPoint,
8106 window: &mut Window,
8107 cx: &mut App,
8108 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8109 let scrolled_content_origin =
8110 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8111
8112 const SCROLL_PADDING_Y: Pixels = px(12.);
8113
8114 if target_display_point.row() < visible_row_range.start {
8115 return self.render_edit_prediction_scroll_popover(
8116 |_| SCROLL_PADDING_Y,
8117 IconName::ArrowUp,
8118 visible_row_range,
8119 line_layouts,
8120 newest_selection_head,
8121 scrolled_content_origin,
8122 window,
8123 cx,
8124 );
8125 } else if target_display_point.row() >= visible_row_range.end {
8126 return self.render_edit_prediction_scroll_popover(
8127 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8128 IconName::ArrowDown,
8129 visible_row_range,
8130 line_layouts,
8131 newest_selection_head,
8132 scrolled_content_origin,
8133 window,
8134 cx,
8135 );
8136 }
8137
8138 const POLE_WIDTH: Pixels = px(2.);
8139
8140 let line_layout =
8141 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8142 let target_column = target_display_point.column() as usize;
8143
8144 let target_x = line_layout.x_for_index(target_column);
8145 let target_y =
8146 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8147
8148 let flag_on_right = target_x < text_bounds.size.width / 2.;
8149
8150 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8151 border_color.l += 0.001;
8152
8153 let mut element = v_flex()
8154 .items_end()
8155 .when(flag_on_right, |el| el.items_start())
8156 .child(if flag_on_right {
8157 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8158 .rounded_bl(px(0.))
8159 .rounded_tl(px(0.))
8160 .border_l_2()
8161 .border_color(border_color)
8162 } else {
8163 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8164 .rounded_br(px(0.))
8165 .rounded_tr(px(0.))
8166 .border_r_2()
8167 .border_color(border_color)
8168 })
8169 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8170 .into_any();
8171
8172 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8173
8174 let mut origin = scrolled_content_origin + point(target_x, target_y)
8175 - point(
8176 if flag_on_right {
8177 POLE_WIDTH
8178 } else {
8179 size.width - POLE_WIDTH
8180 },
8181 size.height - line_height,
8182 );
8183
8184 origin.x = origin.x.max(content_origin.x);
8185
8186 element.prepaint_at(origin, window, cx);
8187
8188 Some((element, origin))
8189 }
8190
8191 fn render_edit_prediction_scroll_popover(
8192 &mut self,
8193 to_y: impl Fn(Size<Pixels>) -> Pixels,
8194 scroll_icon: IconName,
8195 visible_row_range: Range<DisplayRow>,
8196 line_layouts: &[LineWithInvisibles],
8197 newest_selection_head: Option<DisplayPoint>,
8198 scrolled_content_origin: gpui::Point<Pixels>,
8199 window: &mut Window,
8200 cx: &mut App,
8201 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8202 let mut element = self
8203 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8204 .into_any();
8205
8206 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8207
8208 let cursor = newest_selection_head?;
8209 let cursor_row_layout =
8210 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8211 let cursor_column = cursor.column() as usize;
8212
8213 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8214
8215 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8216
8217 element.prepaint_at(origin, window, cx);
8218 Some((element, origin))
8219 }
8220
8221 fn render_edit_prediction_eager_jump_popover(
8222 &mut self,
8223 text_bounds: &Bounds<Pixels>,
8224 content_origin: gpui::Point<Pixels>,
8225 editor_snapshot: &EditorSnapshot,
8226 visible_row_range: Range<DisplayRow>,
8227 scroll_top: f32,
8228 scroll_bottom: f32,
8229 line_height: Pixels,
8230 scroll_pixel_position: gpui::Point<Pixels>,
8231 target_display_point: DisplayPoint,
8232 editor_width: Pixels,
8233 window: &mut Window,
8234 cx: &mut App,
8235 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8236 if target_display_point.row().as_f32() < scroll_top {
8237 let mut element = self
8238 .render_edit_prediction_line_popover(
8239 "Jump to Edit",
8240 Some(IconName::ArrowUp),
8241 window,
8242 cx,
8243 )?
8244 .into_any();
8245
8246 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8247 let offset = point(
8248 (text_bounds.size.width - size.width) / 2.,
8249 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8250 );
8251
8252 let origin = text_bounds.origin + offset;
8253 element.prepaint_at(origin, window, cx);
8254 Some((element, origin))
8255 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8256 let mut element = self
8257 .render_edit_prediction_line_popover(
8258 "Jump to Edit",
8259 Some(IconName::ArrowDown),
8260 window,
8261 cx,
8262 )?
8263 .into_any();
8264
8265 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8266 let offset = point(
8267 (text_bounds.size.width - size.width) / 2.,
8268 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8269 );
8270
8271 let origin = text_bounds.origin + offset;
8272 element.prepaint_at(origin, window, cx);
8273 Some((element, origin))
8274 } else {
8275 self.render_edit_prediction_end_of_line_popover(
8276 "Jump to Edit",
8277 editor_snapshot,
8278 visible_row_range,
8279 target_display_point,
8280 line_height,
8281 scroll_pixel_position,
8282 content_origin,
8283 editor_width,
8284 window,
8285 cx,
8286 )
8287 }
8288 }
8289
8290 fn render_edit_prediction_end_of_line_popover(
8291 self: &mut Editor,
8292 label: &'static str,
8293 editor_snapshot: &EditorSnapshot,
8294 visible_row_range: Range<DisplayRow>,
8295 target_display_point: DisplayPoint,
8296 line_height: Pixels,
8297 scroll_pixel_position: gpui::Point<Pixels>,
8298 content_origin: gpui::Point<Pixels>,
8299 editor_width: Pixels,
8300 window: &mut Window,
8301 cx: &mut App,
8302 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8303 let target_line_end = DisplayPoint::new(
8304 target_display_point.row(),
8305 editor_snapshot.line_len(target_display_point.row()),
8306 );
8307
8308 let mut element = self
8309 .render_edit_prediction_line_popover(label, None, window, cx)?
8310 .into_any();
8311
8312 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8313
8314 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8315
8316 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8317 let mut origin = start_point
8318 + line_origin
8319 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8320 origin.x = origin.x.max(content_origin.x);
8321
8322 let max_x = content_origin.x + editor_width - size.width;
8323
8324 if origin.x > max_x {
8325 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8326
8327 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8328 origin.y += offset;
8329 IconName::ArrowUp
8330 } else {
8331 origin.y -= offset;
8332 IconName::ArrowDown
8333 };
8334
8335 element = self
8336 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8337 .into_any();
8338
8339 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8340
8341 origin.x = content_origin.x + editor_width - size.width - px(2.);
8342 }
8343
8344 element.prepaint_at(origin, window, cx);
8345 Some((element, origin))
8346 }
8347
8348 fn render_edit_prediction_diff_popover(
8349 self: &Editor,
8350 text_bounds: &Bounds<Pixels>,
8351 content_origin: gpui::Point<Pixels>,
8352 right_margin: Pixels,
8353 editor_snapshot: &EditorSnapshot,
8354 visible_row_range: Range<DisplayRow>,
8355 line_layouts: &[LineWithInvisibles],
8356 line_height: Pixels,
8357 scroll_pixel_position: gpui::Point<Pixels>,
8358 newest_selection_head: Option<DisplayPoint>,
8359 editor_width: Pixels,
8360 style: &EditorStyle,
8361 edits: &Vec<(Range<Anchor>, String)>,
8362 edit_preview: &Option<language::EditPreview>,
8363 snapshot: &language::BufferSnapshot,
8364 window: &mut Window,
8365 cx: &mut App,
8366 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8367 let edit_start = edits
8368 .first()
8369 .unwrap()
8370 .0
8371 .start
8372 .to_display_point(editor_snapshot);
8373 let edit_end = edits
8374 .last()
8375 .unwrap()
8376 .0
8377 .end
8378 .to_display_point(editor_snapshot);
8379
8380 let is_visible = visible_row_range.contains(&edit_start.row())
8381 || visible_row_range.contains(&edit_end.row());
8382 if !is_visible {
8383 return None;
8384 }
8385
8386 let highlighted_edits =
8387 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8388
8389 let styled_text = highlighted_edits.to_styled_text(&style.text);
8390 let line_count = highlighted_edits.text.lines().count();
8391
8392 const BORDER_WIDTH: Pixels = px(1.);
8393
8394 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8395 let has_keybind = keybind.is_some();
8396
8397 let mut element = h_flex()
8398 .items_start()
8399 .child(
8400 h_flex()
8401 .bg(cx.theme().colors().editor_background)
8402 .border(BORDER_WIDTH)
8403 .shadow_sm()
8404 .border_color(cx.theme().colors().border)
8405 .rounded_l_lg()
8406 .when(line_count > 1, |el| el.rounded_br_lg())
8407 .pr_1()
8408 .child(styled_text),
8409 )
8410 .child(
8411 h_flex()
8412 .h(line_height + BORDER_WIDTH * 2.)
8413 .px_1p5()
8414 .gap_1()
8415 // Workaround: For some reason, there's a gap if we don't do this
8416 .ml(-BORDER_WIDTH)
8417 .shadow(vec![gpui::BoxShadow {
8418 color: gpui::black().opacity(0.05),
8419 offset: point(px(1.), px(1.)),
8420 blur_radius: px(2.),
8421 spread_radius: px(0.),
8422 }])
8423 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8424 .border(BORDER_WIDTH)
8425 .border_color(cx.theme().colors().border)
8426 .rounded_r_lg()
8427 .id("edit_prediction_diff_popover_keybind")
8428 .when(!has_keybind, |el| {
8429 let status_colors = cx.theme().status();
8430
8431 el.bg(status_colors.error_background)
8432 .border_color(status_colors.error.opacity(0.6))
8433 .child(Icon::new(IconName::Info).color(Color::Error))
8434 .cursor_default()
8435 .hoverable_tooltip(move |_window, cx| {
8436 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8437 })
8438 })
8439 .children(keybind),
8440 )
8441 .into_any();
8442
8443 let longest_row =
8444 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8445 let longest_line_width = if visible_row_range.contains(&longest_row) {
8446 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8447 } else {
8448 layout_line(
8449 longest_row,
8450 editor_snapshot,
8451 style,
8452 editor_width,
8453 |_| false,
8454 window,
8455 cx,
8456 )
8457 .width
8458 };
8459
8460 let viewport_bounds =
8461 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8462 right: -right_margin,
8463 ..Default::default()
8464 });
8465
8466 let x_after_longest =
8467 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8468 - scroll_pixel_position.x;
8469
8470 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8471
8472 // Fully visible if it can be displayed within the window (allow overlapping other
8473 // panes). However, this is only allowed if the popover starts within text_bounds.
8474 let can_position_to_the_right = x_after_longest < text_bounds.right()
8475 && x_after_longest + element_bounds.width < viewport_bounds.right();
8476
8477 let mut origin = if can_position_to_the_right {
8478 point(
8479 x_after_longest,
8480 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8481 - scroll_pixel_position.y,
8482 )
8483 } else {
8484 let cursor_row = newest_selection_head.map(|head| head.row());
8485 let above_edit = edit_start
8486 .row()
8487 .0
8488 .checked_sub(line_count as u32)
8489 .map(DisplayRow);
8490 let below_edit = Some(edit_end.row() + 1);
8491 let above_cursor =
8492 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8493 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8494
8495 // Place the edit popover adjacent to the edit if there is a location
8496 // available that is onscreen and does not obscure the cursor. Otherwise,
8497 // place it adjacent to the cursor.
8498 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8499 .into_iter()
8500 .flatten()
8501 .find(|&start_row| {
8502 let end_row = start_row + line_count as u32;
8503 visible_row_range.contains(&start_row)
8504 && visible_row_range.contains(&end_row)
8505 && cursor_row.map_or(true, |cursor_row| {
8506 !((start_row..end_row).contains(&cursor_row))
8507 })
8508 })?;
8509
8510 content_origin
8511 + point(
8512 -scroll_pixel_position.x,
8513 row_target.as_f32() * line_height - scroll_pixel_position.y,
8514 )
8515 };
8516
8517 origin.x -= BORDER_WIDTH;
8518
8519 window.defer_draw(element, origin, 1);
8520
8521 // Do not return an element, since it will already be drawn due to defer_draw.
8522 None
8523 }
8524
8525 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8526 px(30.)
8527 }
8528
8529 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8530 if self.read_only(cx) {
8531 cx.theme().players().read_only()
8532 } else {
8533 self.style.as_ref().unwrap().local_player
8534 }
8535 }
8536
8537 fn render_edit_prediction_accept_keybind(
8538 &self,
8539 window: &mut Window,
8540 cx: &App,
8541 ) -> Option<AnyElement> {
8542 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8543 let accept_keystroke = accept_binding.keystroke()?;
8544
8545 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8546
8547 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8548 Color::Accent
8549 } else {
8550 Color::Muted
8551 };
8552
8553 h_flex()
8554 .px_0p5()
8555 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8556 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8557 .text_size(TextSize::XSmall.rems(cx))
8558 .child(h_flex().children(ui::render_modifiers(
8559 &accept_keystroke.modifiers,
8560 PlatformStyle::platform(),
8561 Some(modifiers_color),
8562 Some(IconSize::XSmall.rems().into()),
8563 true,
8564 )))
8565 .when(is_platform_style_mac, |parent| {
8566 parent.child(accept_keystroke.key.clone())
8567 })
8568 .when(!is_platform_style_mac, |parent| {
8569 parent.child(
8570 Key::new(
8571 util::capitalize(&accept_keystroke.key),
8572 Some(Color::Default),
8573 )
8574 .size(Some(IconSize::XSmall.rems().into())),
8575 )
8576 })
8577 .into_any()
8578 .into()
8579 }
8580
8581 fn render_edit_prediction_line_popover(
8582 &self,
8583 label: impl Into<SharedString>,
8584 icon: Option<IconName>,
8585 window: &mut Window,
8586 cx: &App,
8587 ) -> Option<Stateful<Div>> {
8588 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8589
8590 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8591 let has_keybind = keybind.is_some();
8592
8593 let result = h_flex()
8594 .id("ep-line-popover")
8595 .py_0p5()
8596 .pl_1()
8597 .pr(padding_right)
8598 .gap_1()
8599 .rounded_md()
8600 .border_1()
8601 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8602 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8603 .shadow_sm()
8604 .when(!has_keybind, |el| {
8605 let status_colors = cx.theme().status();
8606
8607 el.bg(status_colors.error_background)
8608 .border_color(status_colors.error.opacity(0.6))
8609 .pl_2()
8610 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8611 .cursor_default()
8612 .hoverable_tooltip(move |_window, cx| {
8613 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8614 })
8615 })
8616 .children(keybind)
8617 .child(
8618 Label::new(label)
8619 .size(LabelSize::Small)
8620 .when(!has_keybind, |el| {
8621 el.color(cx.theme().status().error.into()).strikethrough()
8622 }),
8623 )
8624 .when(!has_keybind, |el| {
8625 el.child(
8626 h_flex().ml_1().child(
8627 Icon::new(IconName::Info)
8628 .size(IconSize::Small)
8629 .color(cx.theme().status().error.into()),
8630 ),
8631 )
8632 })
8633 .when_some(icon, |element, icon| {
8634 element.child(
8635 div()
8636 .mt(px(1.5))
8637 .child(Icon::new(icon).size(IconSize::Small)),
8638 )
8639 });
8640
8641 Some(result)
8642 }
8643
8644 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8645 let accent_color = cx.theme().colors().text_accent;
8646 let editor_bg_color = cx.theme().colors().editor_background;
8647 editor_bg_color.blend(accent_color.opacity(0.1))
8648 }
8649
8650 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8651 let accent_color = cx.theme().colors().text_accent;
8652 let editor_bg_color = cx.theme().colors().editor_background;
8653 editor_bg_color.blend(accent_color.opacity(0.6))
8654 }
8655
8656 fn render_edit_prediction_cursor_popover(
8657 &self,
8658 min_width: Pixels,
8659 max_width: Pixels,
8660 cursor_point: Point,
8661 style: &EditorStyle,
8662 accept_keystroke: Option<&gpui::Keystroke>,
8663 _window: &Window,
8664 cx: &mut Context<Editor>,
8665 ) -> Option<AnyElement> {
8666 let provider = self.edit_prediction_provider.as_ref()?;
8667
8668 if provider.provider.needs_terms_acceptance(cx) {
8669 return Some(
8670 h_flex()
8671 .min_w(min_width)
8672 .flex_1()
8673 .px_2()
8674 .py_1()
8675 .gap_3()
8676 .elevation_2(cx)
8677 .hover(|style| style.bg(cx.theme().colors().element_hover))
8678 .id("accept-terms")
8679 .cursor_pointer()
8680 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8681 .on_click(cx.listener(|this, _event, window, cx| {
8682 cx.stop_propagation();
8683 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8684 window.dispatch_action(
8685 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8686 cx,
8687 );
8688 }))
8689 .child(
8690 h_flex()
8691 .flex_1()
8692 .gap_2()
8693 .child(Icon::new(IconName::ZedPredict))
8694 .child(Label::new("Accept Terms of Service"))
8695 .child(div().w_full())
8696 .child(
8697 Icon::new(IconName::ArrowUpRight)
8698 .color(Color::Muted)
8699 .size(IconSize::Small),
8700 )
8701 .into_any_element(),
8702 )
8703 .into_any(),
8704 );
8705 }
8706
8707 let is_refreshing = provider.provider.is_refreshing(cx);
8708
8709 fn pending_completion_container() -> Div {
8710 h_flex()
8711 .h_full()
8712 .flex_1()
8713 .gap_2()
8714 .child(Icon::new(IconName::ZedPredict))
8715 }
8716
8717 let completion = match &self.active_inline_completion {
8718 Some(prediction) => {
8719 if !self.has_visible_completions_menu() {
8720 const RADIUS: Pixels = px(6.);
8721 const BORDER_WIDTH: Pixels = px(1.);
8722
8723 return Some(
8724 h_flex()
8725 .elevation_2(cx)
8726 .border(BORDER_WIDTH)
8727 .border_color(cx.theme().colors().border)
8728 .when(accept_keystroke.is_none(), |el| {
8729 el.border_color(cx.theme().status().error)
8730 })
8731 .rounded(RADIUS)
8732 .rounded_tl(px(0.))
8733 .overflow_hidden()
8734 .child(div().px_1p5().child(match &prediction.completion {
8735 InlineCompletion::Move { target, snapshot } => {
8736 use text::ToPoint as _;
8737 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8738 {
8739 Icon::new(IconName::ZedPredictDown)
8740 } else {
8741 Icon::new(IconName::ZedPredictUp)
8742 }
8743 }
8744 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8745 }))
8746 .child(
8747 h_flex()
8748 .gap_1()
8749 .py_1()
8750 .px_2()
8751 .rounded_r(RADIUS - BORDER_WIDTH)
8752 .border_l_1()
8753 .border_color(cx.theme().colors().border)
8754 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8755 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8756 el.child(
8757 Label::new("Hold")
8758 .size(LabelSize::Small)
8759 .when(accept_keystroke.is_none(), |el| {
8760 el.strikethrough()
8761 })
8762 .line_height_style(LineHeightStyle::UiLabel),
8763 )
8764 })
8765 .id("edit_prediction_cursor_popover_keybind")
8766 .when(accept_keystroke.is_none(), |el| {
8767 let status_colors = cx.theme().status();
8768
8769 el.bg(status_colors.error_background)
8770 .border_color(status_colors.error.opacity(0.6))
8771 .child(Icon::new(IconName::Info).color(Color::Error))
8772 .cursor_default()
8773 .hoverable_tooltip(move |_window, cx| {
8774 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8775 .into()
8776 })
8777 })
8778 .when_some(
8779 accept_keystroke.as_ref(),
8780 |el, accept_keystroke| {
8781 el.child(h_flex().children(ui::render_modifiers(
8782 &accept_keystroke.modifiers,
8783 PlatformStyle::platform(),
8784 Some(Color::Default),
8785 Some(IconSize::XSmall.rems().into()),
8786 false,
8787 )))
8788 },
8789 ),
8790 )
8791 .into_any(),
8792 );
8793 }
8794
8795 self.render_edit_prediction_cursor_popover_preview(
8796 prediction,
8797 cursor_point,
8798 style,
8799 cx,
8800 )?
8801 }
8802
8803 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8804 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8805 stale_completion,
8806 cursor_point,
8807 style,
8808 cx,
8809 )?,
8810
8811 None => {
8812 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8813 }
8814 },
8815
8816 None => pending_completion_container().child(Label::new("No Prediction")),
8817 };
8818
8819 let completion = if is_refreshing {
8820 completion
8821 .with_animation(
8822 "loading-completion",
8823 Animation::new(Duration::from_secs(2))
8824 .repeat()
8825 .with_easing(pulsating_between(0.4, 0.8)),
8826 |label, delta| label.opacity(delta),
8827 )
8828 .into_any_element()
8829 } else {
8830 completion.into_any_element()
8831 };
8832
8833 let has_completion = self.active_inline_completion.is_some();
8834
8835 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8836 Some(
8837 h_flex()
8838 .min_w(min_width)
8839 .max_w(max_width)
8840 .flex_1()
8841 .elevation_2(cx)
8842 .border_color(cx.theme().colors().border)
8843 .child(
8844 div()
8845 .flex_1()
8846 .py_1()
8847 .px_2()
8848 .overflow_hidden()
8849 .child(completion),
8850 )
8851 .when_some(accept_keystroke, |el, accept_keystroke| {
8852 if !accept_keystroke.modifiers.modified() {
8853 return el;
8854 }
8855
8856 el.child(
8857 h_flex()
8858 .h_full()
8859 .border_l_1()
8860 .rounded_r_lg()
8861 .border_color(cx.theme().colors().border)
8862 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8863 .gap_1()
8864 .py_1()
8865 .px_2()
8866 .child(
8867 h_flex()
8868 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8869 .when(is_platform_style_mac, |parent| parent.gap_1())
8870 .child(h_flex().children(ui::render_modifiers(
8871 &accept_keystroke.modifiers,
8872 PlatformStyle::platform(),
8873 Some(if !has_completion {
8874 Color::Muted
8875 } else {
8876 Color::Default
8877 }),
8878 None,
8879 false,
8880 ))),
8881 )
8882 .child(Label::new("Preview").into_any_element())
8883 .opacity(if has_completion { 1.0 } else { 0.4 }),
8884 )
8885 })
8886 .into_any(),
8887 )
8888 }
8889
8890 fn render_edit_prediction_cursor_popover_preview(
8891 &self,
8892 completion: &InlineCompletionState,
8893 cursor_point: Point,
8894 style: &EditorStyle,
8895 cx: &mut Context<Editor>,
8896 ) -> Option<Div> {
8897 use text::ToPoint as _;
8898
8899 fn render_relative_row_jump(
8900 prefix: impl Into<String>,
8901 current_row: u32,
8902 target_row: u32,
8903 ) -> Div {
8904 let (row_diff, arrow) = if target_row < current_row {
8905 (current_row - target_row, IconName::ArrowUp)
8906 } else {
8907 (target_row - current_row, IconName::ArrowDown)
8908 };
8909
8910 h_flex()
8911 .child(
8912 Label::new(format!("{}{}", prefix.into(), row_diff))
8913 .color(Color::Muted)
8914 .size(LabelSize::Small),
8915 )
8916 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8917 }
8918
8919 match &completion.completion {
8920 InlineCompletion::Move {
8921 target, snapshot, ..
8922 } => Some(
8923 h_flex()
8924 .px_2()
8925 .gap_2()
8926 .flex_1()
8927 .child(
8928 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8929 Icon::new(IconName::ZedPredictDown)
8930 } else {
8931 Icon::new(IconName::ZedPredictUp)
8932 },
8933 )
8934 .child(Label::new("Jump to Edit")),
8935 ),
8936
8937 InlineCompletion::Edit {
8938 edits,
8939 edit_preview,
8940 snapshot,
8941 display_mode: _,
8942 } => {
8943 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8944
8945 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8946 &snapshot,
8947 &edits,
8948 edit_preview.as_ref()?,
8949 true,
8950 cx,
8951 )
8952 .first_line_preview();
8953
8954 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8955 .with_default_highlights(&style.text, highlighted_edits.highlights);
8956
8957 let preview = h_flex()
8958 .gap_1()
8959 .min_w_16()
8960 .child(styled_text)
8961 .when(has_more_lines, |parent| parent.child("…"));
8962
8963 let left = if first_edit_row != cursor_point.row {
8964 render_relative_row_jump("", cursor_point.row, first_edit_row)
8965 .into_any_element()
8966 } else {
8967 Icon::new(IconName::ZedPredict).into_any_element()
8968 };
8969
8970 Some(
8971 h_flex()
8972 .h_full()
8973 .flex_1()
8974 .gap_2()
8975 .pr_1()
8976 .overflow_x_hidden()
8977 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8978 .child(left)
8979 .child(preview),
8980 )
8981 }
8982 }
8983 }
8984
8985 pub fn render_context_menu(
8986 &self,
8987 style: &EditorStyle,
8988 max_height_in_lines: u32,
8989 window: &mut Window,
8990 cx: &mut Context<Editor>,
8991 ) -> Option<AnyElement> {
8992 let menu = self.context_menu.borrow();
8993 let menu = menu.as_ref()?;
8994 if !menu.visible() {
8995 return None;
8996 };
8997 Some(menu.render(style, max_height_in_lines, window, cx))
8998 }
8999
9000 fn render_context_menu_aside(
9001 &mut self,
9002 max_size: Size<Pixels>,
9003 window: &mut Window,
9004 cx: &mut Context<Editor>,
9005 ) -> Option<AnyElement> {
9006 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9007 if menu.visible() {
9008 menu.render_aside(max_size, window, cx)
9009 } else {
9010 None
9011 }
9012 })
9013 }
9014
9015 fn hide_context_menu(
9016 &mut self,
9017 window: &mut Window,
9018 cx: &mut Context<Self>,
9019 ) -> Option<CodeContextMenu> {
9020 cx.notify();
9021 self.completion_tasks.clear();
9022 let context_menu = self.context_menu.borrow_mut().take();
9023 self.stale_inline_completion_in_menu.take();
9024 self.update_visible_inline_completion(window, cx);
9025 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9026 if let Some(completion_provider) = &self.completion_provider {
9027 completion_provider.selection_changed(None, window, cx);
9028 }
9029 }
9030 context_menu
9031 }
9032
9033 fn show_snippet_choices(
9034 &mut self,
9035 choices: &Vec<String>,
9036 selection: Range<Anchor>,
9037 cx: &mut Context<Self>,
9038 ) {
9039 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9040 (Some(a), Some(b)) if a == b => a,
9041 _ => {
9042 log::error!("expected anchor range to have matching buffer IDs");
9043 return;
9044 }
9045 };
9046 let multi_buffer = self.buffer().read(cx);
9047 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9048 return;
9049 };
9050
9051 let id = post_inc(&mut self.next_completion_id);
9052 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9053 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9054 CompletionsMenu::new_snippet_choices(
9055 id,
9056 true,
9057 choices,
9058 selection,
9059 buffer,
9060 snippet_sort_order,
9061 ),
9062 ));
9063 }
9064
9065 pub fn insert_snippet(
9066 &mut self,
9067 insertion_ranges: &[Range<usize>],
9068 snippet: Snippet,
9069 window: &mut Window,
9070 cx: &mut Context<Self>,
9071 ) -> Result<()> {
9072 struct Tabstop<T> {
9073 is_end_tabstop: bool,
9074 ranges: Vec<Range<T>>,
9075 choices: Option<Vec<String>>,
9076 }
9077
9078 let tabstops = self.buffer.update(cx, |buffer, cx| {
9079 let snippet_text: Arc<str> = snippet.text.clone().into();
9080 let edits = insertion_ranges
9081 .iter()
9082 .cloned()
9083 .map(|range| (range, snippet_text.clone()));
9084 let autoindent_mode = AutoindentMode::Block {
9085 original_indent_columns: Vec::new(),
9086 };
9087 buffer.edit(edits, Some(autoindent_mode), cx);
9088
9089 let snapshot = &*buffer.read(cx);
9090 let snippet = &snippet;
9091 snippet
9092 .tabstops
9093 .iter()
9094 .map(|tabstop| {
9095 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9096 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9097 });
9098 let mut tabstop_ranges = tabstop
9099 .ranges
9100 .iter()
9101 .flat_map(|tabstop_range| {
9102 let mut delta = 0_isize;
9103 insertion_ranges.iter().map(move |insertion_range| {
9104 let insertion_start = insertion_range.start as isize + delta;
9105 delta +=
9106 snippet.text.len() as isize - insertion_range.len() as isize;
9107
9108 let start = ((insertion_start + tabstop_range.start) as usize)
9109 .min(snapshot.len());
9110 let end = ((insertion_start + tabstop_range.end) as usize)
9111 .min(snapshot.len());
9112 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9113 })
9114 })
9115 .collect::<Vec<_>>();
9116 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9117
9118 Tabstop {
9119 is_end_tabstop,
9120 ranges: tabstop_ranges,
9121 choices: tabstop.choices.clone(),
9122 }
9123 })
9124 .collect::<Vec<_>>()
9125 });
9126 if let Some(tabstop) = tabstops.first() {
9127 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9128 // Reverse order so that the first range is the newest created selection.
9129 // Completions will use it and autoscroll will prioritize it.
9130 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9131 });
9132
9133 if let Some(choices) = &tabstop.choices {
9134 if let Some(selection) = tabstop.ranges.first() {
9135 self.show_snippet_choices(choices, selection.clone(), cx)
9136 }
9137 }
9138
9139 // If we're already at the last tabstop and it's at the end of the snippet,
9140 // we're done, we don't need to keep the state around.
9141 if !tabstop.is_end_tabstop {
9142 let choices = tabstops
9143 .iter()
9144 .map(|tabstop| tabstop.choices.clone())
9145 .collect();
9146
9147 let ranges = tabstops
9148 .into_iter()
9149 .map(|tabstop| tabstop.ranges)
9150 .collect::<Vec<_>>();
9151
9152 self.snippet_stack.push(SnippetState {
9153 active_index: 0,
9154 ranges,
9155 choices,
9156 });
9157 }
9158
9159 // Check whether the just-entered snippet ends with an auto-closable bracket.
9160 if self.autoclose_regions.is_empty() {
9161 let snapshot = self.buffer.read(cx).snapshot(cx);
9162 for selection in &mut self.selections.all::<Point>(cx) {
9163 let selection_head = selection.head();
9164 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9165 continue;
9166 };
9167
9168 let mut bracket_pair = None;
9169 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9170 let prev_chars = snapshot
9171 .reversed_chars_at(selection_head)
9172 .collect::<String>();
9173 for (pair, enabled) in scope.brackets() {
9174 if enabled
9175 && pair.close
9176 && prev_chars.starts_with(pair.start.as_str())
9177 && next_chars.starts_with(pair.end.as_str())
9178 {
9179 bracket_pair = Some(pair.clone());
9180 break;
9181 }
9182 }
9183 if let Some(pair) = bracket_pair {
9184 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9185 let autoclose_enabled =
9186 self.use_autoclose && snapshot_settings.use_autoclose;
9187 if autoclose_enabled {
9188 let start = snapshot.anchor_after(selection_head);
9189 let end = snapshot.anchor_after(selection_head);
9190 self.autoclose_regions.push(AutocloseRegion {
9191 selection_id: selection.id,
9192 range: start..end,
9193 pair,
9194 });
9195 }
9196 }
9197 }
9198 }
9199 }
9200 Ok(())
9201 }
9202
9203 pub fn move_to_next_snippet_tabstop(
9204 &mut self,
9205 window: &mut Window,
9206 cx: &mut Context<Self>,
9207 ) -> bool {
9208 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9209 }
9210
9211 pub fn move_to_prev_snippet_tabstop(
9212 &mut self,
9213 window: &mut Window,
9214 cx: &mut Context<Self>,
9215 ) -> bool {
9216 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9217 }
9218
9219 pub fn move_to_snippet_tabstop(
9220 &mut self,
9221 bias: Bias,
9222 window: &mut Window,
9223 cx: &mut Context<Self>,
9224 ) -> bool {
9225 if let Some(mut snippet) = self.snippet_stack.pop() {
9226 match bias {
9227 Bias::Left => {
9228 if snippet.active_index > 0 {
9229 snippet.active_index -= 1;
9230 } else {
9231 self.snippet_stack.push(snippet);
9232 return false;
9233 }
9234 }
9235 Bias::Right => {
9236 if snippet.active_index + 1 < snippet.ranges.len() {
9237 snippet.active_index += 1;
9238 } else {
9239 self.snippet_stack.push(snippet);
9240 return false;
9241 }
9242 }
9243 }
9244 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9245 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9246 // Reverse order so that the first range is the newest created selection.
9247 // Completions will use it and autoscroll will prioritize it.
9248 s.select_ranges(current_ranges.iter().rev().cloned())
9249 });
9250
9251 if let Some(choices) = &snippet.choices[snippet.active_index] {
9252 if let Some(selection) = current_ranges.first() {
9253 self.show_snippet_choices(&choices, selection.clone(), cx);
9254 }
9255 }
9256
9257 // If snippet state is not at the last tabstop, push it back on the stack
9258 if snippet.active_index + 1 < snippet.ranges.len() {
9259 self.snippet_stack.push(snippet);
9260 }
9261 return true;
9262 }
9263 }
9264
9265 false
9266 }
9267
9268 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9269 self.transact(window, cx, |this, window, cx| {
9270 this.select_all(&SelectAll, window, cx);
9271 this.insert("", window, cx);
9272 });
9273 }
9274
9275 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9276 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9277 self.transact(window, cx, |this, window, cx| {
9278 this.select_autoclose_pair(window, cx);
9279 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9280 if !this.linked_edit_ranges.is_empty() {
9281 let selections = this.selections.all::<MultiBufferPoint>(cx);
9282 let snapshot = this.buffer.read(cx).snapshot(cx);
9283
9284 for selection in selections.iter() {
9285 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9286 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9287 if selection_start.buffer_id != selection_end.buffer_id {
9288 continue;
9289 }
9290 if let Some(ranges) =
9291 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9292 {
9293 for (buffer, entries) in ranges {
9294 linked_ranges.entry(buffer).or_default().extend(entries);
9295 }
9296 }
9297 }
9298 }
9299
9300 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9301 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9302 for selection in &mut selections {
9303 if selection.is_empty() {
9304 let old_head = selection.head();
9305 let mut new_head =
9306 movement::left(&display_map, old_head.to_display_point(&display_map))
9307 .to_point(&display_map);
9308 if let Some((buffer, line_buffer_range)) = display_map
9309 .buffer_snapshot
9310 .buffer_line_for_row(MultiBufferRow(old_head.row))
9311 {
9312 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9313 let indent_len = match indent_size.kind {
9314 IndentKind::Space => {
9315 buffer.settings_at(line_buffer_range.start, cx).tab_size
9316 }
9317 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9318 };
9319 if old_head.column <= indent_size.len && old_head.column > 0 {
9320 let indent_len = indent_len.get();
9321 new_head = cmp::min(
9322 new_head,
9323 MultiBufferPoint::new(
9324 old_head.row,
9325 ((old_head.column - 1) / indent_len) * indent_len,
9326 ),
9327 );
9328 }
9329 }
9330
9331 selection.set_head(new_head, SelectionGoal::None);
9332 }
9333 }
9334
9335 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9336 s.select(selections)
9337 });
9338 this.insert("", window, cx);
9339 let empty_str: Arc<str> = Arc::from("");
9340 for (buffer, edits) in linked_ranges {
9341 let snapshot = buffer.read(cx).snapshot();
9342 use text::ToPoint as TP;
9343
9344 let edits = edits
9345 .into_iter()
9346 .map(|range| {
9347 let end_point = TP::to_point(&range.end, &snapshot);
9348 let mut start_point = TP::to_point(&range.start, &snapshot);
9349
9350 if end_point == start_point {
9351 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9352 .saturating_sub(1);
9353 start_point =
9354 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9355 };
9356
9357 (start_point..end_point, empty_str.clone())
9358 })
9359 .sorted_by_key(|(range, _)| range.start)
9360 .collect::<Vec<_>>();
9361 buffer.update(cx, |this, cx| {
9362 this.edit(edits, None, cx);
9363 })
9364 }
9365 this.refresh_inline_completion(true, false, window, cx);
9366 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9367 });
9368 }
9369
9370 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9371 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9372 self.transact(window, cx, |this, window, cx| {
9373 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9374 s.move_with(|map, selection| {
9375 if selection.is_empty() {
9376 let cursor = movement::right(map, selection.head());
9377 selection.end = cursor;
9378 selection.reversed = true;
9379 selection.goal = SelectionGoal::None;
9380 }
9381 })
9382 });
9383 this.insert("", window, cx);
9384 this.refresh_inline_completion(true, false, window, cx);
9385 });
9386 }
9387
9388 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9389 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9390 if self.move_to_prev_snippet_tabstop(window, cx) {
9391 return;
9392 }
9393 self.outdent(&Outdent, window, cx);
9394 }
9395
9396 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9397 if self.move_to_next_snippet_tabstop(window, cx) {
9398 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9399 return;
9400 }
9401 if self.read_only(cx) {
9402 return;
9403 }
9404 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9405 let mut selections = self.selections.all_adjusted(cx);
9406 let buffer = self.buffer.read(cx);
9407 let snapshot = buffer.snapshot(cx);
9408 let rows_iter = selections.iter().map(|s| s.head().row);
9409 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9410
9411 let has_some_cursor_in_whitespace = selections
9412 .iter()
9413 .filter(|selection| selection.is_empty())
9414 .any(|selection| {
9415 let cursor = selection.head();
9416 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9417 cursor.column < current_indent.len
9418 });
9419
9420 let mut edits = Vec::new();
9421 let mut prev_edited_row = 0;
9422 let mut row_delta = 0;
9423 for selection in &mut selections {
9424 if selection.start.row != prev_edited_row {
9425 row_delta = 0;
9426 }
9427 prev_edited_row = selection.end.row;
9428
9429 // If the selection is non-empty, then increase the indentation of the selected lines.
9430 if !selection.is_empty() {
9431 row_delta =
9432 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9433 continue;
9434 }
9435
9436 let cursor = selection.head();
9437 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9438 if let Some(suggested_indent) =
9439 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9440 {
9441 // Don't do anything if already at suggested indent
9442 // and there is any other cursor which is not
9443 if has_some_cursor_in_whitespace
9444 && cursor.column == current_indent.len
9445 && current_indent.len == suggested_indent.len
9446 {
9447 continue;
9448 }
9449
9450 // Adjust line and move cursor to suggested indent
9451 // if cursor is not at suggested indent
9452 if cursor.column < suggested_indent.len
9453 && cursor.column <= current_indent.len
9454 && current_indent.len <= suggested_indent.len
9455 {
9456 selection.start = Point::new(cursor.row, suggested_indent.len);
9457 selection.end = selection.start;
9458 if row_delta == 0 {
9459 edits.extend(Buffer::edit_for_indent_size_adjustment(
9460 cursor.row,
9461 current_indent,
9462 suggested_indent,
9463 ));
9464 row_delta = suggested_indent.len - current_indent.len;
9465 }
9466 continue;
9467 }
9468
9469 // If current indent is more than suggested indent
9470 // only move cursor to current indent and skip indent
9471 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9472 selection.start = Point::new(cursor.row, current_indent.len);
9473 selection.end = selection.start;
9474 continue;
9475 }
9476 }
9477
9478 // Otherwise, insert a hard or soft tab.
9479 let settings = buffer.language_settings_at(cursor, cx);
9480 let tab_size = if settings.hard_tabs {
9481 IndentSize::tab()
9482 } else {
9483 let tab_size = settings.tab_size.get();
9484 let indent_remainder = snapshot
9485 .text_for_range(Point::new(cursor.row, 0)..cursor)
9486 .flat_map(str::chars)
9487 .fold(row_delta % tab_size, |counter: u32, c| {
9488 if c == '\t' {
9489 0
9490 } else {
9491 (counter + 1) % tab_size
9492 }
9493 });
9494
9495 let chars_to_next_tab_stop = tab_size - indent_remainder;
9496 IndentSize::spaces(chars_to_next_tab_stop)
9497 };
9498 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9499 selection.end = selection.start;
9500 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9501 row_delta += tab_size.len;
9502 }
9503
9504 self.transact(window, cx, |this, window, cx| {
9505 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9506 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9507 s.select(selections)
9508 });
9509 this.refresh_inline_completion(true, false, window, cx);
9510 });
9511 }
9512
9513 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9514 if self.read_only(cx) {
9515 return;
9516 }
9517 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9518 let mut selections = self.selections.all::<Point>(cx);
9519 let mut prev_edited_row = 0;
9520 let mut row_delta = 0;
9521 let mut edits = Vec::new();
9522 let buffer = self.buffer.read(cx);
9523 let snapshot = buffer.snapshot(cx);
9524 for selection in &mut selections {
9525 if selection.start.row != prev_edited_row {
9526 row_delta = 0;
9527 }
9528 prev_edited_row = selection.end.row;
9529
9530 row_delta =
9531 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9532 }
9533
9534 self.transact(window, cx, |this, window, cx| {
9535 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9536 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9537 s.select(selections)
9538 });
9539 });
9540 }
9541
9542 fn indent_selection(
9543 buffer: &MultiBuffer,
9544 snapshot: &MultiBufferSnapshot,
9545 selection: &mut Selection<Point>,
9546 edits: &mut Vec<(Range<Point>, String)>,
9547 delta_for_start_row: u32,
9548 cx: &App,
9549 ) -> u32 {
9550 let settings = buffer.language_settings_at(selection.start, cx);
9551 let tab_size = settings.tab_size.get();
9552 let indent_kind = if settings.hard_tabs {
9553 IndentKind::Tab
9554 } else {
9555 IndentKind::Space
9556 };
9557 let mut start_row = selection.start.row;
9558 let mut end_row = selection.end.row + 1;
9559
9560 // If a selection ends at the beginning of a line, don't indent
9561 // that last line.
9562 if selection.end.column == 0 && selection.end.row > selection.start.row {
9563 end_row -= 1;
9564 }
9565
9566 // Avoid re-indenting a row that has already been indented by a
9567 // previous selection, but still update this selection's column
9568 // to reflect that indentation.
9569 if delta_for_start_row > 0 {
9570 start_row += 1;
9571 selection.start.column += delta_for_start_row;
9572 if selection.end.row == selection.start.row {
9573 selection.end.column += delta_for_start_row;
9574 }
9575 }
9576
9577 let mut delta_for_end_row = 0;
9578 let has_multiple_rows = start_row + 1 != end_row;
9579 for row in start_row..end_row {
9580 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9581 let indent_delta = match (current_indent.kind, indent_kind) {
9582 (IndentKind::Space, IndentKind::Space) => {
9583 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9584 IndentSize::spaces(columns_to_next_tab_stop)
9585 }
9586 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9587 (_, IndentKind::Tab) => IndentSize::tab(),
9588 };
9589
9590 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9591 0
9592 } else {
9593 selection.start.column
9594 };
9595 let row_start = Point::new(row, start);
9596 edits.push((
9597 row_start..row_start,
9598 indent_delta.chars().collect::<String>(),
9599 ));
9600
9601 // Update this selection's endpoints to reflect the indentation.
9602 if row == selection.start.row {
9603 selection.start.column += indent_delta.len;
9604 }
9605 if row == selection.end.row {
9606 selection.end.column += indent_delta.len;
9607 delta_for_end_row = indent_delta.len;
9608 }
9609 }
9610
9611 if selection.start.row == selection.end.row {
9612 delta_for_start_row + delta_for_end_row
9613 } else {
9614 delta_for_end_row
9615 }
9616 }
9617
9618 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9619 if self.read_only(cx) {
9620 return;
9621 }
9622 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9624 let selections = self.selections.all::<Point>(cx);
9625 let mut deletion_ranges = Vec::new();
9626 let mut last_outdent = None;
9627 {
9628 let buffer = self.buffer.read(cx);
9629 let snapshot = buffer.snapshot(cx);
9630 for selection in &selections {
9631 let settings = buffer.language_settings_at(selection.start, cx);
9632 let tab_size = settings.tab_size.get();
9633 let mut rows = selection.spanned_rows(false, &display_map);
9634
9635 // Avoid re-outdenting a row that has already been outdented by a
9636 // previous selection.
9637 if let Some(last_row) = last_outdent {
9638 if last_row == rows.start {
9639 rows.start = rows.start.next_row();
9640 }
9641 }
9642 let has_multiple_rows = rows.len() > 1;
9643 for row in rows.iter_rows() {
9644 let indent_size = snapshot.indent_size_for_line(row);
9645 if indent_size.len > 0 {
9646 let deletion_len = match indent_size.kind {
9647 IndentKind::Space => {
9648 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9649 if columns_to_prev_tab_stop == 0 {
9650 tab_size
9651 } else {
9652 columns_to_prev_tab_stop
9653 }
9654 }
9655 IndentKind::Tab => 1,
9656 };
9657 let start = if has_multiple_rows
9658 || deletion_len > selection.start.column
9659 || indent_size.len < selection.start.column
9660 {
9661 0
9662 } else {
9663 selection.start.column - deletion_len
9664 };
9665 deletion_ranges.push(
9666 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9667 );
9668 last_outdent = Some(row);
9669 }
9670 }
9671 }
9672 }
9673
9674 self.transact(window, cx, |this, window, cx| {
9675 this.buffer.update(cx, |buffer, cx| {
9676 let empty_str: Arc<str> = Arc::default();
9677 buffer.edit(
9678 deletion_ranges
9679 .into_iter()
9680 .map(|range| (range, empty_str.clone())),
9681 None,
9682 cx,
9683 );
9684 });
9685 let selections = this.selections.all::<usize>(cx);
9686 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9687 s.select(selections)
9688 });
9689 });
9690 }
9691
9692 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9693 if self.read_only(cx) {
9694 return;
9695 }
9696 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9697 let selections = self
9698 .selections
9699 .all::<usize>(cx)
9700 .into_iter()
9701 .map(|s| s.range());
9702
9703 self.transact(window, cx, |this, window, cx| {
9704 this.buffer.update(cx, |buffer, cx| {
9705 buffer.autoindent_ranges(selections, cx);
9706 });
9707 let selections = this.selections.all::<usize>(cx);
9708 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9709 s.select(selections)
9710 });
9711 });
9712 }
9713
9714 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9715 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9716 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9717 let selections = self.selections.all::<Point>(cx);
9718
9719 let mut new_cursors = Vec::new();
9720 let mut edit_ranges = Vec::new();
9721 let mut selections = selections.iter().peekable();
9722 while let Some(selection) = selections.next() {
9723 let mut rows = selection.spanned_rows(false, &display_map);
9724 let goal_display_column = selection.head().to_display_point(&display_map).column();
9725
9726 // Accumulate contiguous regions of rows that we want to delete.
9727 while let Some(next_selection) = selections.peek() {
9728 let next_rows = next_selection.spanned_rows(false, &display_map);
9729 if next_rows.start <= rows.end {
9730 rows.end = next_rows.end;
9731 selections.next().unwrap();
9732 } else {
9733 break;
9734 }
9735 }
9736
9737 let buffer = &display_map.buffer_snapshot;
9738 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9739 let edit_end;
9740 let cursor_buffer_row;
9741 if buffer.max_point().row >= rows.end.0 {
9742 // If there's a line after the range, delete the \n from the end of the row range
9743 // and position the cursor on the next line.
9744 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9745 cursor_buffer_row = rows.end;
9746 } else {
9747 // If there isn't a line after the range, delete the \n from the line before the
9748 // start of the row range and position the cursor there.
9749 edit_start = edit_start.saturating_sub(1);
9750 edit_end = buffer.len();
9751 cursor_buffer_row = rows.start.previous_row();
9752 }
9753
9754 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9755 *cursor.column_mut() =
9756 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9757
9758 new_cursors.push((
9759 selection.id,
9760 buffer.anchor_after(cursor.to_point(&display_map)),
9761 ));
9762 edit_ranges.push(edit_start..edit_end);
9763 }
9764
9765 self.transact(window, cx, |this, window, cx| {
9766 let buffer = this.buffer.update(cx, |buffer, cx| {
9767 let empty_str: Arc<str> = Arc::default();
9768 buffer.edit(
9769 edit_ranges
9770 .into_iter()
9771 .map(|range| (range, empty_str.clone())),
9772 None,
9773 cx,
9774 );
9775 buffer.snapshot(cx)
9776 });
9777 let new_selections = new_cursors
9778 .into_iter()
9779 .map(|(id, cursor)| {
9780 let cursor = cursor.to_point(&buffer);
9781 Selection {
9782 id,
9783 start: cursor,
9784 end: cursor,
9785 reversed: false,
9786 goal: SelectionGoal::None,
9787 }
9788 })
9789 .collect();
9790
9791 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9792 s.select(new_selections);
9793 });
9794 });
9795 }
9796
9797 pub fn join_lines_impl(
9798 &mut self,
9799 insert_whitespace: bool,
9800 window: &mut Window,
9801 cx: &mut Context<Self>,
9802 ) {
9803 if self.read_only(cx) {
9804 return;
9805 }
9806 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9807 for selection in self.selections.all::<Point>(cx) {
9808 let start = MultiBufferRow(selection.start.row);
9809 // Treat single line selections as if they include the next line. Otherwise this action
9810 // would do nothing for single line selections individual cursors.
9811 let end = if selection.start.row == selection.end.row {
9812 MultiBufferRow(selection.start.row + 1)
9813 } else {
9814 MultiBufferRow(selection.end.row)
9815 };
9816
9817 if let Some(last_row_range) = row_ranges.last_mut() {
9818 if start <= last_row_range.end {
9819 last_row_range.end = end;
9820 continue;
9821 }
9822 }
9823 row_ranges.push(start..end);
9824 }
9825
9826 let snapshot = self.buffer.read(cx).snapshot(cx);
9827 let mut cursor_positions = Vec::new();
9828 for row_range in &row_ranges {
9829 let anchor = snapshot.anchor_before(Point::new(
9830 row_range.end.previous_row().0,
9831 snapshot.line_len(row_range.end.previous_row()),
9832 ));
9833 cursor_positions.push(anchor..anchor);
9834 }
9835
9836 self.transact(window, cx, |this, window, cx| {
9837 for row_range in row_ranges.into_iter().rev() {
9838 for row in row_range.iter_rows().rev() {
9839 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9840 let next_line_row = row.next_row();
9841 let indent = snapshot.indent_size_for_line(next_line_row);
9842 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9843
9844 let replace =
9845 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9846 " "
9847 } else {
9848 ""
9849 };
9850
9851 this.buffer.update(cx, |buffer, cx| {
9852 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9853 });
9854 }
9855 }
9856
9857 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9858 s.select_anchor_ranges(cursor_positions)
9859 });
9860 });
9861 }
9862
9863 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9864 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9865 self.join_lines_impl(true, window, cx);
9866 }
9867
9868 pub fn sort_lines_case_sensitive(
9869 &mut self,
9870 _: &SortLinesCaseSensitive,
9871 window: &mut Window,
9872 cx: &mut Context<Self>,
9873 ) {
9874 self.manipulate_lines(window, cx, |lines| lines.sort())
9875 }
9876
9877 pub fn sort_lines_case_insensitive(
9878 &mut self,
9879 _: &SortLinesCaseInsensitive,
9880 window: &mut Window,
9881 cx: &mut Context<Self>,
9882 ) {
9883 self.manipulate_lines(window, cx, |lines| {
9884 lines.sort_by_key(|line| line.to_lowercase())
9885 })
9886 }
9887
9888 pub fn unique_lines_case_insensitive(
9889 &mut self,
9890 _: &UniqueLinesCaseInsensitive,
9891 window: &mut Window,
9892 cx: &mut Context<Self>,
9893 ) {
9894 self.manipulate_lines(window, cx, |lines| {
9895 let mut seen = HashSet::default();
9896 lines.retain(|line| seen.insert(line.to_lowercase()));
9897 })
9898 }
9899
9900 pub fn unique_lines_case_sensitive(
9901 &mut self,
9902 _: &UniqueLinesCaseSensitive,
9903 window: &mut Window,
9904 cx: &mut Context<Self>,
9905 ) {
9906 self.manipulate_lines(window, cx, |lines| {
9907 let mut seen = HashSet::default();
9908 lines.retain(|line| seen.insert(*line));
9909 })
9910 }
9911
9912 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9913 let Some(project) = self.project.clone() else {
9914 return;
9915 };
9916 self.reload(project, window, cx)
9917 .detach_and_notify_err(window, cx);
9918 }
9919
9920 pub fn restore_file(
9921 &mut self,
9922 _: &::git::RestoreFile,
9923 window: &mut Window,
9924 cx: &mut Context<Self>,
9925 ) {
9926 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9927 let mut buffer_ids = HashSet::default();
9928 let snapshot = self.buffer().read(cx).snapshot(cx);
9929 for selection in self.selections.all::<usize>(cx) {
9930 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9931 }
9932
9933 let buffer = self.buffer().read(cx);
9934 let ranges = buffer_ids
9935 .into_iter()
9936 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9937 .collect::<Vec<_>>();
9938
9939 self.restore_hunks_in_ranges(ranges, window, cx);
9940 }
9941
9942 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9943 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9944 let selections = self
9945 .selections
9946 .all(cx)
9947 .into_iter()
9948 .map(|s| s.range())
9949 .collect();
9950 self.restore_hunks_in_ranges(selections, window, cx);
9951 }
9952
9953 pub fn restore_hunks_in_ranges(
9954 &mut self,
9955 ranges: Vec<Range<Point>>,
9956 window: &mut Window,
9957 cx: &mut Context<Editor>,
9958 ) {
9959 let mut revert_changes = HashMap::default();
9960 let chunk_by = self
9961 .snapshot(window, cx)
9962 .hunks_for_ranges(ranges)
9963 .into_iter()
9964 .chunk_by(|hunk| hunk.buffer_id);
9965 for (buffer_id, hunks) in &chunk_by {
9966 let hunks = hunks.collect::<Vec<_>>();
9967 for hunk in &hunks {
9968 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9969 }
9970 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9971 }
9972 drop(chunk_by);
9973 if !revert_changes.is_empty() {
9974 self.transact(window, cx, |editor, window, cx| {
9975 editor.restore(revert_changes, window, cx);
9976 });
9977 }
9978 }
9979
9980 pub fn open_active_item_in_terminal(
9981 &mut self,
9982 _: &OpenInTerminal,
9983 window: &mut Window,
9984 cx: &mut Context<Self>,
9985 ) {
9986 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9987 let project_path = buffer.read(cx).project_path(cx)?;
9988 let project = self.project.as_ref()?.read(cx);
9989 let entry = project.entry_for_path(&project_path, cx)?;
9990 let parent = match &entry.canonical_path {
9991 Some(canonical_path) => canonical_path.to_path_buf(),
9992 None => project.absolute_path(&project_path, cx)?,
9993 }
9994 .parent()?
9995 .to_path_buf();
9996 Some(parent)
9997 }) {
9998 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9999 }
10000 }
10001
10002 fn set_breakpoint_context_menu(
10003 &mut self,
10004 display_row: DisplayRow,
10005 position: Option<Anchor>,
10006 clicked_point: gpui::Point<Pixels>,
10007 window: &mut Window,
10008 cx: &mut Context<Self>,
10009 ) {
10010 if !cx.has_flag::<DebuggerFeatureFlag>() {
10011 return;
10012 }
10013 let source = self
10014 .buffer
10015 .read(cx)
10016 .snapshot(cx)
10017 .anchor_before(Point::new(display_row.0, 0u32));
10018
10019 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10020
10021 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10022 self,
10023 source,
10024 clicked_point,
10025 context_menu,
10026 window,
10027 cx,
10028 );
10029 }
10030
10031 fn add_edit_breakpoint_block(
10032 &mut self,
10033 anchor: Anchor,
10034 breakpoint: &Breakpoint,
10035 edit_action: BreakpointPromptEditAction,
10036 window: &mut Window,
10037 cx: &mut Context<Self>,
10038 ) {
10039 let weak_editor = cx.weak_entity();
10040 let bp_prompt = cx.new(|cx| {
10041 BreakpointPromptEditor::new(
10042 weak_editor,
10043 anchor,
10044 breakpoint.clone(),
10045 edit_action,
10046 window,
10047 cx,
10048 )
10049 });
10050
10051 let height = bp_prompt.update(cx, |this, cx| {
10052 this.prompt
10053 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10054 });
10055 let cloned_prompt = bp_prompt.clone();
10056 let blocks = vec![BlockProperties {
10057 style: BlockStyle::Sticky,
10058 placement: BlockPlacement::Above(anchor),
10059 height: Some(height),
10060 render: Arc::new(move |cx| {
10061 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10062 cloned_prompt.clone().into_any_element()
10063 }),
10064 priority: 0,
10065 render_in_minimap: true,
10066 }];
10067
10068 let focus_handle = bp_prompt.focus_handle(cx);
10069 window.focus(&focus_handle);
10070
10071 let block_ids = self.insert_blocks(blocks, None, cx);
10072 bp_prompt.update(cx, |prompt, _| {
10073 prompt.add_block_ids(block_ids);
10074 });
10075 }
10076
10077 pub(crate) fn breakpoint_at_row(
10078 &self,
10079 row: u32,
10080 window: &mut Window,
10081 cx: &mut Context<Self>,
10082 ) -> Option<(Anchor, Breakpoint)> {
10083 let snapshot = self.snapshot(window, cx);
10084 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10085
10086 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10087 }
10088
10089 pub(crate) fn breakpoint_at_anchor(
10090 &self,
10091 breakpoint_position: Anchor,
10092 snapshot: &EditorSnapshot,
10093 cx: &mut Context<Self>,
10094 ) -> Option<(Anchor, Breakpoint)> {
10095 let project = self.project.clone()?;
10096
10097 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10098 snapshot
10099 .buffer_snapshot
10100 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10101 })?;
10102
10103 let enclosing_excerpt = breakpoint_position.excerpt_id;
10104 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10105 let buffer_snapshot = buffer.read(cx).snapshot();
10106
10107 let row = buffer_snapshot
10108 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10109 .row;
10110
10111 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10112 let anchor_end = snapshot
10113 .buffer_snapshot
10114 .anchor_after(Point::new(row, line_len));
10115
10116 let bp = self
10117 .breakpoint_store
10118 .as_ref()?
10119 .read_with(cx, |breakpoint_store, cx| {
10120 breakpoint_store
10121 .breakpoints(
10122 &buffer,
10123 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10124 &buffer_snapshot,
10125 cx,
10126 )
10127 .next()
10128 .and_then(|(bp, _)| {
10129 let breakpoint_row = buffer_snapshot
10130 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10131 .row;
10132
10133 if breakpoint_row == row {
10134 snapshot
10135 .buffer_snapshot
10136 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10137 .map(|position| (position, bp.bp.clone()))
10138 } else {
10139 None
10140 }
10141 })
10142 });
10143 bp
10144 }
10145
10146 pub fn edit_log_breakpoint(
10147 &mut self,
10148 _: &EditLogBreakpoint,
10149 window: &mut Window,
10150 cx: &mut Context<Self>,
10151 ) {
10152 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10153 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10154 message: None,
10155 state: BreakpointState::Enabled,
10156 condition: None,
10157 hit_condition: None,
10158 });
10159
10160 self.add_edit_breakpoint_block(
10161 anchor,
10162 &breakpoint,
10163 BreakpointPromptEditAction::Log,
10164 window,
10165 cx,
10166 );
10167 }
10168 }
10169
10170 fn breakpoints_at_cursors(
10171 &self,
10172 window: &mut Window,
10173 cx: &mut Context<Self>,
10174 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10175 let snapshot = self.snapshot(window, cx);
10176 let cursors = self
10177 .selections
10178 .disjoint_anchors()
10179 .into_iter()
10180 .map(|selection| {
10181 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10182
10183 let breakpoint_position = self
10184 .breakpoint_at_row(cursor_position.row, window, cx)
10185 .map(|bp| bp.0)
10186 .unwrap_or_else(|| {
10187 snapshot
10188 .display_snapshot
10189 .buffer_snapshot
10190 .anchor_after(Point::new(cursor_position.row, 0))
10191 });
10192
10193 let breakpoint = self
10194 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10195 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10196
10197 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10198 })
10199 // 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.
10200 .collect::<HashMap<Anchor, _>>();
10201
10202 cursors.into_iter().collect()
10203 }
10204
10205 pub fn enable_breakpoint(
10206 &mut self,
10207 _: &crate::actions::EnableBreakpoint,
10208 window: &mut Window,
10209 cx: &mut Context<Self>,
10210 ) {
10211 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10212 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10213 continue;
10214 };
10215 self.edit_breakpoint_at_anchor(
10216 anchor,
10217 breakpoint,
10218 BreakpointEditAction::InvertState,
10219 cx,
10220 );
10221 }
10222 }
10223
10224 pub fn disable_breakpoint(
10225 &mut self,
10226 _: &crate::actions::DisableBreakpoint,
10227 window: &mut Window,
10228 cx: &mut Context<Self>,
10229 ) {
10230 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10231 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10232 continue;
10233 };
10234 self.edit_breakpoint_at_anchor(
10235 anchor,
10236 breakpoint,
10237 BreakpointEditAction::InvertState,
10238 cx,
10239 );
10240 }
10241 }
10242
10243 pub fn toggle_breakpoint(
10244 &mut self,
10245 _: &crate::actions::ToggleBreakpoint,
10246 window: &mut Window,
10247 cx: &mut Context<Self>,
10248 ) {
10249 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10250 if let Some(breakpoint) = breakpoint {
10251 self.edit_breakpoint_at_anchor(
10252 anchor,
10253 breakpoint,
10254 BreakpointEditAction::Toggle,
10255 cx,
10256 );
10257 } else {
10258 self.edit_breakpoint_at_anchor(
10259 anchor,
10260 Breakpoint::new_standard(),
10261 BreakpointEditAction::Toggle,
10262 cx,
10263 );
10264 }
10265 }
10266 }
10267
10268 pub fn edit_breakpoint_at_anchor(
10269 &mut self,
10270 breakpoint_position: Anchor,
10271 breakpoint: Breakpoint,
10272 edit_action: BreakpointEditAction,
10273 cx: &mut Context<Self>,
10274 ) {
10275 let Some(breakpoint_store) = &self.breakpoint_store else {
10276 return;
10277 };
10278
10279 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10280 if breakpoint_position == Anchor::min() {
10281 self.buffer()
10282 .read(cx)
10283 .excerpt_buffer_ids()
10284 .into_iter()
10285 .next()
10286 } else {
10287 None
10288 }
10289 }) else {
10290 return;
10291 };
10292
10293 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10294 return;
10295 };
10296
10297 breakpoint_store.update(cx, |breakpoint_store, cx| {
10298 breakpoint_store.toggle_breakpoint(
10299 buffer,
10300 BreakpointWithPosition {
10301 position: breakpoint_position.text_anchor,
10302 bp: breakpoint,
10303 },
10304 edit_action,
10305 cx,
10306 );
10307 });
10308
10309 cx.notify();
10310 }
10311
10312 #[cfg(any(test, feature = "test-support"))]
10313 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10314 self.breakpoint_store.clone()
10315 }
10316
10317 pub fn prepare_restore_change(
10318 &self,
10319 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10320 hunk: &MultiBufferDiffHunk,
10321 cx: &mut App,
10322 ) -> Option<()> {
10323 if hunk.is_created_file() {
10324 return None;
10325 }
10326 let buffer = self.buffer.read(cx);
10327 let diff = buffer.diff_for(hunk.buffer_id)?;
10328 let buffer = buffer.buffer(hunk.buffer_id)?;
10329 let buffer = buffer.read(cx);
10330 let original_text = diff
10331 .read(cx)
10332 .base_text()
10333 .as_rope()
10334 .slice(hunk.diff_base_byte_range.clone());
10335 let buffer_snapshot = buffer.snapshot();
10336 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10337 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10338 probe
10339 .0
10340 .start
10341 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10342 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10343 }) {
10344 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10345 Some(())
10346 } else {
10347 None
10348 }
10349 }
10350
10351 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10352 self.manipulate_lines(window, cx, |lines| lines.reverse())
10353 }
10354
10355 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10356 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10357 }
10358
10359 fn manipulate_lines<Fn>(
10360 &mut self,
10361 window: &mut Window,
10362 cx: &mut Context<Self>,
10363 mut callback: Fn,
10364 ) where
10365 Fn: FnMut(&mut Vec<&str>),
10366 {
10367 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10368
10369 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10370 let buffer = self.buffer.read(cx).snapshot(cx);
10371
10372 let mut edits = Vec::new();
10373
10374 let selections = self.selections.all::<Point>(cx);
10375 let mut selections = selections.iter().peekable();
10376 let mut contiguous_row_selections = Vec::new();
10377 let mut new_selections = Vec::new();
10378 let mut added_lines = 0;
10379 let mut removed_lines = 0;
10380
10381 while let Some(selection) = selections.next() {
10382 let (start_row, end_row) = consume_contiguous_rows(
10383 &mut contiguous_row_selections,
10384 selection,
10385 &display_map,
10386 &mut selections,
10387 );
10388
10389 let start_point = Point::new(start_row.0, 0);
10390 let end_point = Point::new(
10391 end_row.previous_row().0,
10392 buffer.line_len(end_row.previous_row()),
10393 );
10394 let text = buffer
10395 .text_for_range(start_point..end_point)
10396 .collect::<String>();
10397
10398 let mut lines = text.split('\n').collect_vec();
10399
10400 let lines_before = lines.len();
10401 callback(&mut lines);
10402 let lines_after = lines.len();
10403
10404 edits.push((start_point..end_point, lines.join("\n")));
10405
10406 // Selections must change based on added and removed line count
10407 let start_row =
10408 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10409 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10410 new_selections.push(Selection {
10411 id: selection.id,
10412 start: start_row,
10413 end: end_row,
10414 goal: SelectionGoal::None,
10415 reversed: selection.reversed,
10416 });
10417
10418 if lines_after > lines_before {
10419 added_lines += lines_after - lines_before;
10420 } else if lines_before > lines_after {
10421 removed_lines += lines_before - lines_after;
10422 }
10423 }
10424
10425 self.transact(window, cx, |this, window, cx| {
10426 let buffer = this.buffer.update(cx, |buffer, cx| {
10427 buffer.edit(edits, None, cx);
10428 buffer.snapshot(cx)
10429 });
10430
10431 // Recalculate offsets on newly edited buffer
10432 let new_selections = new_selections
10433 .iter()
10434 .map(|s| {
10435 let start_point = Point::new(s.start.0, 0);
10436 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10437 Selection {
10438 id: s.id,
10439 start: buffer.point_to_offset(start_point),
10440 end: buffer.point_to_offset(end_point),
10441 goal: s.goal,
10442 reversed: s.reversed,
10443 }
10444 })
10445 .collect();
10446
10447 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10448 s.select(new_selections);
10449 });
10450
10451 this.request_autoscroll(Autoscroll::fit(), cx);
10452 });
10453 }
10454
10455 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10456 self.manipulate_text(window, cx, |text| {
10457 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10458 if has_upper_case_characters {
10459 text.to_lowercase()
10460 } else {
10461 text.to_uppercase()
10462 }
10463 })
10464 }
10465
10466 pub fn convert_to_upper_case(
10467 &mut self,
10468 _: &ConvertToUpperCase,
10469 window: &mut Window,
10470 cx: &mut Context<Self>,
10471 ) {
10472 self.manipulate_text(window, cx, |text| text.to_uppercase())
10473 }
10474
10475 pub fn convert_to_lower_case(
10476 &mut self,
10477 _: &ConvertToLowerCase,
10478 window: &mut Window,
10479 cx: &mut Context<Self>,
10480 ) {
10481 self.manipulate_text(window, cx, |text| text.to_lowercase())
10482 }
10483
10484 pub fn convert_to_title_case(
10485 &mut self,
10486 _: &ConvertToTitleCase,
10487 window: &mut Window,
10488 cx: &mut Context<Self>,
10489 ) {
10490 self.manipulate_text(window, cx, |text| {
10491 text.split('\n')
10492 .map(|line| line.to_case(Case::Title))
10493 .join("\n")
10494 })
10495 }
10496
10497 pub fn convert_to_snake_case(
10498 &mut self,
10499 _: &ConvertToSnakeCase,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10504 }
10505
10506 pub fn convert_to_kebab_case(
10507 &mut self,
10508 _: &ConvertToKebabCase,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10513 }
10514
10515 pub fn convert_to_upper_camel_case(
10516 &mut self,
10517 _: &ConvertToUpperCamelCase,
10518 window: &mut Window,
10519 cx: &mut Context<Self>,
10520 ) {
10521 self.manipulate_text(window, cx, |text| {
10522 text.split('\n')
10523 .map(|line| line.to_case(Case::UpperCamel))
10524 .join("\n")
10525 })
10526 }
10527
10528 pub fn convert_to_lower_camel_case(
10529 &mut self,
10530 _: &ConvertToLowerCamelCase,
10531 window: &mut Window,
10532 cx: &mut Context<Self>,
10533 ) {
10534 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10535 }
10536
10537 pub fn convert_to_opposite_case(
10538 &mut self,
10539 _: &ConvertToOppositeCase,
10540 window: &mut Window,
10541 cx: &mut Context<Self>,
10542 ) {
10543 self.manipulate_text(window, cx, |text| {
10544 text.chars()
10545 .fold(String::with_capacity(text.len()), |mut t, c| {
10546 if c.is_uppercase() {
10547 t.extend(c.to_lowercase());
10548 } else {
10549 t.extend(c.to_uppercase());
10550 }
10551 t
10552 })
10553 })
10554 }
10555
10556 pub fn convert_to_rot13(
10557 &mut self,
10558 _: &ConvertToRot13,
10559 window: &mut Window,
10560 cx: &mut Context<Self>,
10561 ) {
10562 self.manipulate_text(window, cx, |text| {
10563 text.chars()
10564 .map(|c| match c {
10565 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10566 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10567 _ => c,
10568 })
10569 .collect()
10570 })
10571 }
10572
10573 pub fn convert_to_rot47(
10574 &mut self,
10575 _: &ConvertToRot47,
10576 window: &mut Window,
10577 cx: &mut Context<Self>,
10578 ) {
10579 self.manipulate_text(window, cx, |text| {
10580 text.chars()
10581 .map(|c| {
10582 let code_point = c as u32;
10583 if code_point >= 33 && code_point <= 126 {
10584 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10585 }
10586 c
10587 })
10588 .collect()
10589 })
10590 }
10591
10592 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10593 where
10594 Fn: FnMut(&str) -> String,
10595 {
10596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10597 let buffer = self.buffer.read(cx).snapshot(cx);
10598
10599 let mut new_selections = Vec::new();
10600 let mut edits = Vec::new();
10601 let mut selection_adjustment = 0i32;
10602
10603 for selection in self.selections.all::<usize>(cx) {
10604 let selection_is_empty = selection.is_empty();
10605
10606 let (start, end) = if selection_is_empty {
10607 let word_range = movement::surrounding_word(
10608 &display_map,
10609 selection.start.to_display_point(&display_map),
10610 );
10611 let start = word_range.start.to_offset(&display_map, Bias::Left);
10612 let end = word_range.end.to_offset(&display_map, Bias::Left);
10613 (start, end)
10614 } else {
10615 (selection.start, selection.end)
10616 };
10617
10618 let text = buffer.text_for_range(start..end).collect::<String>();
10619 let old_length = text.len() as i32;
10620 let text = callback(&text);
10621
10622 new_selections.push(Selection {
10623 start: (start as i32 - selection_adjustment) as usize,
10624 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10625 goal: SelectionGoal::None,
10626 ..selection
10627 });
10628
10629 selection_adjustment += old_length - text.len() as i32;
10630
10631 edits.push((start..end, text));
10632 }
10633
10634 self.transact(window, cx, |this, window, cx| {
10635 this.buffer.update(cx, |buffer, cx| {
10636 buffer.edit(edits, None, cx);
10637 });
10638
10639 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10640 s.select(new_selections);
10641 });
10642
10643 this.request_autoscroll(Autoscroll::fit(), cx);
10644 });
10645 }
10646
10647 pub fn move_selection_on_drop(
10648 &mut self,
10649 selection: &Selection<Anchor>,
10650 target: DisplayPoint,
10651 is_cut: bool,
10652 window: &mut Window,
10653 cx: &mut Context<Self>,
10654 ) {
10655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10656 let buffer = &display_map.buffer_snapshot;
10657 let mut edits = Vec::new();
10658 let insert_point = display_map
10659 .clip_point(target, Bias::Left)
10660 .to_point(&display_map);
10661 let text = buffer
10662 .text_for_range(selection.start..selection.end)
10663 .collect::<String>();
10664 if is_cut {
10665 edits.push(((selection.start..selection.end), String::new()));
10666 }
10667 let insert_anchor = buffer.anchor_before(insert_point);
10668 edits.push(((insert_anchor..insert_anchor), text));
10669 let last_edit_start = insert_anchor.bias_left(buffer);
10670 let last_edit_end = insert_anchor.bias_right(buffer);
10671 self.transact(window, cx, |this, window, cx| {
10672 this.buffer.update(cx, |buffer, cx| {
10673 buffer.edit(edits, None, cx);
10674 });
10675 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10676 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10677 });
10678 });
10679 }
10680
10681 pub fn clear_selection_drag_state(&mut self) {
10682 self.selection_drag_state = SelectionDragState::None;
10683 }
10684
10685 pub fn duplicate(
10686 &mut self,
10687 upwards: bool,
10688 whole_lines: bool,
10689 window: &mut Window,
10690 cx: &mut Context<Self>,
10691 ) {
10692 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10693
10694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10695 let buffer = &display_map.buffer_snapshot;
10696 let selections = self.selections.all::<Point>(cx);
10697
10698 let mut edits = Vec::new();
10699 let mut selections_iter = selections.iter().peekable();
10700 while let Some(selection) = selections_iter.next() {
10701 let mut rows = selection.spanned_rows(false, &display_map);
10702 // duplicate line-wise
10703 if whole_lines || selection.start == selection.end {
10704 // Avoid duplicating the same lines twice.
10705 while let Some(next_selection) = selections_iter.peek() {
10706 let next_rows = next_selection.spanned_rows(false, &display_map);
10707 if next_rows.start < rows.end {
10708 rows.end = next_rows.end;
10709 selections_iter.next().unwrap();
10710 } else {
10711 break;
10712 }
10713 }
10714
10715 // Copy the text from the selected row region and splice it either at the start
10716 // or end of the region.
10717 let start = Point::new(rows.start.0, 0);
10718 let end = Point::new(
10719 rows.end.previous_row().0,
10720 buffer.line_len(rows.end.previous_row()),
10721 );
10722 let text = buffer
10723 .text_for_range(start..end)
10724 .chain(Some("\n"))
10725 .collect::<String>();
10726 let insert_location = if upwards {
10727 Point::new(rows.end.0, 0)
10728 } else {
10729 start
10730 };
10731 edits.push((insert_location..insert_location, text));
10732 } else {
10733 // duplicate character-wise
10734 let start = selection.start;
10735 let end = selection.end;
10736 let text = buffer.text_for_range(start..end).collect::<String>();
10737 edits.push((selection.end..selection.end, text));
10738 }
10739 }
10740
10741 self.transact(window, cx, |this, _, cx| {
10742 this.buffer.update(cx, |buffer, cx| {
10743 buffer.edit(edits, None, cx);
10744 });
10745
10746 this.request_autoscroll(Autoscroll::fit(), cx);
10747 });
10748 }
10749
10750 pub fn duplicate_line_up(
10751 &mut self,
10752 _: &DuplicateLineUp,
10753 window: &mut Window,
10754 cx: &mut Context<Self>,
10755 ) {
10756 self.duplicate(true, true, window, cx);
10757 }
10758
10759 pub fn duplicate_line_down(
10760 &mut self,
10761 _: &DuplicateLineDown,
10762 window: &mut Window,
10763 cx: &mut Context<Self>,
10764 ) {
10765 self.duplicate(false, true, window, cx);
10766 }
10767
10768 pub fn duplicate_selection(
10769 &mut self,
10770 _: &DuplicateSelection,
10771 window: &mut Window,
10772 cx: &mut Context<Self>,
10773 ) {
10774 self.duplicate(false, false, window, cx);
10775 }
10776
10777 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10778 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10779
10780 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10781 let buffer = self.buffer.read(cx).snapshot(cx);
10782
10783 let mut edits = Vec::new();
10784 let mut unfold_ranges = Vec::new();
10785 let mut refold_creases = Vec::new();
10786
10787 let selections = self.selections.all::<Point>(cx);
10788 let mut selections = selections.iter().peekable();
10789 let mut contiguous_row_selections = Vec::new();
10790 let mut new_selections = Vec::new();
10791
10792 while let Some(selection) = selections.next() {
10793 // Find all the selections that span a contiguous row range
10794 let (start_row, end_row) = consume_contiguous_rows(
10795 &mut contiguous_row_selections,
10796 selection,
10797 &display_map,
10798 &mut selections,
10799 );
10800
10801 // Move the text spanned by the row range to be before the line preceding the row range
10802 if start_row.0 > 0 {
10803 let range_to_move = Point::new(
10804 start_row.previous_row().0,
10805 buffer.line_len(start_row.previous_row()),
10806 )
10807 ..Point::new(
10808 end_row.previous_row().0,
10809 buffer.line_len(end_row.previous_row()),
10810 );
10811 let insertion_point = display_map
10812 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10813 .0;
10814
10815 // Don't move lines across excerpts
10816 if buffer
10817 .excerpt_containing(insertion_point..range_to_move.end)
10818 .is_some()
10819 {
10820 let text = buffer
10821 .text_for_range(range_to_move.clone())
10822 .flat_map(|s| s.chars())
10823 .skip(1)
10824 .chain(['\n'])
10825 .collect::<String>();
10826
10827 edits.push((
10828 buffer.anchor_after(range_to_move.start)
10829 ..buffer.anchor_before(range_to_move.end),
10830 String::new(),
10831 ));
10832 let insertion_anchor = buffer.anchor_after(insertion_point);
10833 edits.push((insertion_anchor..insertion_anchor, text));
10834
10835 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10836
10837 // Move selections up
10838 new_selections.extend(contiguous_row_selections.drain(..).map(
10839 |mut selection| {
10840 selection.start.row -= row_delta;
10841 selection.end.row -= row_delta;
10842 selection
10843 },
10844 ));
10845
10846 // Move folds up
10847 unfold_ranges.push(range_to_move.clone());
10848 for fold in display_map.folds_in_range(
10849 buffer.anchor_before(range_to_move.start)
10850 ..buffer.anchor_after(range_to_move.end),
10851 ) {
10852 let mut start = fold.range.start.to_point(&buffer);
10853 let mut end = fold.range.end.to_point(&buffer);
10854 start.row -= row_delta;
10855 end.row -= row_delta;
10856 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10857 }
10858 }
10859 }
10860
10861 // If we didn't move line(s), preserve the existing selections
10862 new_selections.append(&mut contiguous_row_selections);
10863 }
10864
10865 self.transact(window, cx, |this, window, cx| {
10866 this.unfold_ranges(&unfold_ranges, true, true, cx);
10867 this.buffer.update(cx, |buffer, cx| {
10868 for (range, text) in edits {
10869 buffer.edit([(range, text)], None, cx);
10870 }
10871 });
10872 this.fold_creases(refold_creases, true, window, cx);
10873 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10874 s.select(new_selections);
10875 })
10876 });
10877 }
10878
10879 pub fn move_line_down(
10880 &mut self,
10881 _: &MoveLineDown,
10882 window: &mut Window,
10883 cx: &mut Context<Self>,
10884 ) {
10885 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10886
10887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10888 let buffer = self.buffer.read(cx).snapshot(cx);
10889
10890 let mut edits = Vec::new();
10891 let mut unfold_ranges = Vec::new();
10892 let mut refold_creases = Vec::new();
10893
10894 let selections = self.selections.all::<Point>(cx);
10895 let mut selections = selections.iter().peekable();
10896 let mut contiguous_row_selections = Vec::new();
10897 let mut new_selections = Vec::new();
10898
10899 while let Some(selection) = selections.next() {
10900 // Find all the selections that span a contiguous row range
10901 let (start_row, end_row) = consume_contiguous_rows(
10902 &mut contiguous_row_selections,
10903 selection,
10904 &display_map,
10905 &mut selections,
10906 );
10907
10908 // Move the text spanned by the row range to be after the last line of the row range
10909 if end_row.0 <= buffer.max_point().row {
10910 let range_to_move =
10911 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10912 let insertion_point = display_map
10913 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10914 .0;
10915
10916 // Don't move lines across excerpt boundaries
10917 if buffer
10918 .excerpt_containing(range_to_move.start..insertion_point)
10919 .is_some()
10920 {
10921 let mut text = String::from("\n");
10922 text.extend(buffer.text_for_range(range_to_move.clone()));
10923 text.pop(); // Drop trailing newline
10924 edits.push((
10925 buffer.anchor_after(range_to_move.start)
10926 ..buffer.anchor_before(range_to_move.end),
10927 String::new(),
10928 ));
10929 let insertion_anchor = buffer.anchor_after(insertion_point);
10930 edits.push((insertion_anchor..insertion_anchor, text));
10931
10932 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10933
10934 // Move selections down
10935 new_selections.extend(contiguous_row_selections.drain(..).map(
10936 |mut selection| {
10937 selection.start.row += row_delta;
10938 selection.end.row += row_delta;
10939 selection
10940 },
10941 ));
10942
10943 // Move folds down
10944 unfold_ranges.push(range_to_move.clone());
10945 for fold in display_map.folds_in_range(
10946 buffer.anchor_before(range_to_move.start)
10947 ..buffer.anchor_after(range_to_move.end),
10948 ) {
10949 let mut start = fold.range.start.to_point(&buffer);
10950 let mut end = fold.range.end.to_point(&buffer);
10951 start.row += row_delta;
10952 end.row += row_delta;
10953 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10954 }
10955 }
10956 }
10957
10958 // If we didn't move line(s), preserve the existing selections
10959 new_selections.append(&mut contiguous_row_selections);
10960 }
10961
10962 self.transact(window, cx, |this, window, cx| {
10963 this.unfold_ranges(&unfold_ranges, true, true, cx);
10964 this.buffer.update(cx, |buffer, cx| {
10965 for (range, text) in edits {
10966 buffer.edit([(range, text)], None, cx);
10967 }
10968 });
10969 this.fold_creases(refold_creases, true, window, cx);
10970 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10971 s.select(new_selections)
10972 });
10973 });
10974 }
10975
10976 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10977 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10978 let text_layout_details = &self.text_layout_details(window);
10979 self.transact(window, cx, |this, window, cx| {
10980 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10981 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10982 s.move_with(|display_map, selection| {
10983 if !selection.is_empty() {
10984 return;
10985 }
10986
10987 let mut head = selection.head();
10988 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10989 if head.column() == display_map.line_len(head.row()) {
10990 transpose_offset = display_map
10991 .buffer_snapshot
10992 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10993 }
10994
10995 if transpose_offset == 0 {
10996 return;
10997 }
10998
10999 *head.column_mut() += 1;
11000 head = display_map.clip_point(head, Bias::Right);
11001 let goal = SelectionGoal::HorizontalPosition(
11002 display_map
11003 .x_for_display_point(head, text_layout_details)
11004 .into(),
11005 );
11006 selection.collapse_to(head, goal);
11007
11008 let transpose_start = display_map
11009 .buffer_snapshot
11010 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11011 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11012 let transpose_end = display_map
11013 .buffer_snapshot
11014 .clip_offset(transpose_offset + 1, Bias::Right);
11015 if let Some(ch) =
11016 display_map.buffer_snapshot.chars_at(transpose_start).next()
11017 {
11018 edits.push((transpose_start..transpose_offset, String::new()));
11019 edits.push((transpose_end..transpose_end, ch.to_string()));
11020 }
11021 }
11022 });
11023 edits
11024 });
11025 this.buffer
11026 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11027 let selections = this.selections.all::<usize>(cx);
11028 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11029 s.select(selections);
11030 });
11031 });
11032 }
11033
11034 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11035 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11036 self.rewrap_impl(RewrapOptions::default(), cx)
11037 }
11038
11039 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11040 let buffer = self.buffer.read(cx).snapshot(cx);
11041 let selections = self.selections.all::<Point>(cx);
11042
11043 // Shrink and split selections to respect paragraph boundaries.
11044 let ranges = selections.into_iter().flat_map(|selection| {
11045 let language_settings = buffer.language_settings_at(selection.head(), cx);
11046 let language_scope = buffer.language_scope_at(selection.head());
11047
11048 let Some(start_row) = (selection.start.row..=selection.end.row)
11049 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11050 else {
11051 return vec![];
11052 };
11053 let Some(end_row) = (selection.start.row..=selection.end.row)
11054 .rev()
11055 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11056 else {
11057 return vec![];
11058 };
11059
11060 let mut row = start_row;
11061 let mut ranges = Vec::new();
11062 while let Some(blank_row) =
11063 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11064 {
11065 let next_paragraph_start = (blank_row + 1..=end_row)
11066 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11067 .unwrap();
11068 ranges.push((
11069 language_settings.clone(),
11070 language_scope.clone(),
11071 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11072 ));
11073 row = next_paragraph_start;
11074 }
11075 ranges.push((
11076 language_settings.clone(),
11077 language_scope.clone(),
11078 Point::new(row, 0)..Point::new(end_row, 0),
11079 ));
11080
11081 ranges
11082 });
11083
11084 let mut edits = Vec::new();
11085 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11086
11087 for (language_settings, language_scope, range) in ranges {
11088 let mut start_row = range.start.row;
11089 let mut end_row = range.end.row;
11090
11091 // Skip selections that overlap with a range that has already been rewrapped.
11092 let selection_range = start_row..end_row;
11093 if rewrapped_row_ranges
11094 .iter()
11095 .any(|range| range.overlaps(&selection_range))
11096 {
11097 continue;
11098 }
11099
11100 let tab_size = language_settings.tab_size;
11101
11102 // Since not all lines in the selection may be at the same indent
11103 // level, choose the indent size that is the most common between all
11104 // of the lines.
11105 //
11106 // If there is a tie, we use the deepest indent.
11107 let (indent_size, indent_end) = {
11108 let mut indent_size_occurrences = HashMap::default();
11109 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11110
11111 for row in start_row..=end_row {
11112 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11113 rows_by_indent_size.entry(indent).or_default().push(row);
11114 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11115 }
11116
11117 let indent_size = indent_size_occurrences
11118 .into_iter()
11119 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11120 .map(|(indent, _)| indent)
11121 .unwrap_or_default();
11122 let row = rows_by_indent_size[&indent_size][0];
11123 let indent_end = Point::new(row, indent_size.len);
11124
11125 (indent_size, indent_end)
11126 };
11127
11128 let mut line_prefix = indent_size.chars().collect::<String>();
11129
11130 let mut inside_comment = false;
11131 if let Some(comment_prefix) = language_scope.and_then(|language| {
11132 language
11133 .line_comment_prefixes()
11134 .iter()
11135 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11136 .cloned()
11137 }) {
11138 line_prefix.push_str(&comment_prefix);
11139 inside_comment = true;
11140 }
11141
11142 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11143 RewrapBehavior::InComments => inside_comment,
11144 RewrapBehavior::InSelections => !range.is_empty(),
11145 RewrapBehavior::Anywhere => true,
11146 };
11147
11148 let should_rewrap = options.override_language_settings
11149 || allow_rewrap_based_on_language
11150 || self.hard_wrap.is_some();
11151 if !should_rewrap {
11152 continue;
11153 }
11154
11155 if range.is_empty() {
11156 'expand_upwards: while start_row > 0 {
11157 let prev_row = start_row - 1;
11158 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11159 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11160 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11161 {
11162 start_row = prev_row;
11163 } else {
11164 break 'expand_upwards;
11165 }
11166 }
11167
11168 'expand_downwards: while end_row < buffer.max_point().row {
11169 let next_row = end_row + 1;
11170 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11171 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11172 && !buffer.is_line_blank(MultiBufferRow(next_row))
11173 {
11174 end_row = next_row;
11175 } else {
11176 break 'expand_downwards;
11177 }
11178 }
11179 }
11180
11181 let start = Point::new(start_row, 0);
11182 let start_offset = start.to_offset(&buffer);
11183 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11184 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11185 let Some(lines_without_prefixes) = selection_text
11186 .lines()
11187 .map(|line| {
11188 line.strip_prefix(&line_prefix)
11189 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11190 .with_context(|| {
11191 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11192 })
11193 })
11194 .collect::<Result<Vec<_>, _>>()
11195 .log_err()
11196 else {
11197 continue;
11198 };
11199
11200 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11201 buffer
11202 .language_settings_at(Point::new(start_row, 0), cx)
11203 .preferred_line_length as usize
11204 });
11205 let wrapped_text = wrap_with_prefix(
11206 line_prefix,
11207 lines_without_prefixes.join("\n"),
11208 wrap_column,
11209 tab_size,
11210 options.preserve_existing_whitespace,
11211 );
11212
11213 // TODO: should always use char-based diff while still supporting cursor behavior that
11214 // matches vim.
11215 let mut diff_options = DiffOptions::default();
11216 if options.override_language_settings {
11217 diff_options.max_word_diff_len = 0;
11218 diff_options.max_word_diff_line_count = 0;
11219 } else {
11220 diff_options.max_word_diff_len = usize::MAX;
11221 diff_options.max_word_diff_line_count = usize::MAX;
11222 }
11223
11224 for (old_range, new_text) in
11225 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11226 {
11227 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11228 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11229 edits.push((edit_start..edit_end, new_text));
11230 }
11231
11232 rewrapped_row_ranges.push(start_row..=end_row);
11233 }
11234
11235 self.buffer
11236 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11237 }
11238
11239 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11240 let mut text = String::new();
11241 let buffer = self.buffer.read(cx).snapshot(cx);
11242 let mut selections = self.selections.all::<Point>(cx);
11243 let mut clipboard_selections = Vec::with_capacity(selections.len());
11244 {
11245 let max_point = buffer.max_point();
11246 let mut is_first = true;
11247 for selection in &mut selections {
11248 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11249 if is_entire_line {
11250 selection.start = Point::new(selection.start.row, 0);
11251 if !selection.is_empty() && selection.end.column == 0 {
11252 selection.end = cmp::min(max_point, selection.end);
11253 } else {
11254 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11255 }
11256 selection.goal = SelectionGoal::None;
11257 }
11258 if is_first {
11259 is_first = false;
11260 } else {
11261 text += "\n";
11262 }
11263 let mut len = 0;
11264 for chunk in buffer.text_for_range(selection.start..selection.end) {
11265 text.push_str(chunk);
11266 len += chunk.len();
11267 }
11268 clipboard_selections.push(ClipboardSelection {
11269 len,
11270 is_entire_line,
11271 first_line_indent: buffer
11272 .indent_size_for_line(MultiBufferRow(selection.start.row))
11273 .len,
11274 });
11275 }
11276 }
11277
11278 self.transact(window, cx, |this, window, cx| {
11279 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11280 s.select(selections);
11281 });
11282 this.insert("", window, cx);
11283 });
11284 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11285 }
11286
11287 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11288 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11289 let item = self.cut_common(window, cx);
11290 cx.write_to_clipboard(item);
11291 }
11292
11293 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11294 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11295 self.change_selections(None, window, cx, |s| {
11296 s.move_with(|snapshot, sel| {
11297 if sel.is_empty() {
11298 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11299 }
11300 });
11301 });
11302 let item = self.cut_common(window, cx);
11303 cx.set_global(KillRing(item))
11304 }
11305
11306 pub fn kill_ring_yank(
11307 &mut self,
11308 _: &KillRingYank,
11309 window: &mut Window,
11310 cx: &mut Context<Self>,
11311 ) {
11312 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11313 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11314 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11315 (kill_ring.text().to_string(), kill_ring.metadata_json())
11316 } else {
11317 return;
11318 }
11319 } else {
11320 return;
11321 };
11322 self.do_paste(&text, metadata, false, window, cx);
11323 }
11324
11325 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11326 self.do_copy(true, cx);
11327 }
11328
11329 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11330 self.do_copy(false, cx);
11331 }
11332
11333 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11334 let selections = self.selections.all::<Point>(cx);
11335 let buffer = self.buffer.read(cx).read(cx);
11336 let mut text = String::new();
11337
11338 let mut clipboard_selections = Vec::with_capacity(selections.len());
11339 {
11340 let max_point = buffer.max_point();
11341 let mut is_first = true;
11342 for selection in &selections {
11343 let mut start = selection.start;
11344 let mut end = selection.end;
11345 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11346 if is_entire_line {
11347 start = Point::new(start.row, 0);
11348 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11349 }
11350
11351 let mut trimmed_selections = Vec::new();
11352 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11353 let row = MultiBufferRow(start.row);
11354 let first_indent = buffer.indent_size_for_line(row);
11355 if first_indent.len == 0 || start.column > first_indent.len {
11356 trimmed_selections.push(start..end);
11357 } else {
11358 trimmed_selections.push(
11359 Point::new(row.0, first_indent.len)
11360 ..Point::new(row.0, buffer.line_len(row)),
11361 );
11362 for row in start.row + 1..=end.row {
11363 let mut line_len = buffer.line_len(MultiBufferRow(row));
11364 if row == end.row {
11365 line_len = end.column;
11366 }
11367 if line_len == 0 {
11368 trimmed_selections
11369 .push(Point::new(row, 0)..Point::new(row, line_len));
11370 continue;
11371 }
11372 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11373 if row_indent_size.len >= first_indent.len {
11374 trimmed_selections.push(
11375 Point::new(row, first_indent.len)..Point::new(row, line_len),
11376 );
11377 } else {
11378 trimmed_selections.clear();
11379 trimmed_selections.push(start..end);
11380 break;
11381 }
11382 }
11383 }
11384 } else {
11385 trimmed_selections.push(start..end);
11386 }
11387
11388 for trimmed_range in trimmed_selections {
11389 if is_first {
11390 is_first = false;
11391 } else {
11392 text += "\n";
11393 }
11394 let mut len = 0;
11395 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11396 text.push_str(chunk);
11397 len += chunk.len();
11398 }
11399 clipboard_selections.push(ClipboardSelection {
11400 len,
11401 is_entire_line,
11402 first_line_indent: buffer
11403 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11404 .len,
11405 });
11406 }
11407 }
11408 }
11409
11410 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11411 text,
11412 clipboard_selections,
11413 ));
11414 }
11415
11416 pub fn do_paste(
11417 &mut self,
11418 text: &String,
11419 clipboard_selections: Option<Vec<ClipboardSelection>>,
11420 handle_entire_lines: bool,
11421 window: &mut Window,
11422 cx: &mut Context<Self>,
11423 ) {
11424 if self.read_only(cx) {
11425 return;
11426 }
11427
11428 let clipboard_text = Cow::Borrowed(text);
11429
11430 self.transact(window, cx, |this, window, cx| {
11431 if let Some(mut clipboard_selections) = clipboard_selections {
11432 let old_selections = this.selections.all::<usize>(cx);
11433 let all_selections_were_entire_line =
11434 clipboard_selections.iter().all(|s| s.is_entire_line);
11435 let first_selection_indent_column =
11436 clipboard_selections.first().map(|s| s.first_line_indent);
11437 if clipboard_selections.len() != old_selections.len() {
11438 clipboard_selections.drain(..);
11439 }
11440 let cursor_offset = this.selections.last::<usize>(cx).head();
11441 let mut auto_indent_on_paste = true;
11442
11443 this.buffer.update(cx, |buffer, cx| {
11444 let snapshot = buffer.read(cx);
11445 auto_indent_on_paste = snapshot
11446 .language_settings_at(cursor_offset, cx)
11447 .auto_indent_on_paste;
11448
11449 let mut start_offset = 0;
11450 let mut edits = Vec::new();
11451 let mut original_indent_columns = Vec::new();
11452 for (ix, selection) in old_selections.iter().enumerate() {
11453 let to_insert;
11454 let entire_line;
11455 let original_indent_column;
11456 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11457 let end_offset = start_offset + clipboard_selection.len;
11458 to_insert = &clipboard_text[start_offset..end_offset];
11459 entire_line = clipboard_selection.is_entire_line;
11460 start_offset = end_offset + 1;
11461 original_indent_column = Some(clipboard_selection.first_line_indent);
11462 } else {
11463 to_insert = clipboard_text.as_str();
11464 entire_line = all_selections_were_entire_line;
11465 original_indent_column = first_selection_indent_column
11466 }
11467
11468 // If the corresponding selection was empty when this slice of the
11469 // clipboard text was written, then the entire line containing the
11470 // selection was copied. If this selection is also currently empty,
11471 // then paste the line before the current line of the buffer.
11472 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11473 let column = selection.start.to_point(&snapshot).column as usize;
11474 let line_start = selection.start - column;
11475 line_start..line_start
11476 } else {
11477 selection.range()
11478 };
11479
11480 edits.push((range, to_insert));
11481 original_indent_columns.push(original_indent_column);
11482 }
11483 drop(snapshot);
11484
11485 buffer.edit(
11486 edits,
11487 if auto_indent_on_paste {
11488 Some(AutoindentMode::Block {
11489 original_indent_columns,
11490 })
11491 } else {
11492 None
11493 },
11494 cx,
11495 );
11496 });
11497
11498 let selections = this.selections.all::<usize>(cx);
11499 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11500 s.select(selections)
11501 });
11502 } else {
11503 this.insert(&clipboard_text, window, cx);
11504 }
11505 });
11506 }
11507
11508 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11509 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11510 if let Some(item) = cx.read_from_clipboard() {
11511 let entries = item.entries();
11512
11513 match entries.first() {
11514 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11515 // of all the pasted entries.
11516 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11517 .do_paste(
11518 clipboard_string.text(),
11519 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11520 true,
11521 window,
11522 cx,
11523 ),
11524 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11525 }
11526 }
11527 }
11528
11529 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11530 if self.read_only(cx) {
11531 return;
11532 }
11533
11534 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11535
11536 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11537 if let Some((selections, _)) =
11538 self.selection_history.transaction(transaction_id).cloned()
11539 {
11540 self.change_selections(None, window, cx, |s| {
11541 s.select_anchors(selections.to_vec());
11542 });
11543 } else {
11544 log::error!(
11545 "No entry in selection_history found for undo. \
11546 This may correspond to a bug where undo does not update the selection. \
11547 If this is occurring, please add details to \
11548 https://github.com/zed-industries/zed/issues/22692"
11549 );
11550 }
11551 self.request_autoscroll(Autoscroll::fit(), cx);
11552 self.unmark_text(window, cx);
11553 self.refresh_inline_completion(true, false, window, cx);
11554 cx.emit(EditorEvent::Edited { transaction_id });
11555 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11556 }
11557 }
11558
11559 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11560 if self.read_only(cx) {
11561 return;
11562 }
11563
11564 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11565
11566 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11567 if let Some((_, Some(selections))) =
11568 self.selection_history.transaction(transaction_id).cloned()
11569 {
11570 self.change_selections(None, window, cx, |s| {
11571 s.select_anchors(selections.to_vec());
11572 });
11573 } else {
11574 log::error!(
11575 "No entry in selection_history found for redo. \
11576 This may correspond to a bug where undo does not update the selection. \
11577 If this is occurring, please add details to \
11578 https://github.com/zed-industries/zed/issues/22692"
11579 );
11580 }
11581 self.request_autoscroll(Autoscroll::fit(), cx);
11582 self.unmark_text(window, cx);
11583 self.refresh_inline_completion(true, false, window, cx);
11584 cx.emit(EditorEvent::Edited { transaction_id });
11585 }
11586 }
11587
11588 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11589 self.buffer
11590 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11591 }
11592
11593 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11594 self.buffer
11595 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11596 }
11597
11598 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11599 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11600 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11601 s.move_with(|map, selection| {
11602 let cursor = if selection.is_empty() {
11603 movement::left(map, selection.start)
11604 } else {
11605 selection.start
11606 };
11607 selection.collapse_to(cursor, SelectionGoal::None);
11608 });
11609 })
11610 }
11611
11612 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11613 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11615 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11616 })
11617 }
11618
11619 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11620 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11621 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11622 s.move_with(|map, selection| {
11623 let cursor = if selection.is_empty() {
11624 movement::right(map, selection.end)
11625 } else {
11626 selection.end
11627 };
11628 selection.collapse_to(cursor, SelectionGoal::None)
11629 });
11630 })
11631 }
11632
11633 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11634 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11635 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11636 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11637 })
11638 }
11639
11640 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11641 if self.take_rename(true, window, cx).is_some() {
11642 return;
11643 }
11644
11645 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11646 cx.propagate();
11647 return;
11648 }
11649
11650 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11651
11652 let text_layout_details = &self.text_layout_details(window);
11653 let selection_count = self.selections.count();
11654 let first_selection = self.selections.first_anchor();
11655
11656 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11657 s.move_with(|map, selection| {
11658 if !selection.is_empty() {
11659 selection.goal = SelectionGoal::None;
11660 }
11661 let (cursor, goal) = movement::up(
11662 map,
11663 selection.start,
11664 selection.goal,
11665 false,
11666 text_layout_details,
11667 );
11668 selection.collapse_to(cursor, goal);
11669 });
11670 });
11671
11672 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11673 {
11674 cx.propagate();
11675 }
11676 }
11677
11678 pub fn move_up_by_lines(
11679 &mut self,
11680 action: &MoveUpByLines,
11681 window: &mut Window,
11682 cx: &mut Context<Self>,
11683 ) {
11684 if self.take_rename(true, window, cx).is_some() {
11685 return;
11686 }
11687
11688 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11689 cx.propagate();
11690 return;
11691 }
11692
11693 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11694
11695 let text_layout_details = &self.text_layout_details(window);
11696
11697 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11698 s.move_with(|map, selection| {
11699 if !selection.is_empty() {
11700 selection.goal = SelectionGoal::None;
11701 }
11702 let (cursor, goal) = movement::up_by_rows(
11703 map,
11704 selection.start,
11705 action.lines,
11706 selection.goal,
11707 false,
11708 text_layout_details,
11709 );
11710 selection.collapse_to(cursor, goal);
11711 });
11712 })
11713 }
11714
11715 pub fn move_down_by_lines(
11716 &mut self,
11717 action: &MoveDownByLines,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) {
11721 if self.take_rename(true, window, cx).is_some() {
11722 return;
11723 }
11724
11725 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11726 cx.propagate();
11727 return;
11728 }
11729
11730 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11731
11732 let text_layout_details = &self.text_layout_details(window);
11733
11734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11735 s.move_with(|map, selection| {
11736 if !selection.is_empty() {
11737 selection.goal = SelectionGoal::None;
11738 }
11739 let (cursor, goal) = movement::down_by_rows(
11740 map,
11741 selection.start,
11742 action.lines,
11743 selection.goal,
11744 false,
11745 text_layout_details,
11746 );
11747 selection.collapse_to(cursor, goal);
11748 });
11749 })
11750 }
11751
11752 pub fn select_down_by_lines(
11753 &mut self,
11754 action: &SelectDownByLines,
11755 window: &mut Window,
11756 cx: &mut Context<Self>,
11757 ) {
11758 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11759 let text_layout_details = &self.text_layout_details(window);
11760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11761 s.move_heads_with(|map, head, goal| {
11762 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11763 })
11764 })
11765 }
11766
11767 pub fn select_up_by_lines(
11768 &mut self,
11769 action: &SelectUpByLines,
11770 window: &mut Window,
11771 cx: &mut Context<Self>,
11772 ) {
11773 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11774 let text_layout_details = &self.text_layout_details(window);
11775 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11776 s.move_heads_with(|map, head, goal| {
11777 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11778 })
11779 })
11780 }
11781
11782 pub fn select_page_up(
11783 &mut self,
11784 _: &SelectPageUp,
11785 window: &mut Window,
11786 cx: &mut Context<Self>,
11787 ) {
11788 let Some(row_count) = self.visible_row_count() else {
11789 return;
11790 };
11791
11792 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11793
11794 let text_layout_details = &self.text_layout_details(window);
11795
11796 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11797 s.move_heads_with(|map, head, goal| {
11798 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11799 })
11800 })
11801 }
11802
11803 pub fn move_page_up(
11804 &mut self,
11805 action: &MovePageUp,
11806 window: &mut Window,
11807 cx: &mut Context<Self>,
11808 ) {
11809 if self.take_rename(true, window, cx).is_some() {
11810 return;
11811 }
11812
11813 if self
11814 .context_menu
11815 .borrow_mut()
11816 .as_mut()
11817 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11818 .unwrap_or(false)
11819 {
11820 return;
11821 }
11822
11823 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11824 cx.propagate();
11825 return;
11826 }
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 autoscroll = if action.center_cursor {
11835 Autoscroll::center()
11836 } else {
11837 Autoscroll::fit()
11838 };
11839
11840 let text_layout_details = &self.text_layout_details(window);
11841
11842 self.change_selections(Some(autoscroll), window, cx, |s| {
11843 s.move_with(|map, selection| {
11844 if !selection.is_empty() {
11845 selection.goal = SelectionGoal::None;
11846 }
11847 let (cursor, goal) = movement::up_by_rows(
11848 map,
11849 selection.end,
11850 row_count,
11851 selection.goal,
11852 false,
11853 text_layout_details,
11854 );
11855 selection.collapse_to(cursor, goal);
11856 });
11857 });
11858 }
11859
11860 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11861 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11862 let text_layout_details = &self.text_layout_details(window);
11863 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11864 s.move_heads_with(|map, head, goal| {
11865 movement::up(map, head, goal, false, text_layout_details)
11866 })
11867 })
11868 }
11869
11870 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11871 self.take_rename(true, window, cx);
11872
11873 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11874 cx.propagate();
11875 return;
11876 }
11877
11878 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11879
11880 let text_layout_details = &self.text_layout_details(window);
11881 let selection_count = self.selections.count();
11882 let first_selection = self.selections.first_anchor();
11883
11884 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11885 s.move_with(|map, selection| {
11886 if !selection.is_empty() {
11887 selection.goal = SelectionGoal::None;
11888 }
11889 let (cursor, goal) = movement::down(
11890 map,
11891 selection.end,
11892 selection.goal,
11893 false,
11894 text_layout_details,
11895 );
11896 selection.collapse_to(cursor, goal);
11897 });
11898 });
11899
11900 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11901 {
11902 cx.propagate();
11903 }
11904 }
11905
11906 pub fn select_page_down(
11907 &mut self,
11908 _: &SelectPageDown,
11909 window: &mut Window,
11910 cx: &mut Context<Self>,
11911 ) {
11912 let Some(row_count) = self.visible_row_count() else {
11913 return;
11914 };
11915
11916 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11917
11918 let text_layout_details = &self.text_layout_details(window);
11919
11920 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11921 s.move_heads_with(|map, head, goal| {
11922 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11923 })
11924 })
11925 }
11926
11927 pub fn move_page_down(
11928 &mut self,
11929 action: &MovePageDown,
11930 window: &mut Window,
11931 cx: &mut Context<Self>,
11932 ) {
11933 if self.take_rename(true, window, cx).is_some() {
11934 return;
11935 }
11936
11937 if self
11938 .context_menu
11939 .borrow_mut()
11940 .as_mut()
11941 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11942 .unwrap_or(false)
11943 {
11944 return;
11945 }
11946
11947 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11948 cx.propagate();
11949 return;
11950 }
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 autoscroll = if action.center_cursor {
11959 Autoscroll::center()
11960 } else {
11961 Autoscroll::fit()
11962 };
11963
11964 let text_layout_details = &self.text_layout_details(window);
11965 self.change_selections(Some(autoscroll), window, cx, |s| {
11966 s.move_with(|map, selection| {
11967 if !selection.is_empty() {
11968 selection.goal = SelectionGoal::None;
11969 }
11970 let (cursor, goal) = movement::down_by_rows(
11971 map,
11972 selection.end,
11973 row_count,
11974 selection.goal,
11975 false,
11976 text_layout_details,
11977 );
11978 selection.collapse_to(cursor, goal);
11979 });
11980 });
11981 }
11982
11983 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11984 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11985 let text_layout_details = &self.text_layout_details(window);
11986 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11987 s.move_heads_with(|map, head, goal| {
11988 movement::down(map, head, goal, false, text_layout_details)
11989 })
11990 });
11991 }
11992
11993 pub fn context_menu_first(
11994 &mut self,
11995 _: &ContextMenuFirst,
11996 window: &mut Window,
11997 cx: &mut Context<Self>,
11998 ) {
11999 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12000 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12001 }
12002 }
12003
12004 pub fn context_menu_prev(
12005 &mut self,
12006 _: &ContextMenuPrevious,
12007 window: &mut Window,
12008 cx: &mut Context<Self>,
12009 ) {
12010 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12011 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12012 }
12013 }
12014
12015 pub fn context_menu_next(
12016 &mut self,
12017 _: &ContextMenuNext,
12018 window: &mut Window,
12019 cx: &mut Context<Self>,
12020 ) {
12021 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12022 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12023 }
12024 }
12025
12026 pub fn context_menu_last(
12027 &mut self,
12028 _: &ContextMenuLast,
12029 window: &mut Window,
12030 cx: &mut Context<Self>,
12031 ) {
12032 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12033 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12034 }
12035 }
12036
12037 pub fn move_to_previous_word_start(
12038 &mut self,
12039 _: &MoveToPreviousWordStart,
12040 window: &mut Window,
12041 cx: &mut Context<Self>,
12042 ) {
12043 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12044 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12045 s.move_cursors_with(|map, head, _| {
12046 (
12047 movement::previous_word_start(map, head),
12048 SelectionGoal::None,
12049 )
12050 });
12051 })
12052 }
12053
12054 pub fn move_to_previous_subword_start(
12055 &mut self,
12056 _: &MoveToPreviousSubwordStart,
12057 window: &mut Window,
12058 cx: &mut Context<Self>,
12059 ) {
12060 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12061 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12062 s.move_cursors_with(|map, head, _| {
12063 (
12064 movement::previous_subword_start(map, head),
12065 SelectionGoal::None,
12066 )
12067 });
12068 })
12069 }
12070
12071 pub fn select_to_previous_word_start(
12072 &mut self,
12073 _: &SelectToPreviousWordStart,
12074 window: &mut Window,
12075 cx: &mut Context<Self>,
12076 ) {
12077 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12078 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12079 s.move_heads_with(|map, head, _| {
12080 (
12081 movement::previous_word_start(map, head),
12082 SelectionGoal::None,
12083 )
12084 });
12085 })
12086 }
12087
12088 pub fn select_to_previous_subword_start(
12089 &mut self,
12090 _: &SelectToPreviousSubwordStart,
12091 window: &mut Window,
12092 cx: &mut Context<Self>,
12093 ) {
12094 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12095 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12096 s.move_heads_with(|map, head, _| {
12097 (
12098 movement::previous_subword_start(map, head),
12099 SelectionGoal::None,
12100 )
12101 });
12102 })
12103 }
12104
12105 pub fn delete_to_previous_word_start(
12106 &mut self,
12107 action: &DeleteToPreviousWordStart,
12108 window: &mut Window,
12109 cx: &mut Context<Self>,
12110 ) {
12111 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12112 self.transact(window, cx, |this, window, cx| {
12113 this.select_autoclose_pair(window, cx);
12114 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12115 s.move_with(|map, selection| {
12116 if selection.is_empty() {
12117 let cursor = if action.ignore_newlines {
12118 movement::previous_word_start(map, selection.head())
12119 } else {
12120 movement::previous_word_start_or_newline(map, selection.head())
12121 };
12122 selection.set_head(cursor, SelectionGoal::None);
12123 }
12124 });
12125 });
12126 this.insert("", window, cx);
12127 });
12128 }
12129
12130 pub fn delete_to_previous_subword_start(
12131 &mut self,
12132 _: &DeleteToPreviousSubwordStart,
12133 window: &mut Window,
12134 cx: &mut Context<Self>,
12135 ) {
12136 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12137 self.transact(window, cx, |this, window, cx| {
12138 this.select_autoclose_pair(window, cx);
12139 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12140 s.move_with(|map, selection| {
12141 if selection.is_empty() {
12142 let cursor = movement::previous_subword_start(map, selection.head());
12143 selection.set_head(cursor, SelectionGoal::None);
12144 }
12145 });
12146 });
12147 this.insert("", window, cx);
12148 });
12149 }
12150
12151 pub fn move_to_next_word_end(
12152 &mut self,
12153 _: &MoveToNextWordEnd,
12154 window: &mut Window,
12155 cx: &mut Context<Self>,
12156 ) {
12157 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12158 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12159 s.move_cursors_with(|map, head, _| {
12160 (movement::next_word_end(map, head), SelectionGoal::None)
12161 });
12162 })
12163 }
12164
12165 pub fn move_to_next_subword_end(
12166 &mut self,
12167 _: &MoveToNextSubwordEnd,
12168 window: &mut Window,
12169 cx: &mut Context<Self>,
12170 ) {
12171 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12172 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12173 s.move_cursors_with(|map, head, _| {
12174 (movement::next_subword_end(map, head), SelectionGoal::None)
12175 });
12176 })
12177 }
12178
12179 pub fn select_to_next_word_end(
12180 &mut self,
12181 _: &SelectToNextWordEnd,
12182 window: &mut Window,
12183 cx: &mut Context<Self>,
12184 ) {
12185 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12186 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12187 s.move_heads_with(|map, head, _| {
12188 (movement::next_word_end(map, head), SelectionGoal::None)
12189 });
12190 })
12191 }
12192
12193 pub fn select_to_next_subword_end(
12194 &mut self,
12195 _: &SelectToNextSubwordEnd,
12196 window: &mut Window,
12197 cx: &mut Context<Self>,
12198 ) {
12199 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12200 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12201 s.move_heads_with(|map, head, _| {
12202 (movement::next_subword_end(map, head), SelectionGoal::None)
12203 });
12204 })
12205 }
12206
12207 pub fn delete_to_next_word_end(
12208 &mut self,
12209 action: &DeleteToNextWordEnd,
12210 window: &mut Window,
12211 cx: &mut Context<Self>,
12212 ) {
12213 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12214 self.transact(window, cx, |this, window, cx| {
12215 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12216 s.move_with(|map, selection| {
12217 if selection.is_empty() {
12218 let cursor = if action.ignore_newlines {
12219 movement::next_word_end(map, selection.head())
12220 } else {
12221 movement::next_word_end_or_newline(map, selection.head())
12222 };
12223 selection.set_head(cursor, SelectionGoal::None);
12224 }
12225 });
12226 });
12227 this.insert("", window, cx);
12228 });
12229 }
12230
12231 pub fn delete_to_next_subword_end(
12232 &mut self,
12233 _: &DeleteToNextSubwordEnd,
12234 window: &mut Window,
12235 cx: &mut Context<Self>,
12236 ) {
12237 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12238 self.transact(window, cx, |this, window, cx| {
12239 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12240 s.move_with(|map, selection| {
12241 if selection.is_empty() {
12242 let cursor = movement::next_subword_end(map, selection.head());
12243 selection.set_head(cursor, SelectionGoal::None);
12244 }
12245 });
12246 });
12247 this.insert("", window, cx);
12248 });
12249 }
12250
12251 pub fn move_to_beginning_of_line(
12252 &mut self,
12253 action: &MoveToBeginningOfLine,
12254 window: &mut Window,
12255 cx: &mut Context<Self>,
12256 ) {
12257 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12258 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12259 s.move_cursors_with(|map, head, _| {
12260 (
12261 movement::indented_line_beginning(
12262 map,
12263 head,
12264 action.stop_at_soft_wraps,
12265 action.stop_at_indent,
12266 ),
12267 SelectionGoal::None,
12268 )
12269 });
12270 })
12271 }
12272
12273 pub fn select_to_beginning_of_line(
12274 &mut self,
12275 action: &SelectToBeginningOfLine,
12276 window: &mut Window,
12277 cx: &mut Context<Self>,
12278 ) {
12279 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12280 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12281 s.move_heads_with(|map, head, _| {
12282 (
12283 movement::indented_line_beginning(
12284 map,
12285 head,
12286 action.stop_at_soft_wraps,
12287 action.stop_at_indent,
12288 ),
12289 SelectionGoal::None,
12290 )
12291 });
12292 });
12293 }
12294
12295 pub fn delete_to_beginning_of_line(
12296 &mut self,
12297 action: &DeleteToBeginningOfLine,
12298 window: &mut Window,
12299 cx: &mut Context<Self>,
12300 ) {
12301 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12302 self.transact(window, cx, |this, window, cx| {
12303 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12304 s.move_with(|_, selection| {
12305 selection.reversed = true;
12306 });
12307 });
12308
12309 this.select_to_beginning_of_line(
12310 &SelectToBeginningOfLine {
12311 stop_at_soft_wraps: false,
12312 stop_at_indent: action.stop_at_indent,
12313 },
12314 window,
12315 cx,
12316 );
12317 this.backspace(&Backspace, window, cx);
12318 });
12319 }
12320
12321 pub fn move_to_end_of_line(
12322 &mut self,
12323 action: &MoveToEndOfLine,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) {
12327 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12328 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12329 s.move_cursors_with(|map, head, _| {
12330 (
12331 movement::line_end(map, head, action.stop_at_soft_wraps),
12332 SelectionGoal::None,
12333 )
12334 });
12335 })
12336 }
12337
12338 pub fn select_to_end_of_line(
12339 &mut self,
12340 action: &SelectToEndOfLine,
12341 window: &mut Window,
12342 cx: &mut Context<Self>,
12343 ) {
12344 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12345 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12346 s.move_heads_with(|map, head, _| {
12347 (
12348 movement::line_end(map, head, action.stop_at_soft_wraps),
12349 SelectionGoal::None,
12350 )
12351 });
12352 })
12353 }
12354
12355 pub fn delete_to_end_of_line(
12356 &mut self,
12357 _: &DeleteToEndOfLine,
12358 window: &mut Window,
12359 cx: &mut Context<Self>,
12360 ) {
12361 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12362 self.transact(window, cx, |this, window, cx| {
12363 this.select_to_end_of_line(
12364 &SelectToEndOfLine {
12365 stop_at_soft_wraps: false,
12366 },
12367 window,
12368 cx,
12369 );
12370 this.delete(&Delete, window, cx);
12371 });
12372 }
12373
12374 pub fn cut_to_end_of_line(
12375 &mut self,
12376 _: &CutToEndOfLine,
12377 window: &mut Window,
12378 cx: &mut Context<Self>,
12379 ) {
12380 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12381 self.transact(window, cx, |this, window, cx| {
12382 this.select_to_end_of_line(
12383 &SelectToEndOfLine {
12384 stop_at_soft_wraps: false,
12385 },
12386 window,
12387 cx,
12388 );
12389 this.cut(&Cut, window, cx);
12390 });
12391 }
12392
12393 pub fn move_to_start_of_paragraph(
12394 &mut self,
12395 _: &MoveToStartOfParagraph,
12396 window: &mut Window,
12397 cx: &mut Context<Self>,
12398 ) {
12399 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12400 cx.propagate();
12401 return;
12402 }
12403 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12404 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12405 s.move_with(|map, selection| {
12406 selection.collapse_to(
12407 movement::start_of_paragraph(map, selection.head(), 1),
12408 SelectionGoal::None,
12409 )
12410 });
12411 })
12412 }
12413
12414 pub fn move_to_end_of_paragraph(
12415 &mut self,
12416 _: &MoveToEndOfParagraph,
12417 window: &mut Window,
12418 cx: &mut Context<Self>,
12419 ) {
12420 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12421 cx.propagate();
12422 return;
12423 }
12424 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12425 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12426 s.move_with(|map, selection| {
12427 selection.collapse_to(
12428 movement::end_of_paragraph(map, selection.head(), 1),
12429 SelectionGoal::None,
12430 )
12431 });
12432 })
12433 }
12434
12435 pub fn select_to_start_of_paragraph(
12436 &mut self,
12437 _: &SelectToStartOfParagraph,
12438 window: &mut Window,
12439 cx: &mut Context<Self>,
12440 ) {
12441 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12442 cx.propagate();
12443 return;
12444 }
12445 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12446 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12447 s.move_heads_with(|map, head, _| {
12448 (
12449 movement::start_of_paragraph(map, head, 1),
12450 SelectionGoal::None,
12451 )
12452 });
12453 })
12454 }
12455
12456 pub fn select_to_end_of_paragraph(
12457 &mut self,
12458 _: &SelectToEndOfParagraph,
12459 window: &mut Window,
12460 cx: &mut Context<Self>,
12461 ) {
12462 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12463 cx.propagate();
12464 return;
12465 }
12466 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12467 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12468 s.move_heads_with(|map, head, _| {
12469 (
12470 movement::end_of_paragraph(map, head, 1),
12471 SelectionGoal::None,
12472 )
12473 });
12474 })
12475 }
12476
12477 pub fn move_to_start_of_excerpt(
12478 &mut self,
12479 _: &MoveToStartOfExcerpt,
12480 window: &mut Window,
12481 cx: &mut Context<Self>,
12482 ) {
12483 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12484 cx.propagate();
12485 return;
12486 }
12487 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12488 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12489 s.move_with(|map, selection| {
12490 selection.collapse_to(
12491 movement::start_of_excerpt(
12492 map,
12493 selection.head(),
12494 workspace::searchable::Direction::Prev,
12495 ),
12496 SelectionGoal::None,
12497 )
12498 });
12499 })
12500 }
12501
12502 pub fn move_to_start_of_next_excerpt(
12503 &mut self,
12504 _: &MoveToStartOfNextExcerpt,
12505 window: &mut Window,
12506 cx: &mut Context<Self>,
12507 ) {
12508 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12509 cx.propagate();
12510 return;
12511 }
12512
12513 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12514 s.move_with(|map, selection| {
12515 selection.collapse_to(
12516 movement::start_of_excerpt(
12517 map,
12518 selection.head(),
12519 workspace::searchable::Direction::Next,
12520 ),
12521 SelectionGoal::None,
12522 )
12523 });
12524 })
12525 }
12526
12527 pub fn move_to_end_of_excerpt(
12528 &mut self,
12529 _: &MoveToEndOfExcerpt,
12530 window: &mut Window,
12531 cx: &mut Context<Self>,
12532 ) {
12533 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12534 cx.propagate();
12535 return;
12536 }
12537 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12538 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12539 s.move_with(|map, selection| {
12540 selection.collapse_to(
12541 movement::end_of_excerpt(
12542 map,
12543 selection.head(),
12544 workspace::searchable::Direction::Next,
12545 ),
12546 SelectionGoal::None,
12547 )
12548 });
12549 })
12550 }
12551
12552 pub fn move_to_end_of_previous_excerpt(
12553 &mut self,
12554 _: &MoveToEndOfPreviousExcerpt,
12555 window: &mut Window,
12556 cx: &mut Context<Self>,
12557 ) {
12558 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12559 cx.propagate();
12560 return;
12561 }
12562 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12563 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12564 s.move_with(|map, selection| {
12565 selection.collapse_to(
12566 movement::end_of_excerpt(
12567 map,
12568 selection.head(),
12569 workspace::searchable::Direction::Prev,
12570 ),
12571 SelectionGoal::None,
12572 )
12573 });
12574 })
12575 }
12576
12577 pub fn select_to_start_of_excerpt(
12578 &mut self,
12579 _: &SelectToStartOfExcerpt,
12580 window: &mut Window,
12581 cx: &mut Context<Self>,
12582 ) {
12583 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12584 cx.propagate();
12585 return;
12586 }
12587 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12588 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12589 s.move_heads_with(|map, head, _| {
12590 (
12591 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12592 SelectionGoal::None,
12593 )
12594 });
12595 })
12596 }
12597
12598 pub fn select_to_start_of_next_excerpt(
12599 &mut self,
12600 _: &SelectToStartOfNextExcerpt,
12601 window: &mut Window,
12602 cx: &mut Context<Self>,
12603 ) {
12604 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12605 cx.propagate();
12606 return;
12607 }
12608 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12609 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12610 s.move_heads_with(|map, head, _| {
12611 (
12612 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12613 SelectionGoal::None,
12614 )
12615 });
12616 })
12617 }
12618
12619 pub fn select_to_end_of_excerpt(
12620 &mut self,
12621 _: &SelectToEndOfExcerpt,
12622 window: &mut Window,
12623 cx: &mut Context<Self>,
12624 ) {
12625 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12626 cx.propagate();
12627 return;
12628 }
12629 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12630 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12631 s.move_heads_with(|map, head, _| {
12632 (
12633 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12634 SelectionGoal::None,
12635 )
12636 });
12637 })
12638 }
12639
12640 pub fn select_to_end_of_previous_excerpt(
12641 &mut self,
12642 _: &SelectToEndOfPreviousExcerpt,
12643 window: &mut Window,
12644 cx: &mut Context<Self>,
12645 ) {
12646 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12647 cx.propagate();
12648 return;
12649 }
12650 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12651 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12652 s.move_heads_with(|map, head, _| {
12653 (
12654 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12655 SelectionGoal::None,
12656 )
12657 });
12658 })
12659 }
12660
12661 pub fn move_to_beginning(
12662 &mut self,
12663 _: &MoveToBeginning,
12664 window: &mut Window,
12665 cx: &mut Context<Self>,
12666 ) {
12667 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12668 cx.propagate();
12669 return;
12670 }
12671 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12672 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12673 s.select_ranges(vec![0..0]);
12674 });
12675 }
12676
12677 pub fn select_to_beginning(
12678 &mut self,
12679 _: &SelectToBeginning,
12680 window: &mut Window,
12681 cx: &mut Context<Self>,
12682 ) {
12683 let mut selection = self.selections.last::<Point>(cx);
12684 selection.set_head(Point::zero(), SelectionGoal::None);
12685 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12686 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12687 s.select(vec![selection]);
12688 });
12689 }
12690
12691 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12692 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12693 cx.propagate();
12694 return;
12695 }
12696 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12697 let cursor = self.buffer.read(cx).read(cx).len();
12698 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12699 s.select_ranges(vec![cursor..cursor])
12700 });
12701 }
12702
12703 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12704 self.nav_history = nav_history;
12705 }
12706
12707 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12708 self.nav_history.as_ref()
12709 }
12710
12711 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12712 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12713 }
12714
12715 fn push_to_nav_history(
12716 &mut self,
12717 cursor_anchor: Anchor,
12718 new_position: Option<Point>,
12719 is_deactivate: bool,
12720 cx: &mut Context<Self>,
12721 ) {
12722 if let Some(nav_history) = self.nav_history.as_mut() {
12723 let buffer = self.buffer.read(cx).read(cx);
12724 let cursor_position = cursor_anchor.to_point(&buffer);
12725 let scroll_state = self.scroll_manager.anchor();
12726 let scroll_top_row = scroll_state.top_row(&buffer);
12727 drop(buffer);
12728
12729 if let Some(new_position) = new_position {
12730 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12731 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12732 return;
12733 }
12734 }
12735
12736 nav_history.push(
12737 Some(NavigationData {
12738 cursor_anchor,
12739 cursor_position,
12740 scroll_anchor: scroll_state,
12741 scroll_top_row,
12742 }),
12743 cx,
12744 );
12745 cx.emit(EditorEvent::PushedToNavHistory {
12746 anchor: cursor_anchor,
12747 is_deactivate,
12748 })
12749 }
12750 }
12751
12752 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12753 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12754 let buffer = self.buffer.read(cx).snapshot(cx);
12755 let mut selection = self.selections.first::<usize>(cx);
12756 selection.set_head(buffer.len(), SelectionGoal::None);
12757 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12758 s.select(vec![selection]);
12759 });
12760 }
12761
12762 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12763 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12764 let end = self.buffer.read(cx).read(cx).len();
12765 self.change_selections(None, window, cx, |s| {
12766 s.select_ranges(vec![0..end]);
12767 });
12768 }
12769
12770 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12771 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12772 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12773 let mut selections = self.selections.all::<Point>(cx);
12774 let max_point = display_map.buffer_snapshot.max_point();
12775 for selection in &mut selections {
12776 let rows = selection.spanned_rows(true, &display_map);
12777 selection.start = Point::new(rows.start.0, 0);
12778 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12779 selection.reversed = false;
12780 }
12781 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12782 s.select(selections);
12783 });
12784 }
12785
12786 pub fn split_selection_into_lines(
12787 &mut self,
12788 _: &SplitSelectionIntoLines,
12789 window: &mut Window,
12790 cx: &mut Context<Self>,
12791 ) {
12792 let selections = self
12793 .selections
12794 .all::<Point>(cx)
12795 .into_iter()
12796 .map(|selection| selection.start..selection.end)
12797 .collect::<Vec<_>>();
12798 self.unfold_ranges(&selections, true, true, cx);
12799
12800 let mut new_selection_ranges = Vec::new();
12801 {
12802 let buffer = self.buffer.read(cx).read(cx);
12803 for selection in selections {
12804 for row in selection.start.row..selection.end.row {
12805 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12806 new_selection_ranges.push(cursor..cursor);
12807 }
12808
12809 let is_multiline_selection = selection.start.row != selection.end.row;
12810 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12811 // so this action feels more ergonomic when paired with other selection operations
12812 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12813 if !should_skip_last {
12814 new_selection_ranges.push(selection.end..selection.end);
12815 }
12816 }
12817 }
12818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12819 s.select_ranges(new_selection_ranges);
12820 });
12821 }
12822
12823 pub fn add_selection_above(
12824 &mut self,
12825 _: &AddSelectionAbove,
12826 window: &mut Window,
12827 cx: &mut Context<Self>,
12828 ) {
12829 self.add_selection(true, window, cx);
12830 }
12831
12832 pub fn add_selection_below(
12833 &mut self,
12834 _: &AddSelectionBelow,
12835 window: &mut Window,
12836 cx: &mut Context<Self>,
12837 ) {
12838 self.add_selection(false, window, cx);
12839 }
12840
12841 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12842 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12843
12844 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12845 let all_selections = self.selections.all::<Point>(cx);
12846 let text_layout_details = self.text_layout_details(window);
12847
12848 let (mut columnar_selections, new_selections_to_columnarize) = {
12849 if let Some(state) = self.add_selections_state.as_ref() {
12850 let columnar_selection_ids: HashSet<_> = state
12851 .groups
12852 .iter()
12853 .flat_map(|group| group.stack.iter())
12854 .copied()
12855 .collect();
12856
12857 all_selections
12858 .into_iter()
12859 .partition(|s| columnar_selection_ids.contains(&s.id))
12860 } else {
12861 (Vec::new(), all_selections)
12862 }
12863 };
12864
12865 let mut state = self
12866 .add_selections_state
12867 .take()
12868 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12869
12870 for selection in new_selections_to_columnarize {
12871 let range = selection.display_range(&display_map).sorted();
12872 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12873 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12874 let positions = start_x.min(end_x)..start_x.max(end_x);
12875 let mut stack = Vec::new();
12876 for row in range.start.row().0..=range.end.row().0 {
12877 if let Some(selection) = self.selections.build_columnar_selection(
12878 &display_map,
12879 DisplayRow(row),
12880 &positions,
12881 selection.reversed,
12882 &text_layout_details,
12883 ) {
12884 stack.push(selection.id);
12885 columnar_selections.push(selection);
12886 }
12887 }
12888 if !stack.is_empty() {
12889 if above {
12890 stack.reverse();
12891 }
12892 state.groups.push(AddSelectionsGroup { above, stack });
12893 }
12894 }
12895
12896 let mut final_selections = Vec::new();
12897 let end_row = if above {
12898 DisplayRow(0)
12899 } else {
12900 display_map.max_point().row()
12901 };
12902
12903 let mut last_added_item_per_group = HashMap::default();
12904 for group in state.groups.iter_mut() {
12905 if let Some(last_id) = group.stack.last() {
12906 last_added_item_per_group.insert(*last_id, group);
12907 }
12908 }
12909
12910 for selection in columnar_selections {
12911 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12912 if above == group.above {
12913 let range = selection.display_range(&display_map).sorted();
12914 debug_assert_eq!(range.start.row(), range.end.row());
12915 let mut row = range.start.row();
12916 let positions =
12917 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12918 px(start)..px(end)
12919 } else {
12920 let start_x =
12921 display_map.x_for_display_point(range.start, &text_layout_details);
12922 let end_x =
12923 display_map.x_for_display_point(range.end, &text_layout_details);
12924 start_x.min(end_x)..start_x.max(end_x)
12925 };
12926
12927 let mut maybe_new_selection = None;
12928 while row != end_row {
12929 if above {
12930 row.0 -= 1;
12931 } else {
12932 row.0 += 1;
12933 }
12934 if let Some(new_selection) = self.selections.build_columnar_selection(
12935 &display_map,
12936 row,
12937 &positions,
12938 selection.reversed,
12939 &text_layout_details,
12940 ) {
12941 maybe_new_selection = Some(new_selection);
12942 break;
12943 }
12944 }
12945
12946 if let Some(new_selection) = maybe_new_selection {
12947 group.stack.push(new_selection.id);
12948 if above {
12949 final_selections.push(new_selection);
12950 final_selections.push(selection);
12951 } else {
12952 final_selections.push(selection);
12953 final_selections.push(new_selection);
12954 }
12955 } else {
12956 final_selections.push(selection);
12957 }
12958 } else {
12959 group.stack.pop();
12960 }
12961 } else {
12962 final_selections.push(selection);
12963 }
12964 }
12965
12966 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12967 s.select(final_selections);
12968 });
12969
12970 let final_selection_ids: HashSet<_> = self
12971 .selections
12972 .all::<Point>(cx)
12973 .iter()
12974 .map(|s| s.id)
12975 .collect();
12976 state.groups.retain_mut(|group| {
12977 // selections might get merged above so we remove invalid items from stacks
12978 group.stack.retain(|id| final_selection_ids.contains(id));
12979
12980 // single selection in stack can be treated as initial state
12981 group.stack.len() > 1
12982 });
12983
12984 if !state.groups.is_empty() {
12985 self.add_selections_state = Some(state);
12986 }
12987 }
12988
12989 fn select_match_ranges(
12990 &mut self,
12991 range: Range<usize>,
12992 reversed: bool,
12993 replace_newest: bool,
12994 auto_scroll: Option<Autoscroll>,
12995 window: &mut Window,
12996 cx: &mut Context<Editor>,
12997 ) {
12998 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12999 self.change_selections(auto_scroll, window, cx, |s| {
13000 if replace_newest {
13001 s.delete(s.newest_anchor().id);
13002 }
13003 if reversed {
13004 s.insert_range(range.end..range.start);
13005 } else {
13006 s.insert_range(range);
13007 }
13008 });
13009 }
13010
13011 pub fn select_next_match_internal(
13012 &mut self,
13013 display_map: &DisplaySnapshot,
13014 replace_newest: bool,
13015 autoscroll: Option<Autoscroll>,
13016 window: &mut Window,
13017 cx: &mut Context<Self>,
13018 ) -> Result<()> {
13019 let buffer = &display_map.buffer_snapshot;
13020 let mut selections = self.selections.all::<usize>(cx);
13021 if let Some(mut select_next_state) = self.select_next_state.take() {
13022 let query = &select_next_state.query;
13023 if !select_next_state.done {
13024 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13025 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13026 let mut next_selected_range = None;
13027
13028 let bytes_after_last_selection =
13029 buffer.bytes_in_range(last_selection.end..buffer.len());
13030 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13031 let query_matches = query
13032 .stream_find_iter(bytes_after_last_selection)
13033 .map(|result| (last_selection.end, result))
13034 .chain(
13035 query
13036 .stream_find_iter(bytes_before_first_selection)
13037 .map(|result| (0, result)),
13038 );
13039
13040 for (start_offset, query_match) in query_matches {
13041 let query_match = query_match.unwrap(); // can only fail due to I/O
13042 let offset_range =
13043 start_offset + query_match.start()..start_offset + query_match.end();
13044 let display_range = offset_range.start.to_display_point(display_map)
13045 ..offset_range.end.to_display_point(display_map);
13046
13047 if !select_next_state.wordwise
13048 || (!movement::is_inside_word(display_map, display_range.start)
13049 && !movement::is_inside_word(display_map, display_range.end))
13050 {
13051 // TODO: This is n^2, because we might check all the selections
13052 if !selections
13053 .iter()
13054 .any(|selection| selection.range().overlaps(&offset_range))
13055 {
13056 next_selected_range = Some(offset_range);
13057 break;
13058 }
13059 }
13060 }
13061
13062 if let Some(next_selected_range) = next_selected_range {
13063 self.select_match_ranges(
13064 next_selected_range,
13065 last_selection.reversed,
13066 replace_newest,
13067 autoscroll,
13068 window,
13069 cx,
13070 );
13071 } else {
13072 select_next_state.done = true;
13073 }
13074 }
13075
13076 self.select_next_state = Some(select_next_state);
13077 } else {
13078 let mut only_carets = true;
13079 let mut same_text_selected = true;
13080 let mut selected_text = None;
13081
13082 let mut selections_iter = selections.iter().peekable();
13083 while let Some(selection) = selections_iter.next() {
13084 if selection.start != selection.end {
13085 only_carets = false;
13086 }
13087
13088 if same_text_selected {
13089 if selected_text.is_none() {
13090 selected_text =
13091 Some(buffer.text_for_range(selection.range()).collect::<String>());
13092 }
13093
13094 if let Some(next_selection) = selections_iter.peek() {
13095 if next_selection.range().len() == selection.range().len() {
13096 let next_selected_text = buffer
13097 .text_for_range(next_selection.range())
13098 .collect::<String>();
13099 if Some(next_selected_text) != selected_text {
13100 same_text_selected = false;
13101 selected_text = None;
13102 }
13103 } else {
13104 same_text_selected = false;
13105 selected_text = None;
13106 }
13107 }
13108 }
13109 }
13110
13111 if only_carets {
13112 for selection in &mut selections {
13113 let word_range = movement::surrounding_word(
13114 display_map,
13115 selection.start.to_display_point(display_map),
13116 );
13117 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13118 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13119 selection.goal = SelectionGoal::None;
13120 selection.reversed = false;
13121 self.select_match_ranges(
13122 selection.start..selection.end,
13123 selection.reversed,
13124 replace_newest,
13125 autoscroll,
13126 window,
13127 cx,
13128 );
13129 }
13130
13131 if selections.len() == 1 {
13132 let selection = selections
13133 .last()
13134 .expect("ensured that there's only one selection");
13135 let query = buffer
13136 .text_for_range(selection.start..selection.end)
13137 .collect::<String>();
13138 let is_empty = query.is_empty();
13139 let select_state = SelectNextState {
13140 query: AhoCorasick::new(&[query])?,
13141 wordwise: true,
13142 done: is_empty,
13143 };
13144 self.select_next_state = Some(select_state);
13145 } else {
13146 self.select_next_state = None;
13147 }
13148 } else if let Some(selected_text) = selected_text {
13149 self.select_next_state = Some(SelectNextState {
13150 query: AhoCorasick::new(&[selected_text])?,
13151 wordwise: false,
13152 done: false,
13153 });
13154 self.select_next_match_internal(
13155 display_map,
13156 replace_newest,
13157 autoscroll,
13158 window,
13159 cx,
13160 )?;
13161 }
13162 }
13163 Ok(())
13164 }
13165
13166 pub fn select_all_matches(
13167 &mut self,
13168 _action: &SelectAllMatches,
13169 window: &mut Window,
13170 cx: &mut Context<Self>,
13171 ) -> Result<()> {
13172 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13173
13174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13175
13176 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13177 let Some(select_next_state) = self.select_next_state.as_mut() else {
13178 return Ok(());
13179 };
13180 if select_next_state.done {
13181 return Ok(());
13182 }
13183
13184 let mut new_selections = Vec::new();
13185
13186 let reversed = self.selections.oldest::<usize>(cx).reversed;
13187 let buffer = &display_map.buffer_snapshot;
13188 let query_matches = select_next_state
13189 .query
13190 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13191
13192 for query_match in query_matches.into_iter() {
13193 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13194 let offset_range = if reversed {
13195 query_match.end()..query_match.start()
13196 } else {
13197 query_match.start()..query_match.end()
13198 };
13199 let display_range = offset_range.start.to_display_point(&display_map)
13200 ..offset_range.end.to_display_point(&display_map);
13201
13202 if !select_next_state.wordwise
13203 || (!movement::is_inside_word(&display_map, display_range.start)
13204 && !movement::is_inside_word(&display_map, display_range.end))
13205 {
13206 new_selections.push(offset_range.start..offset_range.end);
13207 }
13208 }
13209
13210 select_next_state.done = true;
13211 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13212 self.change_selections(None, window, cx, |selections| {
13213 selections.select_ranges(new_selections)
13214 });
13215
13216 Ok(())
13217 }
13218
13219 pub fn select_next(
13220 &mut self,
13221 action: &SelectNext,
13222 window: &mut Window,
13223 cx: &mut Context<Self>,
13224 ) -> Result<()> {
13225 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13226 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13227 self.select_next_match_internal(
13228 &display_map,
13229 action.replace_newest,
13230 Some(Autoscroll::newest()),
13231 window,
13232 cx,
13233 )?;
13234 Ok(())
13235 }
13236
13237 pub fn select_previous(
13238 &mut self,
13239 action: &SelectPrevious,
13240 window: &mut Window,
13241 cx: &mut Context<Self>,
13242 ) -> Result<()> {
13243 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13244 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13245 let buffer = &display_map.buffer_snapshot;
13246 let mut selections = self.selections.all::<usize>(cx);
13247 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13248 let query = &select_prev_state.query;
13249 if !select_prev_state.done {
13250 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13251 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13252 let mut next_selected_range = None;
13253 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13254 let bytes_before_last_selection =
13255 buffer.reversed_bytes_in_range(0..last_selection.start);
13256 let bytes_after_first_selection =
13257 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13258 let query_matches = query
13259 .stream_find_iter(bytes_before_last_selection)
13260 .map(|result| (last_selection.start, result))
13261 .chain(
13262 query
13263 .stream_find_iter(bytes_after_first_selection)
13264 .map(|result| (buffer.len(), result)),
13265 );
13266 for (end_offset, query_match) in query_matches {
13267 let query_match = query_match.unwrap(); // can only fail due to I/O
13268 let offset_range =
13269 end_offset - query_match.end()..end_offset - query_match.start();
13270 let display_range = offset_range.start.to_display_point(&display_map)
13271 ..offset_range.end.to_display_point(&display_map);
13272
13273 if !select_prev_state.wordwise
13274 || (!movement::is_inside_word(&display_map, display_range.start)
13275 && !movement::is_inside_word(&display_map, display_range.end))
13276 {
13277 next_selected_range = Some(offset_range);
13278 break;
13279 }
13280 }
13281
13282 if let Some(next_selected_range) = next_selected_range {
13283 self.select_match_ranges(
13284 next_selected_range,
13285 last_selection.reversed,
13286 action.replace_newest,
13287 Some(Autoscroll::newest()),
13288 window,
13289 cx,
13290 );
13291 } else {
13292 select_prev_state.done = true;
13293 }
13294 }
13295
13296 self.select_prev_state = Some(select_prev_state);
13297 } else {
13298 let mut only_carets = true;
13299 let mut same_text_selected = true;
13300 let mut selected_text = None;
13301
13302 let mut selections_iter = selections.iter().peekable();
13303 while let Some(selection) = selections_iter.next() {
13304 if selection.start != selection.end {
13305 only_carets = false;
13306 }
13307
13308 if same_text_selected {
13309 if selected_text.is_none() {
13310 selected_text =
13311 Some(buffer.text_for_range(selection.range()).collect::<String>());
13312 }
13313
13314 if let Some(next_selection) = selections_iter.peek() {
13315 if next_selection.range().len() == selection.range().len() {
13316 let next_selected_text = buffer
13317 .text_for_range(next_selection.range())
13318 .collect::<String>();
13319 if Some(next_selected_text) != selected_text {
13320 same_text_selected = false;
13321 selected_text = None;
13322 }
13323 } else {
13324 same_text_selected = false;
13325 selected_text = None;
13326 }
13327 }
13328 }
13329 }
13330
13331 if only_carets {
13332 for selection in &mut selections {
13333 let word_range = movement::surrounding_word(
13334 &display_map,
13335 selection.start.to_display_point(&display_map),
13336 );
13337 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13338 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13339 selection.goal = SelectionGoal::None;
13340 selection.reversed = false;
13341 self.select_match_ranges(
13342 selection.start..selection.end,
13343 selection.reversed,
13344 action.replace_newest,
13345 Some(Autoscroll::newest()),
13346 window,
13347 cx,
13348 );
13349 }
13350 if selections.len() == 1 {
13351 let selection = selections
13352 .last()
13353 .expect("ensured that there's only one selection");
13354 let query = buffer
13355 .text_for_range(selection.start..selection.end)
13356 .collect::<String>();
13357 let is_empty = query.is_empty();
13358 let select_state = SelectNextState {
13359 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13360 wordwise: true,
13361 done: is_empty,
13362 };
13363 self.select_prev_state = Some(select_state);
13364 } else {
13365 self.select_prev_state = None;
13366 }
13367 } else if let Some(selected_text) = selected_text {
13368 self.select_prev_state = Some(SelectNextState {
13369 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13370 wordwise: false,
13371 done: false,
13372 });
13373 self.select_previous(action, window, cx)?;
13374 }
13375 }
13376 Ok(())
13377 }
13378
13379 pub fn find_next_match(
13380 &mut self,
13381 _: &FindNextMatch,
13382 window: &mut Window,
13383 cx: &mut Context<Self>,
13384 ) -> Result<()> {
13385 let selections = self.selections.disjoint_anchors();
13386 match selections.first() {
13387 Some(first) if selections.len() >= 2 => {
13388 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13389 s.select_ranges([first.range()]);
13390 });
13391 }
13392 _ => self.select_next(
13393 &SelectNext {
13394 replace_newest: true,
13395 },
13396 window,
13397 cx,
13398 )?,
13399 }
13400 Ok(())
13401 }
13402
13403 pub fn find_previous_match(
13404 &mut self,
13405 _: &FindPreviousMatch,
13406 window: &mut Window,
13407 cx: &mut Context<Self>,
13408 ) -> Result<()> {
13409 let selections = self.selections.disjoint_anchors();
13410 match selections.last() {
13411 Some(last) if selections.len() >= 2 => {
13412 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13413 s.select_ranges([last.range()]);
13414 });
13415 }
13416 _ => self.select_previous(
13417 &SelectPrevious {
13418 replace_newest: true,
13419 },
13420 window,
13421 cx,
13422 )?,
13423 }
13424 Ok(())
13425 }
13426
13427 pub fn toggle_comments(
13428 &mut self,
13429 action: &ToggleComments,
13430 window: &mut Window,
13431 cx: &mut Context<Self>,
13432 ) {
13433 if self.read_only(cx) {
13434 return;
13435 }
13436 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13437 let text_layout_details = &self.text_layout_details(window);
13438 self.transact(window, cx, |this, window, cx| {
13439 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13440 let mut edits = Vec::new();
13441 let mut selection_edit_ranges = Vec::new();
13442 let mut last_toggled_row = None;
13443 let snapshot = this.buffer.read(cx).read(cx);
13444 let empty_str: Arc<str> = Arc::default();
13445 let mut suffixes_inserted = Vec::new();
13446 let ignore_indent = action.ignore_indent;
13447
13448 fn comment_prefix_range(
13449 snapshot: &MultiBufferSnapshot,
13450 row: MultiBufferRow,
13451 comment_prefix: &str,
13452 comment_prefix_whitespace: &str,
13453 ignore_indent: bool,
13454 ) -> Range<Point> {
13455 let indent_size = if ignore_indent {
13456 0
13457 } else {
13458 snapshot.indent_size_for_line(row).len
13459 };
13460
13461 let start = Point::new(row.0, indent_size);
13462
13463 let mut line_bytes = snapshot
13464 .bytes_in_range(start..snapshot.max_point())
13465 .flatten()
13466 .copied();
13467
13468 // If this line currently begins with the line comment prefix, then record
13469 // the range containing the prefix.
13470 if line_bytes
13471 .by_ref()
13472 .take(comment_prefix.len())
13473 .eq(comment_prefix.bytes())
13474 {
13475 // Include any whitespace that matches the comment prefix.
13476 let matching_whitespace_len = line_bytes
13477 .zip(comment_prefix_whitespace.bytes())
13478 .take_while(|(a, b)| a == b)
13479 .count() as u32;
13480 let end = Point::new(
13481 start.row,
13482 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13483 );
13484 start..end
13485 } else {
13486 start..start
13487 }
13488 }
13489
13490 fn comment_suffix_range(
13491 snapshot: &MultiBufferSnapshot,
13492 row: MultiBufferRow,
13493 comment_suffix: &str,
13494 comment_suffix_has_leading_space: bool,
13495 ) -> Range<Point> {
13496 let end = Point::new(row.0, snapshot.line_len(row));
13497 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13498
13499 let mut line_end_bytes = snapshot
13500 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13501 .flatten()
13502 .copied();
13503
13504 let leading_space_len = if suffix_start_column > 0
13505 && line_end_bytes.next() == Some(b' ')
13506 && comment_suffix_has_leading_space
13507 {
13508 1
13509 } else {
13510 0
13511 };
13512
13513 // If this line currently begins with the line comment prefix, then record
13514 // the range containing the prefix.
13515 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13516 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13517 start..end
13518 } else {
13519 end..end
13520 }
13521 }
13522
13523 // TODO: Handle selections that cross excerpts
13524 for selection in &mut selections {
13525 let start_column = snapshot
13526 .indent_size_for_line(MultiBufferRow(selection.start.row))
13527 .len;
13528 let language = if let Some(language) =
13529 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13530 {
13531 language
13532 } else {
13533 continue;
13534 };
13535
13536 selection_edit_ranges.clear();
13537
13538 // If multiple selections contain a given row, avoid processing that
13539 // row more than once.
13540 let mut start_row = MultiBufferRow(selection.start.row);
13541 if last_toggled_row == Some(start_row) {
13542 start_row = start_row.next_row();
13543 }
13544 let end_row =
13545 if selection.end.row > selection.start.row && selection.end.column == 0 {
13546 MultiBufferRow(selection.end.row - 1)
13547 } else {
13548 MultiBufferRow(selection.end.row)
13549 };
13550 last_toggled_row = Some(end_row);
13551
13552 if start_row > end_row {
13553 continue;
13554 }
13555
13556 // If the language has line comments, toggle those.
13557 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13558
13559 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13560 if ignore_indent {
13561 full_comment_prefixes = full_comment_prefixes
13562 .into_iter()
13563 .map(|s| Arc::from(s.trim_end()))
13564 .collect();
13565 }
13566
13567 if !full_comment_prefixes.is_empty() {
13568 let first_prefix = full_comment_prefixes
13569 .first()
13570 .expect("prefixes is non-empty");
13571 let prefix_trimmed_lengths = full_comment_prefixes
13572 .iter()
13573 .map(|p| p.trim_end_matches(' ').len())
13574 .collect::<SmallVec<[usize; 4]>>();
13575
13576 let mut all_selection_lines_are_comments = true;
13577
13578 for row in start_row.0..=end_row.0 {
13579 let row = MultiBufferRow(row);
13580 if start_row < end_row && snapshot.is_line_blank(row) {
13581 continue;
13582 }
13583
13584 let prefix_range = full_comment_prefixes
13585 .iter()
13586 .zip(prefix_trimmed_lengths.iter().copied())
13587 .map(|(prefix, trimmed_prefix_len)| {
13588 comment_prefix_range(
13589 snapshot.deref(),
13590 row,
13591 &prefix[..trimmed_prefix_len],
13592 &prefix[trimmed_prefix_len..],
13593 ignore_indent,
13594 )
13595 })
13596 .max_by_key(|range| range.end.column - range.start.column)
13597 .expect("prefixes is non-empty");
13598
13599 if prefix_range.is_empty() {
13600 all_selection_lines_are_comments = false;
13601 }
13602
13603 selection_edit_ranges.push(prefix_range);
13604 }
13605
13606 if all_selection_lines_are_comments {
13607 edits.extend(
13608 selection_edit_ranges
13609 .iter()
13610 .cloned()
13611 .map(|range| (range, empty_str.clone())),
13612 );
13613 } else {
13614 let min_column = selection_edit_ranges
13615 .iter()
13616 .map(|range| range.start.column)
13617 .min()
13618 .unwrap_or(0);
13619 edits.extend(selection_edit_ranges.iter().map(|range| {
13620 let position = Point::new(range.start.row, min_column);
13621 (position..position, first_prefix.clone())
13622 }));
13623 }
13624 } else if let Some((full_comment_prefix, comment_suffix)) =
13625 language.block_comment_delimiters()
13626 {
13627 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13628 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13629 let prefix_range = comment_prefix_range(
13630 snapshot.deref(),
13631 start_row,
13632 comment_prefix,
13633 comment_prefix_whitespace,
13634 ignore_indent,
13635 );
13636 let suffix_range = comment_suffix_range(
13637 snapshot.deref(),
13638 end_row,
13639 comment_suffix.trim_start_matches(' '),
13640 comment_suffix.starts_with(' '),
13641 );
13642
13643 if prefix_range.is_empty() || suffix_range.is_empty() {
13644 edits.push((
13645 prefix_range.start..prefix_range.start,
13646 full_comment_prefix.clone(),
13647 ));
13648 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13649 suffixes_inserted.push((end_row, comment_suffix.len()));
13650 } else {
13651 edits.push((prefix_range, empty_str.clone()));
13652 edits.push((suffix_range, empty_str.clone()));
13653 }
13654 } else {
13655 continue;
13656 }
13657 }
13658
13659 drop(snapshot);
13660 this.buffer.update(cx, |buffer, cx| {
13661 buffer.edit(edits, None, cx);
13662 });
13663
13664 // Adjust selections so that they end before any comment suffixes that
13665 // were inserted.
13666 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13667 let mut selections = this.selections.all::<Point>(cx);
13668 let snapshot = this.buffer.read(cx).read(cx);
13669 for selection in &mut selections {
13670 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13671 match row.cmp(&MultiBufferRow(selection.end.row)) {
13672 Ordering::Less => {
13673 suffixes_inserted.next();
13674 continue;
13675 }
13676 Ordering::Greater => break,
13677 Ordering::Equal => {
13678 if selection.end.column == snapshot.line_len(row) {
13679 if selection.is_empty() {
13680 selection.start.column -= suffix_len as u32;
13681 }
13682 selection.end.column -= suffix_len as u32;
13683 }
13684 break;
13685 }
13686 }
13687 }
13688 }
13689
13690 drop(snapshot);
13691 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13692 s.select(selections)
13693 });
13694
13695 let selections = this.selections.all::<Point>(cx);
13696 let selections_on_single_row = selections.windows(2).all(|selections| {
13697 selections[0].start.row == selections[1].start.row
13698 && selections[0].end.row == selections[1].end.row
13699 && selections[0].start.row == selections[0].end.row
13700 });
13701 let selections_selecting = selections
13702 .iter()
13703 .any(|selection| selection.start != selection.end);
13704 let advance_downwards = action.advance_downwards
13705 && selections_on_single_row
13706 && !selections_selecting
13707 && !matches!(this.mode, EditorMode::SingleLine { .. });
13708
13709 if advance_downwards {
13710 let snapshot = this.buffer.read(cx).snapshot(cx);
13711
13712 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13713 s.move_cursors_with(|display_snapshot, display_point, _| {
13714 let mut point = display_point.to_point(display_snapshot);
13715 point.row += 1;
13716 point = snapshot.clip_point(point, Bias::Left);
13717 let display_point = point.to_display_point(display_snapshot);
13718 let goal = SelectionGoal::HorizontalPosition(
13719 display_snapshot
13720 .x_for_display_point(display_point, text_layout_details)
13721 .into(),
13722 );
13723 (display_point, goal)
13724 })
13725 });
13726 }
13727 });
13728 }
13729
13730 pub fn select_enclosing_symbol(
13731 &mut self,
13732 _: &SelectEnclosingSymbol,
13733 window: &mut Window,
13734 cx: &mut Context<Self>,
13735 ) {
13736 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13737
13738 let buffer = self.buffer.read(cx).snapshot(cx);
13739 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13740
13741 fn update_selection(
13742 selection: &Selection<usize>,
13743 buffer_snap: &MultiBufferSnapshot,
13744 ) -> Option<Selection<usize>> {
13745 let cursor = selection.head();
13746 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13747 for symbol in symbols.iter().rev() {
13748 let start = symbol.range.start.to_offset(buffer_snap);
13749 let end = symbol.range.end.to_offset(buffer_snap);
13750 let new_range = start..end;
13751 if start < selection.start || end > selection.end {
13752 return Some(Selection {
13753 id: selection.id,
13754 start: new_range.start,
13755 end: new_range.end,
13756 goal: SelectionGoal::None,
13757 reversed: selection.reversed,
13758 });
13759 }
13760 }
13761 None
13762 }
13763
13764 let mut selected_larger_symbol = false;
13765 let new_selections = old_selections
13766 .iter()
13767 .map(|selection| match update_selection(selection, &buffer) {
13768 Some(new_selection) => {
13769 if new_selection.range() != selection.range() {
13770 selected_larger_symbol = true;
13771 }
13772 new_selection
13773 }
13774 None => selection.clone(),
13775 })
13776 .collect::<Vec<_>>();
13777
13778 if selected_larger_symbol {
13779 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13780 s.select(new_selections);
13781 });
13782 }
13783 }
13784
13785 pub fn select_larger_syntax_node(
13786 &mut self,
13787 _: &SelectLargerSyntaxNode,
13788 window: &mut Window,
13789 cx: &mut Context<Self>,
13790 ) {
13791 let Some(visible_row_count) = self.visible_row_count() else {
13792 return;
13793 };
13794 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13795 if old_selections.is_empty() {
13796 return;
13797 }
13798
13799 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13800
13801 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13802 let buffer = self.buffer.read(cx).snapshot(cx);
13803
13804 let mut selected_larger_node = false;
13805 let mut new_selections = old_selections
13806 .iter()
13807 .map(|selection| {
13808 let old_range = selection.start..selection.end;
13809
13810 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13811 // manually select word at selection
13812 if ["string_content", "inline"].contains(&node.kind()) {
13813 let word_range = {
13814 let display_point = buffer
13815 .offset_to_point(old_range.start)
13816 .to_display_point(&display_map);
13817 let Range { start, end } =
13818 movement::surrounding_word(&display_map, display_point);
13819 start.to_point(&display_map).to_offset(&buffer)
13820 ..end.to_point(&display_map).to_offset(&buffer)
13821 };
13822 // ignore if word is already selected
13823 if !word_range.is_empty() && old_range != word_range {
13824 let last_word_range = {
13825 let display_point = buffer
13826 .offset_to_point(old_range.end)
13827 .to_display_point(&display_map);
13828 let Range { start, end } =
13829 movement::surrounding_word(&display_map, display_point);
13830 start.to_point(&display_map).to_offset(&buffer)
13831 ..end.to_point(&display_map).to_offset(&buffer)
13832 };
13833 // only select word if start and end point belongs to same word
13834 if word_range == last_word_range {
13835 selected_larger_node = true;
13836 return Selection {
13837 id: selection.id,
13838 start: word_range.start,
13839 end: word_range.end,
13840 goal: SelectionGoal::None,
13841 reversed: selection.reversed,
13842 };
13843 }
13844 }
13845 }
13846 }
13847
13848 let mut new_range = old_range.clone();
13849 while let Some((_node, containing_range)) =
13850 buffer.syntax_ancestor(new_range.clone())
13851 {
13852 new_range = match containing_range {
13853 MultiOrSingleBufferOffsetRange::Single(_) => break,
13854 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13855 };
13856 if !display_map.intersects_fold(new_range.start)
13857 && !display_map.intersects_fold(new_range.end)
13858 {
13859 break;
13860 }
13861 }
13862
13863 selected_larger_node |= new_range != old_range;
13864 Selection {
13865 id: selection.id,
13866 start: new_range.start,
13867 end: new_range.end,
13868 goal: SelectionGoal::None,
13869 reversed: selection.reversed,
13870 }
13871 })
13872 .collect::<Vec<_>>();
13873
13874 if !selected_larger_node {
13875 return; // don't put this call in the history
13876 }
13877
13878 // scroll based on transformation done to the last selection created by the user
13879 let (last_old, last_new) = old_selections
13880 .last()
13881 .zip(new_selections.last().cloned())
13882 .expect("old_selections isn't empty");
13883
13884 // revert selection
13885 let is_selection_reversed = {
13886 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13887 new_selections.last_mut().expect("checked above").reversed =
13888 should_newest_selection_be_reversed;
13889 should_newest_selection_be_reversed
13890 };
13891
13892 if selected_larger_node {
13893 self.select_syntax_node_history.disable_clearing = true;
13894 self.change_selections(None, window, cx, |s| {
13895 s.select(new_selections.clone());
13896 });
13897 self.select_syntax_node_history.disable_clearing = false;
13898 }
13899
13900 let start_row = last_new.start.to_display_point(&display_map).row().0;
13901 let end_row = last_new.end.to_display_point(&display_map).row().0;
13902 let selection_height = end_row - start_row + 1;
13903 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13904
13905 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13906 let scroll_behavior = if fits_on_the_screen {
13907 self.request_autoscroll(Autoscroll::fit(), cx);
13908 SelectSyntaxNodeScrollBehavior::FitSelection
13909 } else if is_selection_reversed {
13910 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13911 SelectSyntaxNodeScrollBehavior::CursorTop
13912 } else {
13913 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13914 SelectSyntaxNodeScrollBehavior::CursorBottom
13915 };
13916
13917 self.select_syntax_node_history.push((
13918 old_selections,
13919 scroll_behavior,
13920 is_selection_reversed,
13921 ));
13922 }
13923
13924 pub fn select_smaller_syntax_node(
13925 &mut self,
13926 _: &SelectSmallerSyntaxNode,
13927 window: &mut Window,
13928 cx: &mut Context<Self>,
13929 ) {
13930 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13931
13932 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13933 self.select_syntax_node_history.pop()
13934 {
13935 if let Some(selection) = selections.last_mut() {
13936 selection.reversed = is_selection_reversed;
13937 }
13938
13939 self.select_syntax_node_history.disable_clearing = true;
13940 self.change_selections(None, window, cx, |s| {
13941 s.select(selections.to_vec());
13942 });
13943 self.select_syntax_node_history.disable_clearing = false;
13944
13945 match scroll_behavior {
13946 SelectSyntaxNodeScrollBehavior::CursorTop => {
13947 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13948 }
13949 SelectSyntaxNodeScrollBehavior::FitSelection => {
13950 self.request_autoscroll(Autoscroll::fit(), cx);
13951 }
13952 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13953 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13954 }
13955 }
13956 }
13957 }
13958
13959 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13960 if !EditorSettings::get_global(cx).gutter.runnables {
13961 self.clear_tasks();
13962 return Task::ready(());
13963 }
13964 let project = self.project.as_ref().map(Entity::downgrade);
13965 let task_sources = self.lsp_task_sources(cx);
13966 let multi_buffer = self.buffer.downgrade();
13967 cx.spawn_in(window, async move |editor, cx| {
13968 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13969 let Some(project) = project.and_then(|p| p.upgrade()) else {
13970 return;
13971 };
13972 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13973 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13974 }) else {
13975 return;
13976 };
13977
13978 let hide_runnables = project
13979 .update(cx, |project, cx| {
13980 // Do not display any test indicators in non-dev server remote projects.
13981 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13982 })
13983 .unwrap_or(true);
13984 if hide_runnables {
13985 return;
13986 }
13987 let new_rows =
13988 cx.background_spawn({
13989 let snapshot = display_snapshot.clone();
13990 async move {
13991 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13992 }
13993 })
13994 .await;
13995 let Ok(lsp_tasks) =
13996 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13997 else {
13998 return;
13999 };
14000 let lsp_tasks = lsp_tasks.await;
14001
14002 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14003 lsp_tasks
14004 .into_iter()
14005 .flat_map(|(kind, tasks)| {
14006 tasks.into_iter().filter_map(move |(location, task)| {
14007 Some((kind.clone(), location?, task))
14008 })
14009 })
14010 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14011 let buffer = location.target.buffer;
14012 let buffer_snapshot = buffer.read(cx).snapshot();
14013 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14014 |(excerpt_id, snapshot, _)| {
14015 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14016 display_snapshot
14017 .buffer_snapshot
14018 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14019 } else {
14020 None
14021 }
14022 },
14023 );
14024 if let Some(offset) = offset {
14025 let task_buffer_range =
14026 location.target.range.to_point(&buffer_snapshot);
14027 let context_buffer_range =
14028 task_buffer_range.to_offset(&buffer_snapshot);
14029 let context_range = BufferOffset(context_buffer_range.start)
14030 ..BufferOffset(context_buffer_range.end);
14031
14032 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14033 .or_insert_with(|| RunnableTasks {
14034 templates: Vec::new(),
14035 offset,
14036 column: task_buffer_range.start.column,
14037 extra_variables: HashMap::default(),
14038 context_range,
14039 })
14040 .templates
14041 .push((kind, task.original_task().clone()));
14042 }
14043
14044 acc
14045 })
14046 }) else {
14047 return;
14048 };
14049
14050 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14051 buffer.language_settings(cx).tasks.prefer_lsp
14052 }) else {
14053 return;
14054 };
14055
14056 let rows = Self::runnable_rows(
14057 project,
14058 display_snapshot,
14059 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14060 new_rows,
14061 cx.clone(),
14062 )
14063 .await;
14064 editor
14065 .update(cx, |editor, _| {
14066 editor.clear_tasks();
14067 for (key, mut value) in rows {
14068 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14069 value.templates.extend(lsp_tasks.templates);
14070 }
14071
14072 editor.insert_tasks(key, value);
14073 }
14074 for (key, value) in lsp_tasks_by_rows {
14075 editor.insert_tasks(key, value);
14076 }
14077 })
14078 .ok();
14079 })
14080 }
14081 fn fetch_runnable_ranges(
14082 snapshot: &DisplaySnapshot,
14083 range: Range<Anchor>,
14084 ) -> Vec<language::RunnableRange> {
14085 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14086 }
14087
14088 fn runnable_rows(
14089 project: Entity<Project>,
14090 snapshot: DisplaySnapshot,
14091 prefer_lsp: bool,
14092 runnable_ranges: Vec<RunnableRange>,
14093 cx: AsyncWindowContext,
14094 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14095 cx.spawn(async move |cx| {
14096 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14097 for mut runnable in runnable_ranges {
14098 let Some(tasks) = cx
14099 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14100 .ok()
14101 else {
14102 continue;
14103 };
14104 let mut tasks = tasks.await;
14105
14106 if prefer_lsp {
14107 tasks.retain(|(task_kind, _)| {
14108 !matches!(task_kind, TaskSourceKind::Language { .. })
14109 });
14110 }
14111 if tasks.is_empty() {
14112 continue;
14113 }
14114
14115 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14116 let Some(row) = snapshot
14117 .buffer_snapshot
14118 .buffer_line_for_row(MultiBufferRow(point.row))
14119 .map(|(_, range)| range.start.row)
14120 else {
14121 continue;
14122 };
14123
14124 let context_range =
14125 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14126 runnable_rows.push((
14127 (runnable.buffer_id, row),
14128 RunnableTasks {
14129 templates: tasks,
14130 offset: snapshot
14131 .buffer_snapshot
14132 .anchor_before(runnable.run_range.start),
14133 context_range,
14134 column: point.column,
14135 extra_variables: runnable.extra_captures,
14136 },
14137 ));
14138 }
14139 runnable_rows
14140 })
14141 }
14142
14143 fn templates_with_tags(
14144 project: &Entity<Project>,
14145 runnable: &mut Runnable,
14146 cx: &mut App,
14147 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14148 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14149 let (worktree_id, file) = project
14150 .buffer_for_id(runnable.buffer, cx)
14151 .and_then(|buffer| buffer.read(cx).file())
14152 .map(|file| (file.worktree_id(cx), file.clone()))
14153 .unzip();
14154
14155 (
14156 project.task_store().read(cx).task_inventory().cloned(),
14157 worktree_id,
14158 file,
14159 )
14160 });
14161
14162 let tags = mem::take(&mut runnable.tags);
14163 let language = runnable.language.clone();
14164 cx.spawn(async move |cx| {
14165 let mut templates_with_tags = Vec::new();
14166 if let Some(inventory) = inventory {
14167 for RunnableTag(tag) in tags {
14168 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14169 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14170 }) else {
14171 return templates_with_tags;
14172 };
14173 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14174 move |(_, template)| {
14175 template.tags.iter().any(|source_tag| source_tag == &tag)
14176 },
14177 ));
14178 }
14179 }
14180 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14181
14182 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14183 // Strongest source wins; if we have worktree tag binding, prefer that to
14184 // global and language bindings;
14185 // if we have a global binding, prefer that to language binding.
14186 let first_mismatch = templates_with_tags
14187 .iter()
14188 .position(|(tag_source, _)| tag_source != leading_tag_source);
14189 if let Some(index) = first_mismatch {
14190 templates_with_tags.truncate(index);
14191 }
14192 }
14193
14194 templates_with_tags
14195 })
14196 }
14197
14198 pub fn move_to_enclosing_bracket(
14199 &mut self,
14200 _: &MoveToEnclosingBracket,
14201 window: &mut Window,
14202 cx: &mut Context<Self>,
14203 ) {
14204 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14205 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14206 s.move_offsets_with(|snapshot, selection| {
14207 let Some(enclosing_bracket_ranges) =
14208 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14209 else {
14210 return;
14211 };
14212
14213 let mut best_length = usize::MAX;
14214 let mut best_inside = false;
14215 let mut best_in_bracket_range = false;
14216 let mut best_destination = None;
14217 for (open, close) in enclosing_bracket_ranges {
14218 let close = close.to_inclusive();
14219 let length = close.end() - open.start;
14220 let inside = selection.start >= open.end && selection.end <= *close.start();
14221 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14222 || close.contains(&selection.head());
14223
14224 // If best is next to a bracket and current isn't, skip
14225 if !in_bracket_range && best_in_bracket_range {
14226 continue;
14227 }
14228
14229 // Prefer smaller lengths unless best is inside and current isn't
14230 if length > best_length && (best_inside || !inside) {
14231 continue;
14232 }
14233
14234 best_length = length;
14235 best_inside = inside;
14236 best_in_bracket_range = in_bracket_range;
14237 best_destination = Some(
14238 if close.contains(&selection.start) && close.contains(&selection.end) {
14239 if inside { open.end } else { open.start }
14240 } else if inside {
14241 *close.start()
14242 } else {
14243 *close.end()
14244 },
14245 );
14246 }
14247
14248 if let Some(destination) = best_destination {
14249 selection.collapse_to(destination, SelectionGoal::None);
14250 }
14251 })
14252 });
14253 }
14254
14255 pub fn undo_selection(
14256 &mut self,
14257 _: &UndoSelection,
14258 window: &mut Window,
14259 cx: &mut Context<Self>,
14260 ) {
14261 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14262 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14263 self.selection_history.mode = SelectionHistoryMode::Undoing;
14264 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14265 this.end_selection(window, cx);
14266 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14267 s.select_anchors(entry.selections.to_vec())
14268 });
14269 });
14270 self.selection_history.mode = SelectionHistoryMode::Normal;
14271
14272 self.select_next_state = entry.select_next_state;
14273 self.select_prev_state = entry.select_prev_state;
14274 self.add_selections_state = entry.add_selections_state;
14275 }
14276 }
14277
14278 pub fn redo_selection(
14279 &mut self,
14280 _: &RedoSelection,
14281 window: &mut Window,
14282 cx: &mut Context<Self>,
14283 ) {
14284 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14285 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14286 self.selection_history.mode = SelectionHistoryMode::Redoing;
14287 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14288 this.end_selection(window, cx);
14289 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14290 s.select_anchors(entry.selections.to_vec())
14291 });
14292 });
14293 self.selection_history.mode = SelectionHistoryMode::Normal;
14294
14295 self.select_next_state = entry.select_next_state;
14296 self.select_prev_state = entry.select_prev_state;
14297 self.add_selections_state = entry.add_selections_state;
14298 }
14299 }
14300
14301 pub fn expand_excerpts(
14302 &mut self,
14303 action: &ExpandExcerpts,
14304 _: &mut Window,
14305 cx: &mut Context<Self>,
14306 ) {
14307 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14308 }
14309
14310 pub fn expand_excerpts_down(
14311 &mut self,
14312 action: &ExpandExcerptsDown,
14313 _: &mut Window,
14314 cx: &mut Context<Self>,
14315 ) {
14316 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14317 }
14318
14319 pub fn expand_excerpts_up(
14320 &mut self,
14321 action: &ExpandExcerptsUp,
14322 _: &mut Window,
14323 cx: &mut Context<Self>,
14324 ) {
14325 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14326 }
14327
14328 pub fn expand_excerpts_for_direction(
14329 &mut self,
14330 lines: u32,
14331 direction: ExpandExcerptDirection,
14332
14333 cx: &mut Context<Self>,
14334 ) {
14335 let selections = self.selections.disjoint_anchors();
14336
14337 let lines = if lines == 0 {
14338 EditorSettings::get_global(cx).expand_excerpt_lines
14339 } else {
14340 lines
14341 };
14342
14343 self.buffer.update(cx, |buffer, cx| {
14344 let snapshot = buffer.snapshot(cx);
14345 let mut excerpt_ids = selections
14346 .iter()
14347 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14348 .collect::<Vec<_>>();
14349 excerpt_ids.sort();
14350 excerpt_ids.dedup();
14351 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14352 })
14353 }
14354
14355 pub fn expand_excerpt(
14356 &mut self,
14357 excerpt: ExcerptId,
14358 direction: ExpandExcerptDirection,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) {
14362 let current_scroll_position = self.scroll_position(cx);
14363 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14364 let mut should_scroll_up = false;
14365
14366 if direction == ExpandExcerptDirection::Down {
14367 let multi_buffer = self.buffer.read(cx);
14368 let snapshot = multi_buffer.snapshot(cx);
14369 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14370 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14371 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14372 let buffer_snapshot = buffer.read(cx).snapshot();
14373 let excerpt_end_row =
14374 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14375 let last_row = buffer_snapshot.max_point().row;
14376 let lines_below = last_row.saturating_sub(excerpt_end_row);
14377 should_scroll_up = lines_below >= lines_to_expand;
14378 }
14379 }
14380 }
14381 }
14382
14383 self.buffer.update(cx, |buffer, cx| {
14384 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14385 });
14386
14387 if should_scroll_up {
14388 let new_scroll_position =
14389 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14390 self.set_scroll_position(new_scroll_position, window, cx);
14391 }
14392 }
14393
14394 pub fn go_to_singleton_buffer_point(
14395 &mut self,
14396 point: Point,
14397 window: &mut Window,
14398 cx: &mut Context<Self>,
14399 ) {
14400 self.go_to_singleton_buffer_range(point..point, window, cx);
14401 }
14402
14403 pub fn go_to_singleton_buffer_range(
14404 &mut self,
14405 range: Range<Point>,
14406 window: &mut Window,
14407 cx: &mut Context<Self>,
14408 ) {
14409 let multibuffer = self.buffer().read(cx);
14410 let Some(buffer) = multibuffer.as_singleton() else {
14411 return;
14412 };
14413 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14414 return;
14415 };
14416 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14417 return;
14418 };
14419 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14420 s.select_anchor_ranges([start..end])
14421 });
14422 }
14423
14424 pub fn go_to_diagnostic(
14425 &mut self,
14426 _: &GoToDiagnostic,
14427 window: &mut Window,
14428 cx: &mut Context<Self>,
14429 ) {
14430 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14431 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14432 }
14433
14434 pub fn go_to_prev_diagnostic(
14435 &mut self,
14436 _: &GoToPreviousDiagnostic,
14437 window: &mut Window,
14438 cx: &mut Context<Self>,
14439 ) {
14440 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14441 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14442 }
14443
14444 pub fn go_to_diagnostic_impl(
14445 &mut self,
14446 direction: Direction,
14447 window: &mut Window,
14448 cx: &mut Context<Self>,
14449 ) {
14450 let buffer = self.buffer.read(cx).snapshot(cx);
14451 let selection = self.selections.newest::<usize>(cx);
14452
14453 let mut active_group_id = None;
14454 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14455 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14456 active_group_id = Some(active_group.group_id);
14457 }
14458 }
14459
14460 fn filtered(
14461 snapshot: EditorSnapshot,
14462 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14463 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14464 diagnostics
14465 .filter(|entry| entry.range.start != entry.range.end)
14466 .filter(|entry| !entry.diagnostic.is_unnecessary)
14467 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14468 }
14469
14470 let snapshot = self.snapshot(window, cx);
14471 let before = filtered(
14472 snapshot.clone(),
14473 buffer
14474 .diagnostics_in_range(0..selection.start)
14475 .filter(|entry| entry.range.start <= selection.start),
14476 );
14477 let after = filtered(
14478 snapshot,
14479 buffer
14480 .diagnostics_in_range(selection.start..buffer.len())
14481 .filter(|entry| entry.range.start >= selection.start),
14482 );
14483
14484 let mut found: Option<DiagnosticEntry<usize>> = None;
14485 if direction == Direction::Prev {
14486 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14487 {
14488 for diagnostic in prev_diagnostics.into_iter().rev() {
14489 if diagnostic.range.start != selection.start
14490 || active_group_id
14491 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14492 {
14493 found = Some(diagnostic);
14494 break 'outer;
14495 }
14496 }
14497 }
14498 } else {
14499 for diagnostic in after.chain(before) {
14500 if diagnostic.range.start != selection.start
14501 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14502 {
14503 found = Some(diagnostic);
14504 break;
14505 }
14506 }
14507 }
14508 let Some(next_diagnostic) = found else {
14509 return;
14510 };
14511
14512 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14513 return;
14514 };
14515 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14516 s.select_ranges(vec![
14517 next_diagnostic.range.start..next_diagnostic.range.start,
14518 ])
14519 });
14520 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14521 self.refresh_inline_completion(false, true, window, cx);
14522 }
14523
14524 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14525 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14526 let snapshot = self.snapshot(window, cx);
14527 let selection = self.selections.newest::<Point>(cx);
14528 self.go_to_hunk_before_or_after_position(
14529 &snapshot,
14530 selection.head(),
14531 Direction::Next,
14532 window,
14533 cx,
14534 );
14535 }
14536
14537 pub fn go_to_hunk_before_or_after_position(
14538 &mut self,
14539 snapshot: &EditorSnapshot,
14540 position: Point,
14541 direction: Direction,
14542 window: &mut Window,
14543 cx: &mut Context<Editor>,
14544 ) {
14545 let row = if direction == Direction::Next {
14546 self.hunk_after_position(snapshot, position)
14547 .map(|hunk| hunk.row_range.start)
14548 } else {
14549 self.hunk_before_position(snapshot, position)
14550 };
14551
14552 if let Some(row) = row {
14553 let destination = Point::new(row.0, 0);
14554 let autoscroll = Autoscroll::center();
14555
14556 self.unfold_ranges(&[destination..destination], false, false, cx);
14557 self.change_selections(Some(autoscroll), window, cx, |s| {
14558 s.select_ranges([destination..destination]);
14559 });
14560 }
14561 }
14562
14563 fn hunk_after_position(
14564 &mut self,
14565 snapshot: &EditorSnapshot,
14566 position: Point,
14567 ) -> Option<MultiBufferDiffHunk> {
14568 snapshot
14569 .buffer_snapshot
14570 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14571 .find(|hunk| hunk.row_range.start.0 > position.row)
14572 .or_else(|| {
14573 snapshot
14574 .buffer_snapshot
14575 .diff_hunks_in_range(Point::zero()..position)
14576 .find(|hunk| hunk.row_range.end.0 < position.row)
14577 })
14578 }
14579
14580 fn go_to_prev_hunk(
14581 &mut self,
14582 _: &GoToPreviousHunk,
14583 window: &mut Window,
14584 cx: &mut Context<Self>,
14585 ) {
14586 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14587 let snapshot = self.snapshot(window, cx);
14588 let selection = self.selections.newest::<Point>(cx);
14589 self.go_to_hunk_before_or_after_position(
14590 &snapshot,
14591 selection.head(),
14592 Direction::Prev,
14593 window,
14594 cx,
14595 );
14596 }
14597
14598 fn hunk_before_position(
14599 &mut self,
14600 snapshot: &EditorSnapshot,
14601 position: Point,
14602 ) -> Option<MultiBufferRow> {
14603 snapshot
14604 .buffer_snapshot
14605 .diff_hunk_before(position)
14606 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14607 }
14608
14609 fn go_to_next_change(
14610 &mut self,
14611 _: &GoToNextChange,
14612 window: &mut Window,
14613 cx: &mut Context<Self>,
14614 ) {
14615 if let Some(selections) = self
14616 .change_list
14617 .next_change(1, Direction::Next)
14618 .map(|s| s.to_vec())
14619 {
14620 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14621 let map = s.display_map();
14622 s.select_display_ranges(selections.iter().map(|a| {
14623 let point = a.to_display_point(&map);
14624 point..point
14625 }))
14626 })
14627 }
14628 }
14629
14630 fn go_to_previous_change(
14631 &mut self,
14632 _: &GoToPreviousChange,
14633 window: &mut Window,
14634 cx: &mut Context<Self>,
14635 ) {
14636 if let Some(selections) = self
14637 .change_list
14638 .next_change(1, Direction::Prev)
14639 .map(|s| s.to_vec())
14640 {
14641 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14642 let map = s.display_map();
14643 s.select_display_ranges(selections.iter().map(|a| {
14644 let point = a.to_display_point(&map);
14645 point..point
14646 }))
14647 })
14648 }
14649 }
14650
14651 fn go_to_line<T: 'static>(
14652 &mut self,
14653 position: Anchor,
14654 highlight_color: Option<Hsla>,
14655 window: &mut Window,
14656 cx: &mut Context<Self>,
14657 ) {
14658 let snapshot = self.snapshot(window, cx).display_snapshot;
14659 let position = position.to_point(&snapshot.buffer_snapshot);
14660 let start = snapshot
14661 .buffer_snapshot
14662 .clip_point(Point::new(position.row, 0), Bias::Left);
14663 let end = start + Point::new(1, 0);
14664 let start = snapshot.buffer_snapshot.anchor_before(start);
14665 let end = snapshot.buffer_snapshot.anchor_before(end);
14666
14667 self.highlight_rows::<T>(
14668 start..end,
14669 highlight_color
14670 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14671 Default::default(),
14672 cx,
14673 );
14674
14675 if self.buffer.read(cx).is_singleton() {
14676 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14677 }
14678 }
14679
14680 pub fn go_to_definition(
14681 &mut self,
14682 _: &GoToDefinition,
14683 window: &mut Window,
14684 cx: &mut Context<Self>,
14685 ) -> Task<Result<Navigated>> {
14686 let definition =
14687 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14688 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14689 cx.spawn_in(window, async move |editor, cx| {
14690 if definition.await? == Navigated::Yes {
14691 return Ok(Navigated::Yes);
14692 }
14693 match fallback_strategy {
14694 GoToDefinitionFallback::None => Ok(Navigated::No),
14695 GoToDefinitionFallback::FindAllReferences => {
14696 match editor.update_in(cx, |editor, window, cx| {
14697 editor.find_all_references(&FindAllReferences, window, cx)
14698 })? {
14699 Some(references) => references.await,
14700 None => Ok(Navigated::No),
14701 }
14702 }
14703 }
14704 })
14705 }
14706
14707 pub fn go_to_declaration(
14708 &mut self,
14709 _: &GoToDeclaration,
14710 window: &mut Window,
14711 cx: &mut Context<Self>,
14712 ) -> Task<Result<Navigated>> {
14713 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14714 }
14715
14716 pub fn go_to_declaration_split(
14717 &mut self,
14718 _: &GoToDeclaration,
14719 window: &mut Window,
14720 cx: &mut Context<Self>,
14721 ) -> Task<Result<Navigated>> {
14722 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14723 }
14724
14725 pub fn go_to_implementation(
14726 &mut self,
14727 _: &GoToImplementation,
14728 window: &mut Window,
14729 cx: &mut Context<Self>,
14730 ) -> Task<Result<Navigated>> {
14731 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14732 }
14733
14734 pub fn go_to_implementation_split(
14735 &mut self,
14736 _: &GoToImplementationSplit,
14737 window: &mut Window,
14738 cx: &mut Context<Self>,
14739 ) -> Task<Result<Navigated>> {
14740 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14741 }
14742
14743 pub fn go_to_type_definition(
14744 &mut self,
14745 _: &GoToTypeDefinition,
14746 window: &mut Window,
14747 cx: &mut Context<Self>,
14748 ) -> Task<Result<Navigated>> {
14749 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14750 }
14751
14752 pub fn go_to_definition_split(
14753 &mut self,
14754 _: &GoToDefinitionSplit,
14755 window: &mut Window,
14756 cx: &mut Context<Self>,
14757 ) -> Task<Result<Navigated>> {
14758 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14759 }
14760
14761 pub fn go_to_type_definition_split(
14762 &mut self,
14763 _: &GoToTypeDefinitionSplit,
14764 window: &mut Window,
14765 cx: &mut Context<Self>,
14766 ) -> Task<Result<Navigated>> {
14767 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14768 }
14769
14770 fn go_to_definition_of_kind(
14771 &mut self,
14772 kind: GotoDefinitionKind,
14773 split: bool,
14774 window: &mut Window,
14775 cx: &mut Context<Self>,
14776 ) -> Task<Result<Navigated>> {
14777 let Some(provider) = self.semantics_provider.clone() else {
14778 return Task::ready(Ok(Navigated::No));
14779 };
14780 let head = self.selections.newest::<usize>(cx).head();
14781 let buffer = self.buffer.read(cx);
14782 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14783 text_anchor
14784 } else {
14785 return Task::ready(Ok(Navigated::No));
14786 };
14787
14788 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14789 return Task::ready(Ok(Navigated::No));
14790 };
14791
14792 cx.spawn_in(window, async move |editor, cx| {
14793 let definitions = definitions.await?;
14794 let navigated = editor
14795 .update_in(cx, |editor, window, cx| {
14796 editor.navigate_to_hover_links(
14797 Some(kind),
14798 definitions
14799 .into_iter()
14800 .filter(|location| {
14801 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14802 })
14803 .map(HoverLink::Text)
14804 .collect::<Vec<_>>(),
14805 split,
14806 window,
14807 cx,
14808 )
14809 })?
14810 .await?;
14811 anyhow::Ok(navigated)
14812 })
14813 }
14814
14815 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14816 let selection = self.selections.newest_anchor();
14817 let head = selection.head();
14818 let tail = selection.tail();
14819
14820 let Some((buffer, start_position)) =
14821 self.buffer.read(cx).text_anchor_for_position(head, cx)
14822 else {
14823 return;
14824 };
14825
14826 let end_position = if head != tail {
14827 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14828 return;
14829 };
14830 Some(pos)
14831 } else {
14832 None
14833 };
14834
14835 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14836 let url = if let Some(end_pos) = end_position {
14837 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14838 } else {
14839 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14840 };
14841
14842 if let Some(url) = url {
14843 editor.update(cx, |_, cx| {
14844 cx.open_url(&url);
14845 })
14846 } else {
14847 Ok(())
14848 }
14849 });
14850
14851 url_finder.detach();
14852 }
14853
14854 pub fn open_selected_filename(
14855 &mut self,
14856 _: &OpenSelectedFilename,
14857 window: &mut Window,
14858 cx: &mut Context<Self>,
14859 ) {
14860 let Some(workspace) = self.workspace() else {
14861 return;
14862 };
14863
14864 let position = self.selections.newest_anchor().head();
14865
14866 let Some((buffer, buffer_position)) =
14867 self.buffer.read(cx).text_anchor_for_position(position, cx)
14868 else {
14869 return;
14870 };
14871
14872 let project = self.project.clone();
14873
14874 cx.spawn_in(window, async move |_, cx| {
14875 let result = find_file(&buffer, project, buffer_position, cx).await;
14876
14877 if let Some((_, path)) = result {
14878 workspace
14879 .update_in(cx, |workspace, window, cx| {
14880 workspace.open_resolved_path(path, window, cx)
14881 })?
14882 .await?;
14883 }
14884 anyhow::Ok(())
14885 })
14886 .detach();
14887 }
14888
14889 pub(crate) fn navigate_to_hover_links(
14890 &mut self,
14891 kind: Option<GotoDefinitionKind>,
14892 mut definitions: Vec<HoverLink>,
14893 split: bool,
14894 window: &mut Window,
14895 cx: &mut Context<Editor>,
14896 ) -> Task<Result<Navigated>> {
14897 // If there is one definition, just open it directly
14898 if definitions.len() == 1 {
14899 let definition = definitions.pop().unwrap();
14900
14901 enum TargetTaskResult {
14902 Location(Option<Location>),
14903 AlreadyNavigated,
14904 }
14905
14906 let target_task = match definition {
14907 HoverLink::Text(link) => {
14908 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14909 }
14910 HoverLink::InlayHint(lsp_location, server_id) => {
14911 let computation =
14912 self.compute_target_location(lsp_location, server_id, window, cx);
14913 cx.background_spawn(async move {
14914 let location = computation.await?;
14915 Ok(TargetTaskResult::Location(location))
14916 })
14917 }
14918 HoverLink::Url(url) => {
14919 cx.open_url(&url);
14920 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14921 }
14922 HoverLink::File(path) => {
14923 if let Some(workspace) = self.workspace() {
14924 cx.spawn_in(window, async move |_, cx| {
14925 workspace
14926 .update_in(cx, |workspace, window, cx| {
14927 workspace.open_resolved_path(path, window, cx)
14928 })?
14929 .await
14930 .map(|_| TargetTaskResult::AlreadyNavigated)
14931 })
14932 } else {
14933 Task::ready(Ok(TargetTaskResult::Location(None)))
14934 }
14935 }
14936 };
14937 cx.spawn_in(window, async move |editor, cx| {
14938 let target = match target_task.await.context("target resolution task")? {
14939 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14940 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14941 TargetTaskResult::Location(Some(target)) => target,
14942 };
14943
14944 editor.update_in(cx, |editor, window, cx| {
14945 let Some(workspace) = editor.workspace() else {
14946 return Navigated::No;
14947 };
14948 let pane = workspace.read(cx).active_pane().clone();
14949
14950 let range = target.range.to_point(target.buffer.read(cx));
14951 let range = editor.range_for_match(&range);
14952 let range = collapse_multiline_range(range);
14953
14954 if !split
14955 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14956 {
14957 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14958 } else {
14959 window.defer(cx, move |window, cx| {
14960 let target_editor: Entity<Self> =
14961 workspace.update(cx, |workspace, cx| {
14962 let pane = if split {
14963 workspace.adjacent_pane(window, cx)
14964 } else {
14965 workspace.active_pane().clone()
14966 };
14967
14968 workspace.open_project_item(
14969 pane,
14970 target.buffer.clone(),
14971 true,
14972 true,
14973 window,
14974 cx,
14975 )
14976 });
14977 target_editor.update(cx, |target_editor, cx| {
14978 // When selecting a definition in a different buffer, disable the nav history
14979 // to avoid creating a history entry at the previous cursor location.
14980 pane.update(cx, |pane, _| pane.disable_history());
14981 target_editor.go_to_singleton_buffer_range(range, window, cx);
14982 pane.update(cx, |pane, _| pane.enable_history());
14983 });
14984 });
14985 }
14986 Navigated::Yes
14987 })
14988 })
14989 } else if !definitions.is_empty() {
14990 cx.spawn_in(window, async move |editor, cx| {
14991 let (title, location_tasks, workspace) = editor
14992 .update_in(cx, |editor, window, cx| {
14993 let tab_kind = match kind {
14994 Some(GotoDefinitionKind::Implementation) => "Implementations",
14995 _ => "Definitions",
14996 };
14997 let title = definitions
14998 .iter()
14999 .find_map(|definition| match definition {
15000 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15001 let buffer = origin.buffer.read(cx);
15002 format!(
15003 "{} for {}",
15004 tab_kind,
15005 buffer
15006 .text_for_range(origin.range.clone())
15007 .collect::<String>()
15008 )
15009 }),
15010 HoverLink::InlayHint(_, _) => None,
15011 HoverLink::Url(_) => None,
15012 HoverLink::File(_) => None,
15013 })
15014 .unwrap_or(tab_kind.to_string());
15015 let location_tasks = definitions
15016 .into_iter()
15017 .map(|definition| match definition {
15018 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15019 HoverLink::InlayHint(lsp_location, server_id) => editor
15020 .compute_target_location(lsp_location, server_id, window, cx),
15021 HoverLink::Url(_) => Task::ready(Ok(None)),
15022 HoverLink::File(_) => Task::ready(Ok(None)),
15023 })
15024 .collect::<Vec<_>>();
15025 (title, location_tasks, editor.workspace().clone())
15026 })
15027 .context("location tasks preparation")?;
15028
15029 let locations = future::join_all(location_tasks)
15030 .await
15031 .into_iter()
15032 .filter_map(|location| location.transpose())
15033 .collect::<Result<_>>()
15034 .context("location tasks")?;
15035
15036 let Some(workspace) = workspace else {
15037 return Ok(Navigated::No);
15038 };
15039 let opened = workspace
15040 .update_in(cx, |workspace, window, cx| {
15041 Self::open_locations_in_multibuffer(
15042 workspace,
15043 locations,
15044 title,
15045 split,
15046 MultibufferSelectionMode::First,
15047 window,
15048 cx,
15049 )
15050 })
15051 .ok();
15052
15053 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15054 })
15055 } else {
15056 Task::ready(Ok(Navigated::No))
15057 }
15058 }
15059
15060 fn compute_target_location(
15061 &self,
15062 lsp_location: lsp::Location,
15063 server_id: LanguageServerId,
15064 window: &mut Window,
15065 cx: &mut Context<Self>,
15066 ) -> Task<anyhow::Result<Option<Location>>> {
15067 let Some(project) = self.project.clone() else {
15068 return Task::ready(Ok(None));
15069 };
15070
15071 cx.spawn_in(window, async move |editor, cx| {
15072 let location_task = editor.update(cx, |_, cx| {
15073 project.update(cx, |project, cx| {
15074 let language_server_name = project
15075 .language_server_statuses(cx)
15076 .find(|(id, _)| server_id == *id)
15077 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15078 language_server_name.map(|language_server_name| {
15079 project.open_local_buffer_via_lsp(
15080 lsp_location.uri.clone(),
15081 server_id,
15082 language_server_name,
15083 cx,
15084 )
15085 })
15086 })
15087 })?;
15088 let location = match location_task {
15089 Some(task) => Some({
15090 let target_buffer_handle = task.await.context("open local buffer")?;
15091 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15092 let target_start = target_buffer
15093 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15094 let target_end = target_buffer
15095 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15096 target_buffer.anchor_after(target_start)
15097 ..target_buffer.anchor_before(target_end)
15098 })?;
15099 Location {
15100 buffer: target_buffer_handle,
15101 range,
15102 }
15103 }),
15104 None => None,
15105 };
15106 Ok(location)
15107 })
15108 }
15109
15110 pub fn find_all_references(
15111 &mut self,
15112 _: &FindAllReferences,
15113 window: &mut Window,
15114 cx: &mut Context<Self>,
15115 ) -> Option<Task<Result<Navigated>>> {
15116 let selection = self.selections.newest::<usize>(cx);
15117 let multi_buffer = self.buffer.read(cx);
15118 let head = selection.head();
15119
15120 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15121 let head_anchor = multi_buffer_snapshot.anchor_at(
15122 head,
15123 if head < selection.tail() {
15124 Bias::Right
15125 } else {
15126 Bias::Left
15127 },
15128 );
15129
15130 match self
15131 .find_all_references_task_sources
15132 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15133 {
15134 Ok(_) => {
15135 log::info!(
15136 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15137 );
15138 return None;
15139 }
15140 Err(i) => {
15141 self.find_all_references_task_sources.insert(i, head_anchor);
15142 }
15143 }
15144
15145 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15146 let workspace = self.workspace()?;
15147 let project = workspace.read(cx).project().clone();
15148 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15149 Some(cx.spawn_in(window, async move |editor, cx| {
15150 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15151 if let Ok(i) = editor
15152 .find_all_references_task_sources
15153 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15154 {
15155 editor.find_all_references_task_sources.remove(i);
15156 }
15157 });
15158
15159 let locations = references.await?;
15160 if locations.is_empty() {
15161 return anyhow::Ok(Navigated::No);
15162 }
15163
15164 workspace.update_in(cx, |workspace, window, cx| {
15165 let title = locations
15166 .first()
15167 .as_ref()
15168 .map(|location| {
15169 let buffer = location.buffer.read(cx);
15170 format!(
15171 "References to `{}`",
15172 buffer
15173 .text_for_range(location.range.clone())
15174 .collect::<String>()
15175 )
15176 })
15177 .unwrap();
15178 Self::open_locations_in_multibuffer(
15179 workspace,
15180 locations,
15181 title,
15182 false,
15183 MultibufferSelectionMode::First,
15184 window,
15185 cx,
15186 );
15187 Navigated::Yes
15188 })
15189 }))
15190 }
15191
15192 /// Opens a multibuffer with the given project locations in it
15193 pub fn open_locations_in_multibuffer(
15194 workspace: &mut Workspace,
15195 mut locations: Vec<Location>,
15196 title: String,
15197 split: bool,
15198 multibuffer_selection_mode: MultibufferSelectionMode,
15199 window: &mut Window,
15200 cx: &mut Context<Workspace>,
15201 ) {
15202 // If there are multiple definitions, open them in a multibuffer
15203 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15204 let mut locations = locations.into_iter().peekable();
15205 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15206 let capability = workspace.project().read(cx).capability();
15207
15208 let excerpt_buffer = cx.new(|cx| {
15209 let mut multibuffer = MultiBuffer::new(capability);
15210 while let Some(location) = locations.next() {
15211 let buffer = location.buffer.read(cx);
15212 let mut ranges_for_buffer = Vec::new();
15213 let range = location.range.to_point(buffer);
15214 ranges_for_buffer.push(range.clone());
15215
15216 while let Some(next_location) = locations.peek() {
15217 if next_location.buffer == location.buffer {
15218 ranges_for_buffer.push(next_location.range.to_point(buffer));
15219 locations.next();
15220 } else {
15221 break;
15222 }
15223 }
15224
15225 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15226 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15227 PathKey::for_buffer(&location.buffer, cx),
15228 location.buffer.clone(),
15229 ranges_for_buffer,
15230 DEFAULT_MULTIBUFFER_CONTEXT,
15231 cx,
15232 );
15233 ranges.extend(new_ranges)
15234 }
15235
15236 multibuffer.with_title(title)
15237 });
15238
15239 let editor = cx.new(|cx| {
15240 Editor::for_multibuffer(
15241 excerpt_buffer,
15242 Some(workspace.project().clone()),
15243 window,
15244 cx,
15245 )
15246 });
15247 editor.update(cx, |editor, cx| {
15248 match multibuffer_selection_mode {
15249 MultibufferSelectionMode::First => {
15250 if let Some(first_range) = ranges.first() {
15251 editor.change_selections(None, window, cx, |selections| {
15252 selections.clear_disjoint();
15253 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15254 });
15255 }
15256 editor.highlight_background::<Self>(
15257 &ranges,
15258 |theme| theme.editor_highlighted_line_background,
15259 cx,
15260 );
15261 }
15262 MultibufferSelectionMode::All => {
15263 editor.change_selections(None, window, cx, |selections| {
15264 selections.clear_disjoint();
15265 selections.select_anchor_ranges(ranges);
15266 });
15267 }
15268 }
15269 editor.register_buffers_with_language_servers(cx);
15270 });
15271
15272 let item = Box::new(editor);
15273 let item_id = item.item_id();
15274
15275 if split {
15276 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15277 } else {
15278 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15279 let (preview_item_id, preview_item_idx) =
15280 workspace.active_pane().read_with(cx, |pane, _| {
15281 (pane.preview_item_id(), pane.preview_item_idx())
15282 });
15283
15284 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15285
15286 if let Some(preview_item_id) = preview_item_id {
15287 workspace.active_pane().update(cx, |pane, cx| {
15288 pane.remove_item(preview_item_id, false, false, window, cx);
15289 });
15290 }
15291 } else {
15292 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15293 }
15294 }
15295 workspace.active_pane().update(cx, |pane, cx| {
15296 pane.set_preview_item_id(Some(item_id), cx);
15297 });
15298 }
15299
15300 pub fn rename(
15301 &mut self,
15302 _: &Rename,
15303 window: &mut Window,
15304 cx: &mut Context<Self>,
15305 ) -> Option<Task<Result<()>>> {
15306 use language::ToOffset as _;
15307
15308 let provider = self.semantics_provider.clone()?;
15309 let selection = self.selections.newest_anchor().clone();
15310 let (cursor_buffer, cursor_buffer_position) = self
15311 .buffer
15312 .read(cx)
15313 .text_anchor_for_position(selection.head(), cx)?;
15314 let (tail_buffer, cursor_buffer_position_end) = self
15315 .buffer
15316 .read(cx)
15317 .text_anchor_for_position(selection.tail(), cx)?;
15318 if tail_buffer != cursor_buffer {
15319 return None;
15320 }
15321
15322 let snapshot = cursor_buffer.read(cx).snapshot();
15323 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15324 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15325 let prepare_rename = provider
15326 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15327 .unwrap_or_else(|| Task::ready(Ok(None)));
15328 drop(snapshot);
15329
15330 Some(cx.spawn_in(window, async move |this, cx| {
15331 let rename_range = if let Some(range) = prepare_rename.await? {
15332 Some(range)
15333 } else {
15334 this.update(cx, |this, cx| {
15335 let buffer = this.buffer.read(cx).snapshot(cx);
15336 let mut buffer_highlights = this
15337 .document_highlights_for_position(selection.head(), &buffer)
15338 .filter(|highlight| {
15339 highlight.start.excerpt_id == selection.head().excerpt_id
15340 && highlight.end.excerpt_id == selection.head().excerpt_id
15341 });
15342 buffer_highlights
15343 .next()
15344 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15345 })?
15346 };
15347 if let Some(rename_range) = rename_range {
15348 this.update_in(cx, |this, window, cx| {
15349 let snapshot = cursor_buffer.read(cx).snapshot();
15350 let rename_buffer_range = rename_range.to_offset(&snapshot);
15351 let cursor_offset_in_rename_range =
15352 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15353 let cursor_offset_in_rename_range_end =
15354 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15355
15356 this.take_rename(false, window, cx);
15357 let buffer = this.buffer.read(cx).read(cx);
15358 let cursor_offset = selection.head().to_offset(&buffer);
15359 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15360 let rename_end = rename_start + rename_buffer_range.len();
15361 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15362 let mut old_highlight_id = None;
15363 let old_name: Arc<str> = buffer
15364 .chunks(rename_start..rename_end, true)
15365 .map(|chunk| {
15366 if old_highlight_id.is_none() {
15367 old_highlight_id = chunk.syntax_highlight_id;
15368 }
15369 chunk.text
15370 })
15371 .collect::<String>()
15372 .into();
15373
15374 drop(buffer);
15375
15376 // Position the selection in the rename editor so that it matches the current selection.
15377 this.show_local_selections = false;
15378 let rename_editor = cx.new(|cx| {
15379 let mut editor = Editor::single_line(window, cx);
15380 editor.buffer.update(cx, |buffer, cx| {
15381 buffer.edit([(0..0, old_name.clone())], None, cx)
15382 });
15383 let rename_selection_range = match cursor_offset_in_rename_range
15384 .cmp(&cursor_offset_in_rename_range_end)
15385 {
15386 Ordering::Equal => {
15387 editor.select_all(&SelectAll, window, cx);
15388 return editor;
15389 }
15390 Ordering::Less => {
15391 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15392 }
15393 Ordering::Greater => {
15394 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15395 }
15396 };
15397 if rename_selection_range.end > old_name.len() {
15398 editor.select_all(&SelectAll, window, cx);
15399 } else {
15400 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15401 s.select_ranges([rename_selection_range]);
15402 });
15403 }
15404 editor
15405 });
15406 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15407 if e == &EditorEvent::Focused {
15408 cx.emit(EditorEvent::FocusedIn)
15409 }
15410 })
15411 .detach();
15412
15413 let write_highlights =
15414 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15415 let read_highlights =
15416 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15417 let ranges = write_highlights
15418 .iter()
15419 .flat_map(|(_, ranges)| ranges.iter())
15420 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15421 .cloned()
15422 .collect();
15423
15424 this.highlight_text::<Rename>(
15425 ranges,
15426 HighlightStyle {
15427 fade_out: Some(0.6),
15428 ..Default::default()
15429 },
15430 cx,
15431 );
15432 let rename_focus_handle = rename_editor.focus_handle(cx);
15433 window.focus(&rename_focus_handle);
15434 let block_id = this.insert_blocks(
15435 [BlockProperties {
15436 style: BlockStyle::Flex,
15437 placement: BlockPlacement::Below(range.start),
15438 height: Some(1),
15439 render: Arc::new({
15440 let rename_editor = rename_editor.clone();
15441 move |cx: &mut BlockContext| {
15442 let mut text_style = cx.editor_style.text.clone();
15443 if let Some(highlight_style) = old_highlight_id
15444 .and_then(|h| h.style(&cx.editor_style.syntax))
15445 {
15446 text_style = text_style.highlight(highlight_style);
15447 }
15448 div()
15449 .block_mouse_except_scroll()
15450 .pl(cx.anchor_x)
15451 .child(EditorElement::new(
15452 &rename_editor,
15453 EditorStyle {
15454 background: cx.theme().system().transparent,
15455 local_player: cx.editor_style.local_player,
15456 text: text_style,
15457 scrollbar_width: cx.editor_style.scrollbar_width,
15458 syntax: cx.editor_style.syntax.clone(),
15459 status: cx.editor_style.status.clone(),
15460 inlay_hints_style: HighlightStyle {
15461 font_weight: Some(FontWeight::BOLD),
15462 ..make_inlay_hints_style(cx.app)
15463 },
15464 inline_completion_styles: make_suggestion_styles(
15465 cx.app,
15466 ),
15467 ..EditorStyle::default()
15468 },
15469 ))
15470 .into_any_element()
15471 }
15472 }),
15473 priority: 0,
15474 render_in_minimap: true,
15475 }],
15476 Some(Autoscroll::fit()),
15477 cx,
15478 )[0];
15479 this.pending_rename = Some(RenameState {
15480 range,
15481 old_name,
15482 editor: rename_editor,
15483 block_id,
15484 });
15485 })?;
15486 }
15487
15488 Ok(())
15489 }))
15490 }
15491
15492 pub fn confirm_rename(
15493 &mut self,
15494 _: &ConfirmRename,
15495 window: &mut Window,
15496 cx: &mut Context<Self>,
15497 ) -> Option<Task<Result<()>>> {
15498 let rename = self.take_rename(false, window, cx)?;
15499 let workspace = self.workspace()?.downgrade();
15500 let (buffer, start) = self
15501 .buffer
15502 .read(cx)
15503 .text_anchor_for_position(rename.range.start, cx)?;
15504 let (end_buffer, _) = self
15505 .buffer
15506 .read(cx)
15507 .text_anchor_for_position(rename.range.end, cx)?;
15508 if buffer != end_buffer {
15509 return None;
15510 }
15511
15512 let old_name = rename.old_name;
15513 let new_name = rename.editor.read(cx).text(cx);
15514
15515 let rename = self.semantics_provider.as_ref()?.perform_rename(
15516 &buffer,
15517 start,
15518 new_name.clone(),
15519 cx,
15520 )?;
15521
15522 Some(cx.spawn_in(window, async move |editor, cx| {
15523 let project_transaction = rename.await?;
15524 Self::open_project_transaction(
15525 &editor,
15526 workspace,
15527 project_transaction,
15528 format!("Rename: {} → {}", old_name, new_name),
15529 cx,
15530 )
15531 .await?;
15532
15533 editor.update(cx, |editor, cx| {
15534 editor.refresh_document_highlights(cx);
15535 })?;
15536 Ok(())
15537 }))
15538 }
15539
15540 fn take_rename(
15541 &mut self,
15542 moving_cursor: bool,
15543 window: &mut Window,
15544 cx: &mut Context<Self>,
15545 ) -> Option<RenameState> {
15546 let rename = self.pending_rename.take()?;
15547 if rename.editor.focus_handle(cx).is_focused(window) {
15548 window.focus(&self.focus_handle);
15549 }
15550
15551 self.remove_blocks(
15552 [rename.block_id].into_iter().collect(),
15553 Some(Autoscroll::fit()),
15554 cx,
15555 );
15556 self.clear_highlights::<Rename>(cx);
15557 self.show_local_selections = true;
15558
15559 if moving_cursor {
15560 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15561 editor.selections.newest::<usize>(cx).head()
15562 });
15563
15564 // Update the selection to match the position of the selection inside
15565 // the rename editor.
15566 let snapshot = self.buffer.read(cx).read(cx);
15567 let rename_range = rename.range.to_offset(&snapshot);
15568 let cursor_in_editor = snapshot
15569 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15570 .min(rename_range.end);
15571 drop(snapshot);
15572
15573 self.change_selections(None, window, cx, |s| {
15574 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15575 });
15576 } else {
15577 self.refresh_document_highlights(cx);
15578 }
15579
15580 Some(rename)
15581 }
15582
15583 pub fn pending_rename(&self) -> Option<&RenameState> {
15584 self.pending_rename.as_ref()
15585 }
15586
15587 fn format(
15588 &mut self,
15589 _: &Format,
15590 window: &mut Window,
15591 cx: &mut Context<Self>,
15592 ) -> Option<Task<Result<()>>> {
15593 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15594
15595 let project = match &self.project {
15596 Some(project) => project.clone(),
15597 None => return None,
15598 };
15599
15600 Some(self.perform_format(
15601 project,
15602 FormatTrigger::Manual,
15603 FormatTarget::Buffers,
15604 window,
15605 cx,
15606 ))
15607 }
15608
15609 fn format_selections(
15610 &mut self,
15611 _: &FormatSelections,
15612 window: &mut Window,
15613 cx: &mut Context<Self>,
15614 ) -> Option<Task<Result<()>>> {
15615 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15616
15617 let project = match &self.project {
15618 Some(project) => project.clone(),
15619 None => return None,
15620 };
15621
15622 let ranges = self
15623 .selections
15624 .all_adjusted(cx)
15625 .into_iter()
15626 .map(|selection| selection.range())
15627 .collect_vec();
15628
15629 Some(self.perform_format(
15630 project,
15631 FormatTrigger::Manual,
15632 FormatTarget::Ranges(ranges),
15633 window,
15634 cx,
15635 ))
15636 }
15637
15638 fn perform_format(
15639 &mut self,
15640 project: Entity<Project>,
15641 trigger: FormatTrigger,
15642 target: FormatTarget,
15643 window: &mut Window,
15644 cx: &mut Context<Self>,
15645 ) -> Task<Result<()>> {
15646 let buffer = self.buffer.clone();
15647 let (buffers, target) = match target {
15648 FormatTarget::Buffers => {
15649 let mut buffers = buffer.read(cx).all_buffers();
15650 if trigger == FormatTrigger::Save {
15651 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15652 }
15653 (buffers, LspFormatTarget::Buffers)
15654 }
15655 FormatTarget::Ranges(selection_ranges) => {
15656 let multi_buffer = buffer.read(cx);
15657 let snapshot = multi_buffer.read(cx);
15658 let mut buffers = HashSet::default();
15659 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15660 BTreeMap::new();
15661 for selection_range in selection_ranges {
15662 for (buffer, buffer_range, _) in
15663 snapshot.range_to_buffer_ranges(selection_range)
15664 {
15665 let buffer_id = buffer.remote_id();
15666 let start = buffer.anchor_before(buffer_range.start);
15667 let end = buffer.anchor_after(buffer_range.end);
15668 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15669 buffer_id_to_ranges
15670 .entry(buffer_id)
15671 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15672 .or_insert_with(|| vec![start..end]);
15673 }
15674 }
15675 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15676 }
15677 };
15678
15679 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15680 let selections_prev = transaction_id_prev
15681 .and_then(|transaction_id_prev| {
15682 // default to selections as they were after the last edit, if we have them,
15683 // instead of how they are now.
15684 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15685 // will take you back to where you made the last edit, instead of staying where you scrolled
15686 self.selection_history
15687 .transaction(transaction_id_prev)
15688 .map(|t| t.0.clone())
15689 })
15690 .unwrap_or_else(|| {
15691 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15692 self.selections.disjoint_anchors()
15693 });
15694
15695 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15696 let format = project.update(cx, |project, cx| {
15697 project.format(buffers, target, true, trigger, cx)
15698 });
15699
15700 cx.spawn_in(window, async move |editor, cx| {
15701 let transaction = futures::select_biased! {
15702 transaction = format.log_err().fuse() => transaction,
15703 () = timeout => {
15704 log::warn!("timed out waiting for formatting");
15705 None
15706 }
15707 };
15708
15709 buffer
15710 .update(cx, |buffer, cx| {
15711 if let Some(transaction) = transaction {
15712 if !buffer.is_singleton() {
15713 buffer.push_transaction(&transaction.0, cx);
15714 }
15715 }
15716 cx.notify();
15717 })
15718 .ok();
15719
15720 if let Some(transaction_id_now) =
15721 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15722 {
15723 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15724 if has_new_transaction {
15725 _ = editor.update(cx, |editor, _| {
15726 editor
15727 .selection_history
15728 .insert_transaction(transaction_id_now, selections_prev);
15729 });
15730 }
15731 }
15732
15733 Ok(())
15734 })
15735 }
15736
15737 fn organize_imports(
15738 &mut self,
15739 _: &OrganizeImports,
15740 window: &mut Window,
15741 cx: &mut Context<Self>,
15742 ) -> Option<Task<Result<()>>> {
15743 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15744 let project = match &self.project {
15745 Some(project) => project.clone(),
15746 None => return None,
15747 };
15748 Some(self.perform_code_action_kind(
15749 project,
15750 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15751 window,
15752 cx,
15753 ))
15754 }
15755
15756 fn perform_code_action_kind(
15757 &mut self,
15758 project: Entity<Project>,
15759 kind: CodeActionKind,
15760 window: &mut Window,
15761 cx: &mut Context<Self>,
15762 ) -> Task<Result<()>> {
15763 let buffer = self.buffer.clone();
15764 let buffers = buffer.read(cx).all_buffers();
15765 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15766 let apply_action = project.update(cx, |project, cx| {
15767 project.apply_code_action_kind(buffers, kind, true, cx)
15768 });
15769 cx.spawn_in(window, async move |_, cx| {
15770 let transaction = futures::select_biased! {
15771 () = timeout => {
15772 log::warn!("timed out waiting for executing code action");
15773 None
15774 }
15775 transaction = apply_action.log_err().fuse() => transaction,
15776 };
15777 buffer
15778 .update(cx, |buffer, cx| {
15779 // check if we need this
15780 if let Some(transaction) = transaction {
15781 if !buffer.is_singleton() {
15782 buffer.push_transaction(&transaction.0, cx);
15783 }
15784 }
15785 cx.notify();
15786 })
15787 .ok();
15788 Ok(())
15789 })
15790 }
15791
15792 fn restart_language_server(
15793 &mut self,
15794 _: &RestartLanguageServer,
15795 _: &mut Window,
15796 cx: &mut Context<Self>,
15797 ) {
15798 if let Some(project) = self.project.clone() {
15799 self.buffer.update(cx, |multi_buffer, cx| {
15800 project.update(cx, |project, cx| {
15801 project.restart_language_servers_for_buffers(
15802 multi_buffer.all_buffers().into_iter().collect(),
15803 cx,
15804 );
15805 });
15806 })
15807 }
15808 }
15809
15810 fn stop_language_server(
15811 &mut self,
15812 _: &StopLanguageServer,
15813 _: &mut Window,
15814 cx: &mut Context<Self>,
15815 ) {
15816 if let Some(project) = self.project.clone() {
15817 self.buffer.update(cx, |multi_buffer, cx| {
15818 project.update(cx, |project, cx| {
15819 project.stop_language_servers_for_buffers(
15820 multi_buffer.all_buffers().into_iter().collect(),
15821 cx,
15822 );
15823 cx.emit(project::Event::RefreshInlayHints);
15824 });
15825 });
15826 }
15827 }
15828
15829 fn cancel_language_server_work(
15830 workspace: &mut Workspace,
15831 _: &actions::CancelLanguageServerWork,
15832 _: &mut Window,
15833 cx: &mut Context<Workspace>,
15834 ) {
15835 let project = workspace.project();
15836 let buffers = workspace
15837 .active_item(cx)
15838 .and_then(|item| item.act_as::<Editor>(cx))
15839 .map_or(HashSet::default(), |editor| {
15840 editor.read(cx).buffer.read(cx).all_buffers()
15841 });
15842 project.update(cx, |project, cx| {
15843 project.cancel_language_server_work_for_buffers(buffers, cx);
15844 });
15845 }
15846
15847 fn show_character_palette(
15848 &mut self,
15849 _: &ShowCharacterPalette,
15850 window: &mut Window,
15851 _: &mut Context<Self>,
15852 ) {
15853 window.show_character_palette();
15854 }
15855
15856 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15857 if self.mode.is_minimap() {
15858 return;
15859 }
15860
15861 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15862 let buffer = self.buffer.read(cx).snapshot(cx);
15863 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15864 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15865 let is_valid = buffer
15866 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15867 .any(|entry| {
15868 entry.diagnostic.is_primary
15869 && !entry.range.is_empty()
15870 && entry.range.start == primary_range_start
15871 && entry.diagnostic.message == active_diagnostics.active_message
15872 });
15873
15874 if !is_valid {
15875 self.dismiss_diagnostics(cx);
15876 }
15877 }
15878 }
15879
15880 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15881 match &self.active_diagnostics {
15882 ActiveDiagnostic::Group(group) => Some(group),
15883 _ => None,
15884 }
15885 }
15886
15887 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15888 self.dismiss_diagnostics(cx);
15889 self.active_diagnostics = ActiveDiagnostic::All;
15890 }
15891
15892 fn activate_diagnostics(
15893 &mut self,
15894 buffer_id: BufferId,
15895 diagnostic: DiagnosticEntry<usize>,
15896 window: &mut Window,
15897 cx: &mut Context<Self>,
15898 ) {
15899 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15900 return;
15901 }
15902 self.dismiss_diagnostics(cx);
15903 let snapshot = self.snapshot(window, cx);
15904 let buffer = self.buffer.read(cx).snapshot(cx);
15905 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15906 return;
15907 };
15908
15909 let diagnostic_group = buffer
15910 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15911 .collect::<Vec<_>>();
15912
15913 let blocks =
15914 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15915
15916 let blocks = self.display_map.update(cx, |display_map, cx| {
15917 display_map.insert_blocks(blocks, cx).into_iter().collect()
15918 });
15919 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15920 active_range: buffer.anchor_before(diagnostic.range.start)
15921 ..buffer.anchor_after(diagnostic.range.end),
15922 active_message: diagnostic.diagnostic.message.clone(),
15923 group_id: diagnostic.diagnostic.group_id,
15924 blocks,
15925 });
15926 cx.notify();
15927 }
15928
15929 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15930 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15931 return;
15932 };
15933
15934 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15935 if let ActiveDiagnostic::Group(group) = prev {
15936 self.display_map.update(cx, |display_map, cx| {
15937 display_map.remove_blocks(group.blocks, cx);
15938 });
15939 cx.notify();
15940 }
15941 }
15942
15943 /// Disable inline diagnostics rendering for this editor.
15944 pub fn disable_inline_diagnostics(&mut self) {
15945 self.inline_diagnostics_enabled = false;
15946 self.inline_diagnostics_update = Task::ready(());
15947 self.inline_diagnostics.clear();
15948 }
15949
15950 pub fn diagnostics_enabled(&self) -> bool {
15951 self.mode.is_full()
15952 }
15953
15954 pub fn inline_diagnostics_enabled(&self) -> bool {
15955 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15956 }
15957
15958 pub fn show_inline_diagnostics(&self) -> bool {
15959 self.show_inline_diagnostics
15960 }
15961
15962 pub fn toggle_inline_diagnostics(
15963 &mut self,
15964 _: &ToggleInlineDiagnostics,
15965 window: &mut Window,
15966 cx: &mut Context<Editor>,
15967 ) {
15968 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15969 self.refresh_inline_diagnostics(false, window, cx);
15970 }
15971
15972 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15973 self.diagnostics_max_severity = severity;
15974 self.display_map.update(cx, |display_map, _| {
15975 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15976 });
15977 }
15978
15979 pub fn toggle_diagnostics(
15980 &mut self,
15981 _: &ToggleDiagnostics,
15982 window: &mut Window,
15983 cx: &mut Context<Editor>,
15984 ) {
15985 if !self.diagnostics_enabled() {
15986 return;
15987 }
15988
15989 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15990 EditorSettings::get_global(cx)
15991 .diagnostics_max_severity
15992 .filter(|severity| severity != &DiagnosticSeverity::Off)
15993 .unwrap_or(DiagnosticSeverity::Hint)
15994 } else {
15995 DiagnosticSeverity::Off
15996 };
15997 self.set_max_diagnostics_severity(new_severity, cx);
15998 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15999 self.active_diagnostics = ActiveDiagnostic::None;
16000 self.inline_diagnostics_update = Task::ready(());
16001 self.inline_diagnostics.clear();
16002 } else {
16003 self.refresh_inline_diagnostics(false, window, cx);
16004 }
16005
16006 cx.notify();
16007 }
16008
16009 pub fn toggle_minimap(
16010 &mut self,
16011 _: &ToggleMinimap,
16012 window: &mut Window,
16013 cx: &mut Context<Editor>,
16014 ) {
16015 if self.supports_minimap(cx) {
16016 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16017 }
16018 }
16019
16020 fn refresh_inline_diagnostics(
16021 &mut self,
16022 debounce: bool,
16023 window: &mut Window,
16024 cx: &mut Context<Self>,
16025 ) {
16026 let max_severity = ProjectSettings::get_global(cx)
16027 .diagnostics
16028 .inline
16029 .max_severity
16030 .unwrap_or(self.diagnostics_max_severity);
16031
16032 if !self.inline_diagnostics_enabled()
16033 || !self.show_inline_diagnostics
16034 || max_severity == DiagnosticSeverity::Off
16035 {
16036 self.inline_diagnostics_update = Task::ready(());
16037 self.inline_diagnostics.clear();
16038 return;
16039 }
16040
16041 let debounce_ms = ProjectSettings::get_global(cx)
16042 .diagnostics
16043 .inline
16044 .update_debounce_ms;
16045 let debounce = if debounce && debounce_ms > 0 {
16046 Some(Duration::from_millis(debounce_ms))
16047 } else {
16048 None
16049 };
16050 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16051 if let Some(debounce) = debounce {
16052 cx.background_executor().timer(debounce).await;
16053 }
16054 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16055 editor
16056 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16057 .ok()
16058 }) else {
16059 return;
16060 };
16061
16062 let new_inline_diagnostics = cx
16063 .background_spawn(async move {
16064 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16065 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16066 let message = diagnostic_entry
16067 .diagnostic
16068 .message
16069 .split_once('\n')
16070 .map(|(line, _)| line)
16071 .map(SharedString::new)
16072 .unwrap_or_else(|| {
16073 SharedString::from(diagnostic_entry.diagnostic.message)
16074 });
16075 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16076 let (Ok(i) | Err(i)) = inline_diagnostics
16077 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16078 inline_diagnostics.insert(
16079 i,
16080 (
16081 start_anchor,
16082 InlineDiagnostic {
16083 message,
16084 group_id: diagnostic_entry.diagnostic.group_id,
16085 start: diagnostic_entry.range.start.to_point(&snapshot),
16086 is_primary: diagnostic_entry.diagnostic.is_primary,
16087 severity: diagnostic_entry.diagnostic.severity,
16088 },
16089 ),
16090 );
16091 }
16092 inline_diagnostics
16093 })
16094 .await;
16095
16096 editor
16097 .update(cx, |editor, cx| {
16098 editor.inline_diagnostics = new_inline_diagnostics;
16099 cx.notify();
16100 })
16101 .ok();
16102 });
16103 }
16104
16105 fn pull_diagnostics(
16106 &mut self,
16107 buffer_id: Option<BufferId>,
16108 window: &Window,
16109 cx: &mut Context<Self>,
16110 ) -> Option<()> {
16111 if !self.mode().is_full() {
16112 return None;
16113 }
16114 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16115 .diagnostics
16116 .lsp_pull_diagnostics;
16117 if !pull_diagnostics_settings.enabled {
16118 return None;
16119 }
16120 let project = self.project.as_ref()?.downgrade();
16121 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16122 let mut buffers = self.buffer.read(cx).all_buffers();
16123 if let Some(buffer_id) = buffer_id {
16124 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16125 }
16126
16127 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16128 cx.background_executor().timer(debounce).await;
16129
16130 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16131 buffers
16132 .into_iter()
16133 .flat_map(|buffer| {
16134 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16135 })
16136 .collect::<FuturesUnordered<_>>()
16137 }) else {
16138 return;
16139 };
16140
16141 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16142 match pull_task {
16143 Ok(()) => {
16144 if editor
16145 .update_in(cx, |editor, window, cx| {
16146 editor.update_diagnostics_state(window, cx);
16147 })
16148 .is_err()
16149 {
16150 return;
16151 }
16152 }
16153 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16154 }
16155 }
16156 });
16157
16158 Some(())
16159 }
16160
16161 pub fn set_selections_from_remote(
16162 &mut self,
16163 selections: Vec<Selection<Anchor>>,
16164 pending_selection: Option<Selection<Anchor>>,
16165 window: &mut Window,
16166 cx: &mut Context<Self>,
16167 ) {
16168 let old_cursor_position = self.selections.newest_anchor().head();
16169 self.selections.change_with(cx, |s| {
16170 s.select_anchors(selections);
16171 if let Some(pending_selection) = pending_selection {
16172 s.set_pending(pending_selection, SelectMode::Character);
16173 } else {
16174 s.clear_pending();
16175 }
16176 });
16177 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16178 }
16179
16180 pub fn transact(
16181 &mut self,
16182 window: &mut Window,
16183 cx: &mut Context<Self>,
16184 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16185 ) -> Option<TransactionId> {
16186 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16187 this.start_transaction_at(Instant::now(), window, cx);
16188 update(this, window, cx);
16189 this.end_transaction_at(Instant::now(), cx)
16190 })
16191 }
16192
16193 pub fn start_transaction_at(
16194 &mut self,
16195 now: Instant,
16196 window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 self.end_selection(window, cx);
16200 if let Some(tx_id) = self
16201 .buffer
16202 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16203 {
16204 self.selection_history
16205 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16206 cx.emit(EditorEvent::TransactionBegun {
16207 transaction_id: tx_id,
16208 })
16209 }
16210 }
16211
16212 pub fn end_transaction_at(
16213 &mut self,
16214 now: Instant,
16215 cx: &mut Context<Self>,
16216 ) -> Option<TransactionId> {
16217 if let Some(transaction_id) = self
16218 .buffer
16219 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16220 {
16221 if let Some((_, end_selections)) =
16222 self.selection_history.transaction_mut(transaction_id)
16223 {
16224 *end_selections = Some(self.selections.disjoint_anchors());
16225 } else {
16226 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16227 }
16228
16229 cx.emit(EditorEvent::Edited { transaction_id });
16230 Some(transaction_id)
16231 } else {
16232 None
16233 }
16234 }
16235
16236 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16237 if self.selection_mark_mode {
16238 self.change_selections(None, window, cx, |s| {
16239 s.move_with(|_, sel| {
16240 sel.collapse_to(sel.head(), SelectionGoal::None);
16241 });
16242 })
16243 }
16244 self.selection_mark_mode = true;
16245 cx.notify();
16246 }
16247
16248 pub fn swap_selection_ends(
16249 &mut self,
16250 _: &actions::SwapSelectionEnds,
16251 window: &mut Window,
16252 cx: &mut Context<Self>,
16253 ) {
16254 self.change_selections(None, window, cx, |s| {
16255 s.move_with(|_, sel| {
16256 if sel.start != sel.end {
16257 sel.reversed = !sel.reversed
16258 }
16259 });
16260 });
16261 self.request_autoscroll(Autoscroll::newest(), cx);
16262 cx.notify();
16263 }
16264
16265 pub fn toggle_fold(
16266 &mut self,
16267 _: &actions::ToggleFold,
16268 window: &mut Window,
16269 cx: &mut Context<Self>,
16270 ) {
16271 if self.is_singleton(cx) {
16272 let selection = self.selections.newest::<Point>(cx);
16273
16274 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16275 let range = if selection.is_empty() {
16276 let point = selection.head().to_display_point(&display_map);
16277 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16278 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16279 .to_point(&display_map);
16280 start..end
16281 } else {
16282 selection.range()
16283 };
16284 if display_map.folds_in_range(range).next().is_some() {
16285 self.unfold_lines(&Default::default(), window, cx)
16286 } else {
16287 self.fold(&Default::default(), window, cx)
16288 }
16289 } else {
16290 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16291 let buffer_ids: HashSet<_> = self
16292 .selections
16293 .disjoint_anchor_ranges()
16294 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16295 .collect();
16296
16297 let should_unfold = buffer_ids
16298 .iter()
16299 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16300
16301 for buffer_id in buffer_ids {
16302 if should_unfold {
16303 self.unfold_buffer(buffer_id, cx);
16304 } else {
16305 self.fold_buffer(buffer_id, cx);
16306 }
16307 }
16308 }
16309 }
16310
16311 pub fn toggle_fold_recursive(
16312 &mut self,
16313 _: &actions::ToggleFoldRecursive,
16314 window: &mut Window,
16315 cx: &mut Context<Self>,
16316 ) {
16317 let selection = self.selections.newest::<Point>(cx);
16318
16319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16320 let range = if selection.is_empty() {
16321 let point = selection.head().to_display_point(&display_map);
16322 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16323 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16324 .to_point(&display_map);
16325 start..end
16326 } else {
16327 selection.range()
16328 };
16329 if display_map.folds_in_range(range).next().is_some() {
16330 self.unfold_recursive(&Default::default(), window, cx)
16331 } else {
16332 self.fold_recursive(&Default::default(), window, cx)
16333 }
16334 }
16335
16336 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16337 if self.is_singleton(cx) {
16338 let mut to_fold = Vec::new();
16339 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16340 let selections = self.selections.all_adjusted(cx);
16341
16342 for selection in selections {
16343 let range = selection.range().sorted();
16344 let buffer_start_row = range.start.row;
16345
16346 if range.start.row != range.end.row {
16347 let mut found = false;
16348 let mut row = range.start.row;
16349 while row <= range.end.row {
16350 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16351 {
16352 found = true;
16353 row = crease.range().end.row + 1;
16354 to_fold.push(crease);
16355 } else {
16356 row += 1
16357 }
16358 }
16359 if found {
16360 continue;
16361 }
16362 }
16363
16364 for row in (0..=range.start.row).rev() {
16365 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16366 if crease.range().end.row >= buffer_start_row {
16367 to_fold.push(crease);
16368 if row <= range.start.row {
16369 break;
16370 }
16371 }
16372 }
16373 }
16374 }
16375
16376 self.fold_creases(to_fold, true, window, cx);
16377 } else {
16378 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16379 let buffer_ids = self
16380 .selections
16381 .disjoint_anchor_ranges()
16382 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16383 .collect::<HashSet<_>>();
16384 for buffer_id in buffer_ids {
16385 self.fold_buffer(buffer_id, cx);
16386 }
16387 }
16388 }
16389
16390 fn fold_at_level(
16391 &mut self,
16392 fold_at: &FoldAtLevel,
16393 window: &mut Window,
16394 cx: &mut Context<Self>,
16395 ) {
16396 if !self.buffer.read(cx).is_singleton() {
16397 return;
16398 }
16399
16400 let fold_at_level = fold_at.0;
16401 let snapshot = self.buffer.read(cx).snapshot(cx);
16402 let mut to_fold = Vec::new();
16403 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16404
16405 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16406 while start_row < end_row {
16407 match self
16408 .snapshot(window, cx)
16409 .crease_for_buffer_row(MultiBufferRow(start_row))
16410 {
16411 Some(crease) => {
16412 let nested_start_row = crease.range().start.row + 1;
16413 let nested_end_row = crease.range().end.row;
16414
16415 if current_level < fold_at_level {
16416 stack.push((nested_start_row, nested_end_row, current_level + 1));
16417 } else if current_level == fold_at_level {
16418 to_fold.push(crease);
16419 }
16420
16421 start_row = nested_end_row + 1;
16422 }
16423 None => start_row += 1,
16424 }
16425 }
16426 }
16427
16428 self.fold_creases(to_fold, true, window, cx);
16429 }
16430
16431 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16432 if self.buffer.read(cx).is_singleton() {
16433 let mut fold_ranges = Vec::new();
16434 let snapshot = self.buffer.read(cx).snapshot(cx);
16435
16436 for row in 0..snapshot.max_row().0 {
16437 if let Some(foldable_range) = self
16438 .snapshot(window, cx)
16439 .crease_for_buffer_row(MultiBufferRow(row))
16440 {
16441 fold_ranges.push(foldable_range);
16442 }
16443 }
16444
16445 self.fold_creases(fold_ranges, true, window, cx);
16446 } else {
16447 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16448 editor
16449 .update_in(cx, |editor, _, cx| {
16450 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16451 editor.fold_buffer(buffer_id, cx);
16452 }
16453 })
16454 .ok();
16455 });
16456 }
16457 }
16458
16459 pub fn fold_function_bodies(
16460 &mut self,
16461 _: &actions::FoldFunctionBodies,
16462 window: &mut Window,
16463 cx: &mut Context<Self>,
16464 ) {
16465 let snapshot = self.buffer.read(cx).snapshot(cx);
16466
16467 let ranges = snapshot
16468 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16469 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16470 .collect::<Vec<_>>();
16471
16472 let creases = ranges
16473 .into_iter()
16474 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16475 .collect();
16476
16477 self.fold_creases(creases, true, window, cx);
16478 }
16479
16480 pub fn fold_recursive(
16481 &mut self,
16482 _: &actions::FoldRecursive,
16483 window: &mut Window,
16484 cx: &mut Context<Self>,
16485 ) {
16486 let mut to_fold = Vec::new();
16487 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16488 let selections = self.selections.all_adjusted(cx);
16489
16490 for selection in selections {
16491 let range = selection.range().sorted();
16492 let buffer_start_row = range.start.row;
16493
16494 if range.start.row != range.end.row {
16495 let mut found = false;
16496 for row in range.start.row..=range.end.row {
16497 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16498 found = true;
16499 to_fold.push(crease);
16500 }
16501 }
16502 if found {
16503 continue;
16504 }
16505 }
16506
16507 for row in (0..=range.start.row).rev() {
16508 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16509 if crease.range().end.row >= buffer_start_row {
16510 to_fold.push(crease);
16511 } else {
16512 break;
16513 }
16514 }
16515 }
16516 }
16517
16518 self.fold_creases(to_fold, true, window, cx);
16519 }
16520
16521 pub fn fold_at(
16522 &mut self,
16523 buffer_row: MultiBufferRow,
16524 window: &mut Window,
16525 cx: &mut Context<Self>,
16526 ) {
16527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16528
16529 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16530 let autoscroll = self
16531 .selections
16532 .all::<Point>(cx)
16533 .iter()
16534 .any(|selection| crease.range().overlaps(&selection.range()));
16535
16536 self.fold_creases(vec![crease], autoscroll, window, cx);
16537 }
16538 }
16539
16540 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16541 if self.is_singleton(cx) {
16542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16543 let buffer = &display_map.buffer_snapshot;
16544 let selections = self.selections.all::<Point>(cx);
16545 let ranges = selections
16546 .iter()
16547 .map(|s| {
16548 let range = s.display_range(&display_map).sorted();
16549 let mut start = range.start.to_point(&display_map);
16550 let mut end = range.end.to_point(&display_map);
16551 start.column = 0;
16552 end.column = buffer.line_len(MultiBufferRow(end.row));
16553 start..end
16554 })
16555 .collect::<Vec<_>>();
16556
16557 self.unfold_ranges(&ranges, true, true, cx);
16558 } else {
16559 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16560 let buffer_ids = self
16561 .selections
16562 .disjoint_anchor_ranges()
16563 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16564 .collect::<HashSet<_>>();
16565 for buffer_id in buffer_ids {
16566 self.unfold_buffer(buffer_id, cx);
16567 }
16568 }
16569 }
16570
16571 pub fn unfold_recursive(
16572 &mut self,
16573 _: &UnfoldRecursive,
16574 _window: &mut Window,
16575 cx: &mut Context<Self>,
16576 ) {
16577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16578 let selections = self.selections.all::<Point>(cx);
16579 let ranges = selections
16580 .iter()
16581 .map(|s| {
16582 let mut range = s.display_range(&display_map).sorted();
16583 *range.start.column_mut() = 0;
16584 *range.end.column_mut() = display_map.line_len(range.end.row());
16585 let start = range.start.to_point(&display_map);
16586 let end = range.end.to_point(&display_map);
16587 start..end
16588 })
16589 .collect::<Vec<_>>();
16590
16591 self.unfold_ranges(&ranges, true, true, cx);
16592 }
16593
16594 pub fn unfold_at(
16595 &mut self,
16596 buffer_row: MultiBufferRow,
16597 _window: &mut Window,
16598 cx: &mut Context<Self>,
16599 ) {
16600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16601
16602 let intersection_range = Point::new(buffer_row.0, 0)
16603 ..Point::new(
16604 buffer_row.0,
16605 display_map.buffer_snapshot.line_len(buffer_row),
16606 );
16607
16608 let autoscroll = self
16609 .selections
16610 .all::<Point>(cx)
16611 .iter()
16612 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16613
16614 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16615 }
16616
16617 pub fn unfold_all(
16618 &mut self,
16619 _: &actions::UnfoldAll,
16620 _window: &mut Window,
16621 cx: &mut Context<Self>,
16622 ) {
16623 if self.buffer.read(cx).is_singleton() {
16624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16625 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16626 } else {
16627 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16628 editor
16629 .update(cx, |editor, cx| {
16630 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16631 editor.unfold_buffer(buffer_id, cx);
16632 }
16633 })
16634 .ok();
16635 });
16636 }
16637 }
16638
16639 pub fn fold_selected_ranges(
16640 &mut self,
16641 _: &FoldSelectedRanges,
16642 window: &mut Window,
16643 cx: &mut Context<Self>,
16644 ) {
16645 let selections = self.selections.all_adjusted(cx);
16646 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16647 let ranges = selections
16648 .into_iter()
16649 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16650 .collect::<Vec<_>>();
16651 self.fold_creases(ranges, true, window, cx);
16652 }
16653
16654 pub fn fold_ranges<T: ToOffset + Clone>(
16655 &mut self,
16656 ranges: Vec<Range<T>>,
16657 auto_scroll: bool,
16658 window: &mut Window,
16659 cx: &mut Context<Self>,
16660 ) {
16661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16662 let ranges = ranges
16663 .into_iter()
16664 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16665 .collect::<Vec<_>>();
16666 self.fold_creases(ranges, auto_scroll, window, cx);
16667 }
16668
16669 pub fn fold_creases<T: ToOffset + Clone>(
16670 &mut self,
16671 creases: Vec<Crease<T>>,
16672 auto_scroll: bool,
16673 _window: &mut Window,
16674 cx: &mut Context<Self>,
16675 ) {
16676 if creases.is_empty() {
16677 return;
16678 }
16679
16680 let mut buffers_affected = HashSet::default();
16681 let multi_buffer = self.buffer().read(cx);
16682 for crease in &creases {
16683 if let Some((_, buffer, _)) =
16684 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16685 {
16686 buffers_affected.insert(buffer.read(cx).remote_id());
16687 };
16688 }
16689
16690 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16691
16692 if auto_scroll {
16693 self.request_autoscroll(Autoscroll::fit(), cx);
16694 }
16695
16696 cx.notify();
16697
16698 self.scrollbar_marker_state.dirty = true;
16699 self.folds_did_change(cx);
16700 }
16701
16702 /// Removes any folds whose ranges intersect any of the given ranges.
16703 pub fn unfold_ranges<T: ToOffset + Clone>(
16704 &mut self,
16705 ranges: &[Range<T>],
16706 inclusive: bool,
16707 auto_scroll: bool,
16708 cx: &mut Context<Self>,
16709 ) {
16710 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16711 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16712 });
16713 self.folds_did_change(cx);
16714 }
16715
16716 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16717 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16718 return;
16719 }
16720 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16721 self.display_map.update(cx, |display_map, cx| {
16722 display_map.fold_buffers([buffer_id], cx)
16723 });
16724 cx.emit(EditorEvent::BufferFoldToggled {
16725 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16726 folded: true,
16727 });
16728 cx.notify();
16729 }
16730
16731 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16732 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16733 return;
16734 }
16735 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16736 self.display_map.update(cx, |display_map, cx| {
16737 display_map.unfold_buffers([buffer_id], cx);
16738 });
16739 cx.emit(EditorEvent::BufferFoldToggled {
16740 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16741 folded: false,
16742 });
16743 cx.notify();
16744 }
16745
16746 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16747 self.display_map.read(cx).is_buffer_folded(buffer)
16748 }
16749
16750 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16751 self.display_map.read(cx).folded_buffers()
16752 }
16753
16754 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16755 self.display_map.update(cx, |display_map, cx| {
16756 display_map.disable_header_for_buffer(buffer_id, cx);
16757 });
16758 cx.notify();
16759 }
16760
16761 /// Removes any folds with the given ranges.
16762 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16763 &mut self,
16764 ranges: &[Range<T>],
16765 type_id: TypeId,
16766 auto_scroll: bool,
16767 cx: &mut Context<Self>,
16768 ) {
16769 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16770 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16771 });
16772 self.folds_did_change(cx);
16773 }
16774
16775 fn remove_folds_with<T: ToOffset + Clone>(
16776 &mut self,
16777 ranges: &[Range<T>],
16778 auto_scroll: bool,
16779 cx: &mut Context<Self>,
16780 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16781 ) {
16782 if ranges.is_empty() {
16783 return;
16784 }
16785
16786 let mut buffers_affected = HashSet::default();
16787 let multi_buffer = self.buffer().read(cx);
16788 for range in ranges {
16789 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16790 buffers_affected.insert(buffer.read(cx).remote_id());
16791 };
16792 }
16793
16794 self.display_map.update(cx, update);
16795
16796 if auto_scroll {
16797 self.request_autoscroll(Autoscroll::fit(), cx);
16798 }
16799
16800 cx.notify();
16801 self.scrollbar_marker_state.dirty = true;
16802 self.active_indent_guides_state.dirty = true;
16803 }
16804
16805 pub fn update_fold_widths(
16806 &mut self,
16807 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16808 cx: &mut Context<Self>,
16809 ) -> bool {
16810 self.display_map
16811 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16812 }
16813
16814 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16815 self.display_map.read(cx).fold_placeholder.clone()
16816 }
16817
16818 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16819 self.buffer.update(cx, |buffer, cx| {
16820 buffer.set_all_diff_hunks_expanded(cx);
16821 });
16822 }
16823
16824 pub fn expand_all_diff_hunks(
16825 &mut self,
16826 _: &ExpandAllDiffHunks,
16827 _window: &mut Window,
16828 cx: &mut Context<Self>,
16829 ) {
16830 self.buffer.update(cx, |buffer, cx| {
16831 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16832 });
16833 }
16834
16835 pub fn toggle_selected_diff_hunks(
16836 &mut self,
16837 _: &ToggleSelectedDiffHunks,
16838 _window: &mut Window,
16839 cx: &mut Context<Self>,
16840 ) {
16841 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16842 self.toggle_diff_hunks_in_ranges(ranges, cx);
16843 }
16844
16845 pub fn diff_hunks_in_ranges<'a>(
16846 &'a self,
16847 ranges: &'a [Range<Anchor>],
16848 buffer: &'a MultiBufferSnapshot,
16849 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16850 ranges.iter().flat_map(move |range| {
16851 let end_excerpt_id = range.end.excerpt_id;
16852 let range = range.to_point(buffer);
16853 let mut peek_end = range.end;
16854 if range.end.row < buffer.max_row().0 {
16855 peek_end = Point::new(range.end.row + 1, 0);
16856 }
16857 buffer
16858 .diff_hunks_in_range(range.start..peek_end)
16859 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16860 })
16861 }
16862
16863 pub fn has_stageable_diff_hunks_in_ranges(
16864 &self,
16865 ranges: &[Range<Anchor>],
16866 snapshot: &MultiBufferSnapshot,
16867 ) -> bool {
16868 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16869 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16870 }
16871
16872 pub fn toggle_staged_selected_diff_hunks(
16873 &mut self,
16874 _: &::git::ToggleStaged,
16875 _: &mut Window,
16876 cx: &mut Context<Self>,
16877 ) {
16878 let snapshot = self.buffer.read(cx).snapshot(cx);
16879 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16880 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16881 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16882 }
16883
16884 pub fn set_render_diff_hunk_controls(
16885 &mut self,
16886 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16887 cx: &mut Context<Self>,
16888 ) {
16889 self.render_diff_hunk_controls = render_diff_hunk_controls;
16890 cx.notify();
16891 }
16892
16893 pub fn stage_and_next(
16894 &mut self,
16895 _: &::git::StageAndNext,
16896 window: &mut Window,
16897 cx: &mut Context<Self>,
16898 ) {
16899 self.do_stage_or_unstage_and_next(true, window, cx);
16900 }
16901
16902 pub fn unstage_and_next(
16903 &mut self,
16904 _: &::git::UnstageAndNext,
16905 window: &mut Window,
16906 cx: &mut Context<Self>,
16907 ) {
16908 self.do_stage_or_unstage_and_next(false, window, cx);
16909 }
16910
16911 pub fn stage_or_unstage_diff_hunks(
16912 &mut self,
16913 stage: bool,
16914 ranges: Vec<Range<Anchor>>,
16915 cx: &mut Context<Self>,
16916 ) {
16917 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16918 cx.spawn(async move |this, cx| {
16919 task.await?;
16920 this.update(cx, |this, cx| {
16921 let snapshot = this.buffer.read(cx).snapshot(cx);
16922 let chunk_by = this
16923 .diff_hunks_in_ranges(&ranges, &snapshot)
16924 .chunk_by(|hunk| hunk.buffer_id);
16925 for (buffer_id, hunks) in &chunk_by {
16926 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16927 }
16928 })
16929 })
16930 .detach_and_log_err(cx);
16931 }
16932
16933 fn save_buffers_for_ranges_if_needed(
16934 &mut self,
16935 ranges: &[Range<Anchor>],
16936 cx: &mut Context<Editor>,
16937 ) -> Task<Result<()>> {
16938 let multibuffer = self.buffer.read(cx);
16939 let snapshot = multibuffer.read(cx);
16940 let buffer_ids: HashSet<_> = ranges
16941 .iter()
16942 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16943 .collect();
16944 drop(snapshot);
16945
16946 let mut buffers = HashSet::default();
16947 for buffer_id in buffer_ids {
16948 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16949 let buffer = buffer_entity.read(cx);
16950 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16951 {
16952 buffers.insert(buffer_entity);
16953 }
16954 }
16955 }
16956
16957 if let Some(project) = &self.project {
16958 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16959 } else {
16960 Task::ready(Ok(()))
16961 }
16962 }
16963
16964 fn do_stage_or_unstage_and_next(
16965 &mut self,
16966 stage: bool,
16967 window: &mut Window,
16968 cx: &mut Context<Self>,
16969 ) {
16970 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16971
16972 if ranges.iter().any(|range| range.start != range.end) {
16973 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16974 return;
16975 }
16976
16977 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16978 let snapshot = self.snapshot(window, cx);
16979 let position = self.selections.newest::<Point>(cx).head();
16980 let mut row = snapshot
16981 .buffer_snapshot
16982 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16983 .find(|hunk| hunk.row_range.start.0 > position.row)
16984 .map(|hunk| hunk.row_range.start);
16985
16986 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16987 // Outside of the project diff editor, wrap around to the beginning.
16988 if !all_diff_hunks_expanded {
16989 row = row.or_else(|| {
16990 snapshot
16991 .buffer_snapshot
16992 .diff_hunks_in_range(Point::zero()..position)
16993 .find(|hunk| hunk.row_range.end.0 < position.row)
16994 .map(|hunk| hunk.row_range.start)
16995 });
16996 }
16997
16998 if let Some(row) = row {
16999 let destination = Point::new(row.0, 0);
17000 let autoscroll = Autoscroll::center();
17001
17002 self.unfold_ranges(&[destination..destination], false, false, cx);
17003 self.change_selections(Some(autoscroll), window, cx, |s| {
17004 s.select_ranges([destination..destination]);
17005 });
17006 }
17007 }
17008
17009 fn do_stage_or_unstage(
17010 &self,
17011 stage: bool,
17012 buffer_id: BufferId,
17013 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17014 cx: &mut App,
17015 ) -> Option<()> {
17016 let project = self.project.as_ref()?;
17017 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17018 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17019 let buffer_snapshot = buffer.read(cx).snapshot();
17020 let file_exists = buffer_snapshot
17021 .file()
17022 .is_some_and(|file| file.disk_state().exists());
17023 diff.update(cx, |diff, cx| {
17024 diff.stage_or_unstage_hunks(
17025 stage,
17026 &hunks
17027 .map(|hunk| buffer_diff::DiffHunk {
17028 buffer_range: hunk.buffer_range,
17029 diff_base_byte_range: hunk.diff_base_byte_range,
17030 secondary_status: hunk.secondary_status,
17031 range: Point::zero()..Point::zero(), // unused
17032 })
17033 .collect::<Vec<_>>(),
17034 &buffer_snapshot,
17035 file_exists,
17036 cx,
17037 )
17038 });
17039 None
17040 }
17041
17042 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17043 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17044 self.buffer
17045 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17046 }
17047
17048 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17049 self.buffer.update(cx, |buffer, cx| {
17050 let ranges = vec![Anchor::min()..Anchor::max()];
17051 if !buffer.all_diff_hunks_expanded()
17052 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17053 {
17054 buffer.collapse_diff_hunks(ranges, cx);
17055 true
17056 } else {
17057 false
17058 }
17059 })
17060 }
17061
17062 fn toggle_diff_hunks_in_ranges(
17063 &mut self,
17064 ranges: Vec<Range<Anchor>>,
17065 cx: &mut Context<Editor>,
17066 ) {
17067 self.buffer.update(cx, |buffer, cx| {
17068 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17069 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17070 })
17071 }
17072
17073 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17074 self.buffer.update(cx, |buffer, cx| {
17075 let snapshot = buffer.snapshot(cx);
17076 let excerpt_id = range.end.excerpt_id;
17077 let point_range = range.to_point(&snapshot);
17078 let expand = !buffer.single_hunk_is_expanded(range, cx);
17079 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17080 })
17081 }
17082
17083 pub(crate) fn apply_all_diff_hunks(
17084 &mut self,
17085 _: &ApplyAllDiffHunks,
17086 window: &mut Window,
17087 cx: &mut Context<Self>,
17088 ) {
17089 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17090
17091 let buffers = self.buffer.read(cx).all_buffers();
17092 for branch_buffer in buffers {
17093 branch_buffer.update(cx, |branch_buffer, cx| {
17094 branch_buffer.merge_into_base(Vec::new(), cx);
17095 });
17096 }
17097
17098 if let Some(project) = self.project.clone() {
17099 self.save(true, project, window, cx).detach_and_log_err(cx);
17100 }
17101 }
17102
17103 pub(crate) fn apply_selected_diff_hunks(
17104 &mut self,
17105 _: &ApplyDiffHunk,
17106 window: &mut Window,
17107 cx: &mut Context<Self>,
17108 ) {
17109 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17110 let snapshot = self.snapshot(window, cx);
17111 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17112 let mut ranges_by_buffer = HashMap::default();
17113 self.transact(window, cx, |editor, _window, cx| {
17114 for hunk in hunks {
17115 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17116 ranges_by_buffer
17117 .entry(buffer.clone())
17118 .or_insert_with(Vec::new)
17119 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17120 }
17121 }
17122
17123 for (buffer, ranges) in ranges_by_buffer {
17124 buffer.update(cx, |buffer, cx| {
17125 buffer.merge_into_base(ranges, cx);
17126 });
17127 }
17128 });
17129
17130 if let Some(project) = self.project.clone() {
17131 self.save(true, project, window, cx).detach_and_log_err(cx);
17132 }
17133 }
17134
17135 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17136 if hovered != self.gutter_hovered {
17137 self.gutter_hovered = hovered;
17138 cx.notify();
17139 }
17140 }
17141
17142 pub fn insert_blocks(
17143 &mut self,
17144 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17145 autoscroll: Option<Autoscroll>,
17146 cx: &mut Context<Self>,
17147 ) -> Vec<CustomBlockId> {
17148 let blocks = self
17149 .display_map
17150 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17151 if let Some(autoscroll) = autoscroll {
17152 self.request_autoscroll(autoscroll, cx);
17153 }
17154 cx.notify();
17155 blocks
17156 }
17157
17158 pub fn resize_blocks(
17159 &mut self,
17160 heights: HashMap<CustomBlockId, u32>,
17161 autoscroll: Option<Autoscroll>,
17162 cx: &mut Context<Self>,
17163 ) {
17164 self.display_map
17165 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17166 if let Some(autoscroll) = autoscroll {
17167 self.request_autoscroll(autoscroll, cx);
17168 }
17169 cx.notify();
17170 }
17171
17172 pub fn replace_blocks(
17173 &mut self,
17174 renderers: HashMap<CustomBlockId, RenderBlock>,
17175 autoscroll: Option<Autoscroll>,
17176 cx: &mut Context<Self>,
17177 ) {
17178 self.display_map
17179 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17180 if let Some(autoscroll) = autoscroll {
17181 self.request_autoscroll(autoscroll, cx);
17182 }
17183 cx.notify();
17184 }
17185
17186 pub fn remove_blocks(
17187 &mut self,
17188 block_ids: HashSet<CustomBlockId>,
17189 autoscroll: Option<Autoscroll>,
17190 cx: &mut Context<Self>,
17191 ) {
17192 self.display_map.update(cx, |display_map, cx| {
17193 display_map.remove_blocks(block_ids, cx)
17194 });
17195 if let Some(autoscroll) = autoscroll {
17196 self.request_autoscroll(autoscroll, cx);
17197 }
17198 cx.notify();
17199 }
17200
17201 pub fn row_for_block(
17202 &self,
17203 block_id: CustomBlockId,
17204 cx: &mut Context<Self>,
17205 ) -> Option<DisplayRow> {
17206 self.display_map
17207 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17208 }
17209
17210 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17211 self.focused_block = Some(focused_block);
17212 }
17213
17214 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17215 self.focused_block.take()
17216 }
17217
17218 pub fn insert_creases(
17219 &mut self,
17220 creases: impl IntoIterator<Item = Crease<Anchor>>,
17221 cx: &mut Context<Self>,
17222 ) -> Vec<CreaseId> {
17223 self.display_map
17224 .update(cx, |map, cx| map.insert_creases(creases, cx))
17225 }
17226
17227 pub fn remove_creases(
17228 &mut self,
17229 ids: impl IntoIterator<Item = CreaseId>,
17230 cx: &mut Context<Self>,
17231 ) -> Vec<(CreaseId, Range<Anchor>)> {
17232 self.display_map
17233 .update(cx, |map, cx| map.remove_creases(ids, cx))
17234 }
17235
17236 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17237 self.display_map
17238 .update(cx, |map, cx| map.snapshot(cx))
17239 .longest_row()
17240 }
17241
17242 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17243 self.display_map
17244 .update(cx, |map, cx| map.snapshot(cx))
17245 .max_point()
17246 }
17247
17248 pub fn text(&self, cx: &App) -> String {
17249 self.buffer.read(cx).read(cx).text()
17250 }
17251
17252 pub fn is_empty(&self, cx: &App) -> bool {
17253 self.buffer.read(cx).read(cx).is_empty()
17254 }
17255
17256 pub fn text_option(&self, cx: &App) -> Option<String> {
17257 let text = self.text(cx);
17258 let text = text.trim();
17259
17260 if text.is_empty() {
17261 return None;
17262 }
17263
17264 Some(text.to_string())
17265 }
17266
17267 pub fn set_text(
17268 &mut self,
17269 text: impl Into<Arc<str>>,
17270 window: &mut Window,
17271 cx: &mut Context<Self>,
17272 ) {
17273 self.transact(window, cx, |this, _, cx| {
17274 this.buffer
17275 .read(cx)
17276 .as_singleton()
17277 .expect("you can only call set_text on editors for singleton buffers")
17278 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17279 });
17280 }
17281
17282 pub fn display_text(&self, cx: &mut App) -> String {
17283 self.display_map
17284 .update(cx, |map, cx| map.snapshot(cx))
17285 .text()
17286 }
17287
17288 fn create_minimap(
17289 &self,
17290 minimap_settings: MinimapSettings,
17291 window: &mut Window,
17292 cx: &mut Context<Self>,
17293 ) -> Option<Entity<Self>> {
17294 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17295 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17296 }
17297
17298 fn initialize_new_minimap(
17299 &self,
17300 minimap_settings: MinimapSettings,
17301 window: &mut Window,
17302 cx: &mut Context<Self>,
17303 ) -> Entity<Self> {
17304 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17305
17306 let mut minimap = Editor::new_internal(
17307 EditorMode::Minimap {
17308 parent: cx.weak_entity(),
17309 },
17310 self.buffer.clone(),
17311 self.project.clone(),
17312 Some(self.display_map.clone()),
17313 window,
17314 cx,
17315 );
17316 minimap.scroll_manager.clone_state(&self.scroll_manager);
17317 minimap.set_text_style_refinement(TextStyleRefinement {
17318 font_size: Some(MINIMAP_FONT_SIZE),
17319 font_weight: Some(MINIMAP_FONT_WEIGHT),
17320 ..Default::default()
17321 });
17322 minimap.update_minimap_configuration(minimap_settings, cx);
17323 cx.new(|_| minimap)
17324 }
17325
17326 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17327 let current_line_highlight = minimap_settings
17328 .current_line_highlight
17329 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17330 self.set_current_line_highlight(Some(current_line_highlight));
17331 }
17332
17333 pub fn minimap(&self) -> Option<&Entity<Self>> {
17334 self.minimap
17335 .as_ref()
17336 .filter(|_| self.minimap_visibility.visible())
17337 }
17338
17339 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17340 let mut wrap_guides = smallvec![];
17341
17342 if self.show_wrap_guides == Some(false) {
17343 return wrap_guides;
17344 }
17345
17346 let settings = self.buffer.read(cx).language_settings(cx);
17347 if settings.show_wrap_guides {
17348 match self.soft_wrap_mode(cx) {
17349 SoftWrap::Column(soft_wrap) => {
17350 wrap_guides.push((soft_wrap as usize, true));
17351 }
17352 SoftWrap::Bounded(soft_wrap) => {
17353 wrap_guides.push((soft_wrap as usize, true));
17354 }
17355 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17356 }
17357 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17358 }
17359
17360 wrap_guides
17361 }
17362
17363 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17364 let settings = self.buffer.read(cx).language_settings(cx);
17365 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17366 match mode {
17367 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17368 SoftWrap::None
17369 }
17370 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17371 language_settings::SoftWrap::PreferredLineLength => {
17372 SoftWrap::Column(settings.preferred_line_length)
17373 }
17374 language_settings::SoftWrap::Bounded => {
17375 SoftWrap::Bounded(settings.preferred_line_length)
17376 }
17377 }
17378 }
17379
17380 pub fn set_soft_wrap_mode(
17381 &mut self,
17382 mode: language_settings::SoftWrap,
17383
17384 cx: &mut Context<Self>,
17385 ) {
17386 self.soft_wrap_mode_override = Some(mode);
17387 cx.notify();
17388 }
17389
17390 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17391 self.hard_wrap = hard_wrap;
17392 cx.notify();
17393 }
17394
17395 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17396 self.text_style_refinement = Some(style);
17397 }
17398
17399 /// called by the Element so we know what style we were most recently rendered with.
17400 pub(crate) fn set_style(
17401 &mut self,
17402 style: EditorStyle,
17403 window: &mut Window,
17404 cx: &mut Context<Self>,
17405 ) {
17406 // We intentionally do not inform the display map about the minimap style
17407 // so that wrapping is not recalculated and stays consistent for the editor
17408 // and its linked minimap.
17409 if !self.mode.is_minimap() {
17410 let rem_size = window.rem_size();
17411 self.display_map.update(cx, |map, cx| {
17412 map.set_font(
17413 style.text.font(),
17414 style.text.font_size.to_pixels(rem_size),
17415 cx,
17416 )
17417 });
17418 }
17419 self.style = Some(style);
17420 }
17421
17422 pub fn style(&self) -> Option<&EditorStyle> {
17423 self.style.as_ref()
17424 }
17425
17426 // Called by the element. This method is not designed to be called outside of the editor
17427 // element's layout code because it does not notify when rewrapping is computed synchronously.
17428 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17429 self.display_map
17430 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17431 }
17432
17433 pub fn set_soft_wrap(&mut self) {
17434 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17435 }
17436
17437 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17438 if self.soft_wrap_mode_override.is_some() {
17439 self.soft_wrap_mode_override.take();
17440 } else {
17441 let soft_wrap = match self.soft_wrap_mode(cx) {
17442 SoftWrap::GitDiff => return,
17443 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17444 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17445 language_settings::SoftWrap::None
17446 }
17447 };
17448 self.soft_wrap_mode_override = Some(soft_wrap);
17449 }
17450 cx.notify();
17451 }
17452
17453 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17454 let Some(workspace) = self.workspace() else {
17455 return;
17456 };
17457 let fs = workspace.read(cx).app_state().fs.clone();
17458 let current_show = TabBarSettings::get_global(cx).show;
17459 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17460 setting.show = Some(!current_show);
17461 });
17462 }
17463
17464 pub fn toggle_indent_guides(
17465 &mut self,
17466 _: &ToggleIndentGuides,
17467 _: &mut Window,
17468 cx: &mut Context<Self>,
17469 ) {
17470 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17471 self.buffer
17472 .read(cx)
17473 .language_settings(cx)
17474 .indent_guides
17475 .enabled
17476 });
17477 self.show_indent_guides = Some(!currently_enabled);
17478 cx.notify();
17479 }
17480
17481 fn should_show_indent_guides(&self) -> Option<bool> {
17482 self.show_indent_guides
17483 }
17484
17485 pub fn toggle_line_numbers(
17486 &mut self,
17487 _: &ToggleLineNumbers,
17488 _: &mut Window,
17489 cx: &mut Context<Self>,
17490 ) {
17491 let mut editor_settings = EditorSettings::get_global(cx).clone();
17492 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17493 EditorSettings::override_global(editor_settings, cx);
17494 }
17495
17496 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17497 if let Some(show_line_numbers) = self.show_line_numbers {
17498 return show_line_numbers;
17499 }
17500 EditorSettings::get_global(cx).gutter.line_numbers
17501 }
17502
17503 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17504 self.use_relative_line_numbers
17505 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17506 }
17507
17508 pub fn toggle_relative_line_numbers(
17509 &mut self,
17510 _: &ToggleRelativeLineNumbers,
17511 _: &mut Window,
17512 cx: &mut Context<Self>,
17513 ) {
17514 let is_relative = self.should_use_relative_line_numbers(cx);
17515 self.set_relative_line_number(Some(!is_relative), cx)
17516 }
17517
17518 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17519 self.use_relative_line_numbers = is_relative;
17520 cx.notify();
17521 }
17522
17523 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17524 self.show_gutter = show_gutter;
17525 cx.notify();
17526 }
17527
17528 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17529 self.show_scrollbars = ScrollbarAxes {
17530 horizontal: show,
17531 vertical: show,
17532 };
17533 cx.notify();
17534 }
17535
17536 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17537 self.show_scrollbars.vertical = show;
17538 cx.notify();
17539 }
17540
17541 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17542 self.show_scrollbars.horizontal = show;
17543 cx.notify();
17544 }
17545
17546 pub fn set_minimap_visibility(
17547 &mut self,
17548 minimap_visibility: MinimapVisibility,
17549 window: &mut Window,
17550 cx: &mut Context<Self>,
17551 ) {
17552 if self.minimap_visibility != minimap_visibility {
17553 if minimap_visibility.visible() && self.minimap.is_none() {
17554 let minimap_settings = EditorSettings::get_global(cx).minimap;
17555 self.minimap =
17556 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17557 }
17558 self.minimap_visibility = minimap_visibility;
17559 cx.notify();
17560 }
17561 }
17562
17563 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17564 self.set_show_scrollbars(false, cx);
17565 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17566 }
17567
17568 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17569 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17570 }
17571
17572 /// Normally the text in full mode and auto height editors is padded on the
17573 /// left side by roughly half a character width for improved hit testing.
17574 ///
17575 /// Use this method to disable this for cases where this is not wanted (e.g.
17576 /// if you want to align the editor text with some other text above or below)
17577 /// or if you want to add this padding to single-line editors.
17578 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17579 self.offset_content = offset_content;
17580 cx.notify();
17581 }
17582
17583 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17584 self.show_line_numbers = Some(show_line_numbers);
17585 cx.notify();
17586 }
17587
17588 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17589 self.disable_expand_excerpt_buttons = true;
17590 cx.notify();
17591 }
17592
17593 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17594 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17595 cx.notify();
17596 }
17597
17598 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17599 self.show_code_actions = Some(show_code_actions);
17600 cx.notify();
17601 }
17602
17603 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17604 self.show_runnables = Some(show_runnables);
17605 cx.notify();
17606 }
17607
17608 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17609 self.show_breakpoints = Some(show_breakpoints);
17610 cx.notify();
17611 }
17612
17613 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17614 if self.display_map.read(cx).masked != masked {
17615 self.display_map.update(cx, |map, _| map.masked = masked);
17616 }
17617 cx.notify()
17618 }
17619
17620 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17621 self.show_wrap_guides = Some(show_wrap_guides);
17622 cx.notify();
17623 }
17624
17625 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17626 self.show_indent_guides = Some(show_indent_guides);
17627 cx.notify();
17628 }
17629
17630 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17631 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17632 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17633 if let Some(dir) = file.abs_path(cx).parent() {
17634 return Some(dir.to_owned());
17635 }
17636 }
17637
17638 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17639 return Some(project_path.path.to_path_buf());
17640 }
17641 }
17642
17643 None
17644 }
17645
17646 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17647 self.active_excerpt(cx)?
17648 .1
17649 .read(cx)
17650 .file()
17651 .and_then(|f| f.as_local())
17652 }
17653
17654 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17655 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17656 let buffer = buffer.read(cx);
17657 if let Some(project_path) = buffer.project_path(cx) {
17658 let project = self.project.as_ref()?.read(cx);
17659 project.absolute_path(&project_path, cx)
17660 } else {
17661 buffer
17662 .file()
17663 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17664 }
17665 })
17666 }
17667
17668 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17669 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17670 let project_path = buffer.read(cx).project_path(cx)?;
17671 let project = self.project.as_ref()?.read(cx);
17672 let entry = project.entry_for_path(&project_path, cx)?;
17673 let path = entry.path.to_path_buf();
17674 Some(path)
17675 })
17676 }
17677
17678 pub fn reveal_in_finder(
17679 &mut self,
17680 _: &RevealInFileManager,
17681 _window: &mut Window,
17682 cx: &mut Context<Self>,
17683 ) {
17684 if let Some(target) = self.target_file(cx) {
17685 cx.reveal_path(&target.abs_path(cx));
17686 }
17687 }
17688
17689 pub fn copy_path(
17690 &mut self,
17691 _: &zed_actions::workspace::CopyPath,
17692 _window: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) {
17695 if let Some(path) = self.target_file_abs_path(cx) {
17696 if let Some(path) = path.to_str() {
17697 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17698 }
17699 }
17700 }
17701
17702 pub fn copy_relative_path(
17703 &mut self,
17704 _: &zed_actions::workspace::CopyRelativePath,
17705 _window: &mut Window,
17706 cx: &mut Context<Self>,
17707 ) {
17708 if let Some(path) = self.target_file_path(cx) {
17709 if let Some(path) = path.to_str() {
17710 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17711 }
17712 }
17713 }
17714
17715 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17716 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17717 buffer.read(cx).project_path(cx)
17718 } else {
17719 None
17720 }
17721 }
17722
17723 // Returns true if the editor handled a go-to-line request
17724 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17725 maybe!({
17726 let breakpoint_store = self.breakpoint_store.as_ref()?;
17727
17728 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17729 else {
17730 self.clear_row_highlights::<ActiveDebugLine>();
17731 return None;
17732 };
17733
17734 let position = active_stack_frame.position;
17735 let buffer_id = position.buffer_id?;
17736 let snapshot = self
17737 .project
17738 .as_ref()?
17739 .read(cx)
17740 .buffer_for_id(buffer_id, cx)?
17741 .read(cx)
17742 .snapshot();
17743
17744 let mut handled = false;
17745 for (id, ExcerptRange { context, .. }) in
17746 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17747 {
17748 if context.start.cmp(&position, &snapshot).is_ge()
17749 || context.end.cmp(&position, &snapshot).is_lt()
17750 {
17751 continue;
17752 }
17753 let snapshot = self.buffer.read(cx).snapshot(cx);
17754 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17755
17756 handled = true;
17757 self.clear_row_highlights::<ActiveDebugLine>();
17758
17759 self.go_to_line::<ActiveDebugLine>(
17760 multibuffer_anchor,
17761 Some(cx.theme().colors().editor_debugger_active_line_background),
17762 window,
17763 cx,
17764 );
17765
17766 cx.notify();
17767 }
17768
17769 handled.then_some(())
17770 })
17771 .is_some()
17772 }
17773
17774 pub fn copy_file_name_without_extension(
17775 &mut self,
17776 _: &CopyFileNameWithoutExtension,
17777 _: &mut Window,
17778 cx: &mut Context<Self>,
17779 ) {
17780 if let Some(file) = self.target_file(cx) {
17781 if let Some(file_stem) = file.path().file_stem() {
17782 if let Some(name) = file_stem.to_str() {
17783 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17784 }
17785 }
17786 }
17787 }
17788
17789 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17790 if let Some(file) = self.target_file(cx) {
17791 if let Some(file_name) = file.path().file_name() {
17792 if let Some(name) = file_name.to_str() {
17793 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17794 }
17795 }
17796 }
17797 }
17798
17799 pub fn toggle_git_blame(
17800 &mut self,
17801 _: &::git::Blame,
17802 window: &mut Window,
17803 cx: &mut Context<Self>,
17804 ) {
17805 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17806
17807 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17808 self.start_git_blame(true, window, cx);
17809 }
17810
17811 cx.notify();
17812 }
17813
17814 pub fn toggle_git_blame_inline(
17815 &mut self,
17816 _: &ToggleGitBlameInline,
17817 window: &mut Window,
17818 cx: &mut Context<Self>,
17819 ) {
17820 self.toggle_git_blame_inline_internal(true, window, cx);
17821 cx.notify();
17822 }
17823
17824 pub fn open_git_blame_commit(
17825 &mut self,
17826 _: &OpenGitBlameCommit,
17827 window: &mut Window,
17828 cx: &mut Context<Self>,
17829 ) {
17830 self.open_git_blame_commit_internal(window, cx);
17831 }
17832
17833 fn open_git_blame_commit_internal(
17834 &mut self,
17835 window: &mut Window,
17836 cx: &mut Context<Self>,
17837 ) -> Option<()> {
17838 let blame = self.blame.as_ref()?;
17839 let snapshot = self.snapshot(window, cx);
17840 let cursor = self.selections.newest::<Point>(cx).head();
17841 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17842 let blame_entry = blame
17843 .update(cx, |blame, cx| {
17844 blame
17845 .blame_for_rows(
17846 &[RowInfo {
17847 buffer_id: Some(buffer.remote_id()),
17848 buffer_row: Some(point.row),
17849 ..Default::default()
17850 }],
17851 cx,
17852 )
17853 .next()
17854 })
17855 .flatten()?;
17856 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17857 let repo = blame.read(cx).repository(cx)?;
17858 let workspace = self.workspace()?.downgrade();
17859 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17860 None
17861 }
17862
17863 pub fn git_blame_inline_enabled(&self) -> bool {
17864 self.git_blame_inline_enabled
17865 }
17866
17867 pub fn toggle_selection_menu(
17868 &mut self,
17869 _: &ToggleSelectionMenu,
17870 _: &mut Window,
17871 cx: &mut Context<Self>,
17872 ) {
17873 self.show_selection_menu = self
17874 .show_selection_menu
17875 .map(|show_selections_menu| !show_selections_menu)
17876 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17877
17878 cx.notify();
17879 }
17880
17881 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17882 self.show_selection_menu
17883 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17884 }
17885
17886 fn start_git_blame(
17887 &mut self,
17888 user_triggered: bool,
17889 window: &mut Window,
17890 cx: &mut Context<Self>,
17891 ) {
17892 if let Some(project) = self.project.as_ref() {
17893 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17894 return;
17895 };
17896
17897 if buffer.read(cx).file().is_none() {
17898 return;
17899 }
17900
17901 let focused = self.focus_handle(cx).contains_focused(window, cx);
17902
17903 let project = project.clone();
17904 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17905 self.blame_subscription =
17906 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17907 self.blame = Some(blame);
17908 }
17909 }
17910
17911 fn toggle_git_blame_inline_internal(
17912 &mut self,
17913 user_triggered: bool,
17914 window: &mut Window,
17915 cx: &mut Context<Self>,
17916 ) {
17917 if self.git_blame_inline_enabled {
17918 self.git_blame_inline_enabled = false;
17919 self.show_git_blame_inline = false;
17920 self.show_git_blame_inline_delay_task.take();
17921 } else {
17922 self.git_blame_inline_enabled = true;
17923 self.start_git_blame_inline(user_triggered, window, cx);
17924 }
17925
17926 cx.notify();
17927 }
17928
17929 fn start_git_blame_inline(
17930 &mut self,
17931 user_triggered: bool,
17932 window: &mut Window,
17933 cx: &mut Context<Self>,
17934 ) {
17935 self.start_git_blame(user_triggered, window, cx);
17936
17937 if ProjectSettings::get_global(cx)
17938 .git
17939 .inline_blame_delay()
17940 .is_some()
17941 {
17942 self.start_inline_blame_timer(window, cx);
17943 } else {
17944 self.show_git_blame_inline = true
17945 }
17946 }
17947
17948 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17949 self.blame.as_ref()
17950 }
17951
17952 pub fn show_git_blame_gutter(&self) -> bool {
17953 self.show_git_blame_gutter
17954 }
17955
17956 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17957 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17958 }
17959
17960 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17961 self.show_git_blame_inline
17962 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17963 && !self.newest_selection_head_on_empty_line(cx)
17964 && self.has_blame_entries(cx)
17965 }
17966
17967 fn has_blame_entries(&self, cx: &App) -> bool {
17968 self.blame()
17969 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17970 }
17971
17972 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17973 let cursor_anchor = self.selections.newest_anchor().head();
17974
17975 let snapshot = self.buffer.read(cx).snapshot(cx);
17976 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17977
17978 snapshot.line_len(buffer_row) == 0
17979 }
17980
17981 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17982 let buffer_and_selection = maybe!({
17983 let selection = self.selections.newest::<Point>(cx);
17984 let selection_range = selection.range();
17985
17986 let multi_buffer = self.buffer().read(cx);
17987 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17988 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17989
17990 let (buffer, range, _) = if selection.reversed {
17991 buffer_ranges.first()
17992 } else {
17993 buffer_ranges.last()
17994 }?;
17995
17996 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17997 ..text::ToPoint::to_point(&range.end, &buffer).row;
17998 Some((
17999 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18000 selection,
18001 ))
18002 });
18003
18004 let Some((buffer, selection)) = buffer_and_selection else {
18005 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18006 };
18007
18008 let Some(project) = self.project.as_ref() else {
18009 return Task::ready(Err(anyhow!("editor does not have project")));
18010 };
18011
18012 project.update(cx, |project, cx| {
18013 project.get_permalink_to_line(&buffer, selection, cx)
18014 })
18015 }
18016
18017 pub fn copy_permalink_to_line(
18018 &mut self,
18019 _: &CopyPermalinkToLine,
18020 window: &mut Window,
18021 cx: &mut Context<Self>,
18022 ) {
18023 let permalink_task = self.get_permalink_to_line(cx);
18024 let workspace = self.workspace();
18025
18026 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18027 Ok(permalink) => {
18028 cx.update(|_, cx| {
18029 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18030 })
18031 .ok();
18032 }
18033 Err(err) => {
18034 let message = format!("Failed to copy permalink: {err}");
18035
18036 anyhow::Result::<()>::Err(err).log_err();
18037
18038 if let Some(workspace) = workspace {
18039 workspace
18040 .update_in(cx, |workspace, _, cx| {
18041 struct CopyPermalinkToLine;
18042
18043 workspace.show_toast(
18044 Toast::new(
18045 NotificationId::unique::<CopyPermalinkToLine>(),
18046 message,
18047 ),
18048 cx,
18049 )
18050 })
18051 .ok();
18052 }
18053 }
18054 })
18055 .detach();
18056 }
18057
18058 pub fn copy_file_location(
18059 &mut self,
18060 _: &CopyFileLocation,
18061 _: &mut Window,
18062 cx: &mut Context<Self>,
18063 ) {
18064 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18065 if let Some(file) = self.target_file(cx) {
18066 if let Some(path) = file.path().to_str() {
18067 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18068 }
18069 }
18070 }
18071
18072 pub fn open_permalink_to_line(
18073 &mut self,
18074 _: &OpenPermalinkToLine,
18075 window: &mut Window,
18076 cx: &mut Context<Self>,
18077 ) {
18078 let permalink_task = self.get_permalink_to_line(cx);
18079 let workspace = self.workspace();
18080
18081 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18082 Ok(permalink) => {
18083 cx.update(|_, cx| {
18084 cx.open_url(permalink.as_ref());
18085 })
18086 .ok();
18087 }
18088 Err(err) => {
18089 let message = format!("Failed to open permalink: {err}");
18090
18091 anyhow::Result::<()>::Err(err).log_err();
18092
18093 if let Some(workspace) = workspace {
18094 workspace
18095 .update(cx, |workspace, cx| {
18096 struct OpenPermalinkToLine;
18097
18098 workspace.show_toast(
18099 Toast::new(
18100 NotificationId::unique::<OpenPermalinkToLine>(),
18101 message,
18102 ),
18103 cx,
18104 )
18105 })
18106 .ok();
18107 }
18108 }
18109 })
18110 .detach();
18111 }
18112
18113 pub fn insert_uuid_v4(
18114 &mut self,
18115 _: &InsertUuidV4,
18116 window: &mut Window,
18117 cx: &mut Context<Self>,
18118 ) {
18119 self.insert_uuid(UuidVersion::V4, window, cx);
18120 }
18121
18122 pub fn insert_uuid_v7(
18123 &mut self,
18124 _: &InsertUuidV7,
18125 window: &mut Window,
18126 cx: &mut Context<Self>,
18127 ) {
18128 self.insert_uuid(UuidVersion::V7, window, cx);
18129 }
18130
18131 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18132 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18133 self.transact(window, cx, |this, window, cx| {
18134 let edits = this
18135 .selections
18136 .all::<Point>(cx)
18137 .into_iter()
18138 .map(|selection| {
18139 let uuid = match version {
18140 UuidVersion::V4 => uuid::Uuid::new_v4(),
18141 UuidVersion::V7 => uuid::Uuid::now_v7(),
18142 };
18143
18144 (selection.range(), uuid.to_string())
18145 });
18146 this.edit(edits, cx);
18147 this.refresh_inline_completion(true, false, window, cx);
18148 });
18149 }
18150
18151 pub fn open_selections_in_multibuffer(
18152 &mut self,
18153 _: &OpenSelectionsInMultibuffer,
18154 window: &mut Window,
18155 cx: &mut Context<Self>,
18156 ) {
18157 let multibuffer = self.buffer.read(cx);
18158
18159 let Some(buffer) = multibuffer.as_singleton() else {
18160 return;
18161 };
18162
18163 let Some(workspace) = self.workspace() else {
18164 return;
18165 };
18166
18167 let locations = self
18168 .selections
18169 .disjoint_anchors()
18170 .iter()
18171 .map(|range| Location {
18172 buffer: buffer.clone(),
18173 range: range.start.text_anchor..range.end.text_anchor,
18174 })
18175 .collect::<Vec<_>>();
18176
18177 let title = multibuffer.title(cx).to_string();
18178
18179 cx.spawn_in(window, async move |_, cx| {
18180 workspace.update_in(cx, |workspace, window, cx| {
18181 Self::open_locations_in_multibuffer(
18182 workspace,
18183 locations,
18184 format!("Selections for '{title}'"),
18185 false,
18186 MultibufferSelectionMode::All,
18187 window,
18188 cx,
18189 );
18190 })
18191 })
18192 .detach();
18193 }
18194
18195 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18196 /// last highlight added will be used.
18197 ///
18198 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18199 pub fn highlight_rows<T: 'static>(
18200 &mut self,
18201 range: Range<Anchor>,
18202 color: Hsla,
18203 options: RowHighlightOptions,
18204 cx: &mut Context<Self>,
18205 ) {
18206 let snapshot = self.buffer().read(cx).snapshot(cx);
18207 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18208 let ix = row_highlights.binary_search_by(|highlight| {
18209 Ordering::Equal
18210 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18211 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18212 });
18213
18214 if let Err(mut ix) = ix {
18215 let index = post_inc(&mut self.highlight_order);
18216
18217 // If this range intersects with the preceding highlight, then merge it with
18218 // the preceding highlight. Otherwise insert a new highlight.
18219 let mut merged = false;
18220 if ix > 0 {
18221 let prev_highlight = &mut row_highlights[ix - 1];
18222 if prev_highlight
18223 .range
18224 .end
18225 .cmp(&range.start, &snapshot)
18226 .is_ge()
18227 {
18228 ix -= 1;
18229 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18230 prev_highlight.range.end = range.end;
18231 }
18232 merged = true;
18233 prev_highlight.index = index;
18234 prev_highlight.color = color;
18235 prev_highlight.options = options;
18236 }
18237 }
18238
18239 if !merged {
18240 row_highlights.insert(
18241 ix,
18242 RowHighlight {
18243 range: range.clone(),
18244 index,
18245 color,
18246 options,
18247 type_id: TypeId::of::<T>(),
18248 },
18249 );
18250 }
18251
18252 // If any of the following highlights intersect with this one, merge them.
18253 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18254 let highlight = &row_highlights[ix];
18255 if next_highlight
18256 .range
18257 .start
18258 .cmp(&highlight.range.end, &snapshot)
18259 .is_le()
18260 {
18261 if next_highlight
18262 .range
18263 .end
18264 .cmp(&highlight.range.end, &snapshot)
18265 .is_gt()
18266 {
18267 row_highlights[ix].range.end = next_highlight.range.end;
18268 }
18269 row_highlights.remove(ix + 1);
18270 } else {
18271 break;
18272 }
18273 }
18274 }
18275 }
18276
18277 /// Remove any highlighted row ranges of the given type that intersect the
18278 /// given ranges.
18279 pub fn remove_highlighted_rows<T: 'static>(
18280 &mut self,
18281 ranges_to_remove: Vec<Range<Anchor>>,
18282 cx: &mut Context<Self>,
18283 ) {
18284 let snapshot = self.buffer().read(cx).snapshot(cx);
18285 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18286 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18287 row_highlights.retain(|highlight| {
18288 while let Some(range_to_remove) = ranges_to_remove.peek() {
18289 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18290 Ordering::Less | Ordering::Equal => {
18291 ranges_to_remove.next();
18292 }
18293 Ordering::Greater => {
18294 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18295 Ordering::Less | Ordering::Equal => {
18296 return false;
18297 }
18298 Ordering::Greater => break,
18299 }
18300 }
18301 }
18302 }
18303
18304 true
18305 })
18306 }
18307
18308 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18309 pub fn clear_row_highlights<T: 'static>(&mut self) {
18310 self.highlighted_rows.remove(&TypeId::of::<T>());
18311 }
18312
18313 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18314 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18315 self.highlighted_rows
18316 .get(&TypeId::of::<T>())
18317 .map_or(&[] as &[_], |vec| vec.as_slice())
18318 .iter()
18319 .map(|highlight| (highlight.range.clone(), highlight.color))
18320 }
18321
18322 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18323 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18324 /// Allows to ignore certain kinds of highlights.
18325 pub fn highlighted_display_rows(
18326 &self,
18327 window: &mut Window,
18328 cx: &mut App,
18329 ) -> BTreeMap<DisplayRow, LineHighlight> {
18330 let snapshot = self.snapshot(window, cx);
18331 let mut used_highlight_orders = HashMap::default();
18332 self.highlighted_rows
18333 .iter()
18334 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18335 .fold(
18336 BTreeMap::<DisplayRow, LineHighlight>::new(),
18337 |mut unique_rows, highlight| {
18338 let start = highlight.range.start.to_display_point(&snapshot);
18339 let end = highlight.range.end.to_display_point(&snapshot);
18340 let start_row = start.row().0;
18341 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18342 && end.column() == 0
18343 {
18344 end.row().0.saturating_sub(1)
18345 } else {
18346 end.row().0
18347 };
18348 for row in start_row..=end_row {
18349 let used_index =
18350 used_highlight_orders.entry(row).or_insert(highlight.index);
18351 if highlight.index >= *used_index {
18352 *used_index = highlight.index;
18353 unique_rows.insert(
18354 DisplayRow(row),
18355 LineHighlight {
18356 include_gutter: highlight.options.include_gutter,
18357 border: None,
18358 background: highlight.color.into(),
18359 type_id: Some(highlight.type_id),
18360 },
18361 );
18362 }
18363 }
18364 unique_rows
18365 },
18366 )
18367 }
18368
18369 pub fn highlighted_display_row_for_autoscroll(
18370 &self,
18371 snapshot: &DisplaySnapshot,
18372 ) -> Option<DisplayRow> {
18373 self.highlighted_rows
18374 .values()
18375 .flat_map(|highlighted_rows| highlighted_rows.iter())
18376 .filter_map(|highlight| {
18377 if highlight.options.autoscroll {
18378 Some(highlight.range.start.to_display_point(snapshot).row())
18379 } else {
18380 None
18381 }
18382 })
18383 .min()
18384 }
18385
18386 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18387 self.highlight_background::<SearchWithinRange>(
18388 ranges,
18389 |colors| colors.editor_document_highlight_read_background,
18390 cx,
18391 )
18392 }
18393
18394 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18395 self.breadcrumb_header = Some(new_header);
18396 }
18397
18398 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18399 self.clear_background_highlights::<SearchWithinRange>(cx);
18400 }
18401
18402 pub fn highlight_background<T: 'static>(
18403 &mut self,
18404 ranges: &[Range<Anchor>],
18405 color_fetcher: fn(&ThemeColors) -> Hsla,
18406 cx: &mut Context<Self>,
18407 ) {
18408 self.background_highlights
18409 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18410 self.scrollbar_marker_state.dirty = true;
18411 cx.notify();
18412 }
18413
18414 pub fn clear_background_highlights<T: 'static>(
18415 &mut self,
18416 cx: &mut Context<Self>,
18417 ) -> Option<BackgroundHighlight> {
18418 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18419 if !text_highlights.1.is_empty() {
18420 self.scrollbar_marker_state.dirty = true;
18421 cx.notify();
18422 }
18423 Some(text_highlights)
18424 }
18425
18426 pub fn highlight_gutter<T: 'static>(
18427 &mut self,
18428 ranges: impl Into<Vec<Range<Anchor>>>,
18429 color_fetcher: fn(&App) -> Hsla,
18430 cx: &mut Context<Self>,
18431 ) {
18432 self.gutter_highlights
18433 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18434 cx.notify();
18435 }
18436
18437 pub fn clear_gutter_highlights<T: 'static>(
18438 &mut self,
18439 cx: &mut Context<Self>,
18440 ) -> Option<GutterHighlight> {
18441 cx.notify();
18442 self.gutter_highlights.remove(&TypeId::of::<T>())
18443 }
18444
18445 pub fn insert_gutter_highlight<T: 'static>(
18446 &mut self,
18447 range: Range<Anchor>,
18448 color_fetcher: fn(&App) -> Hsla,
18449 cx: &mut Context<Self>,
18450 ) {
18451 let snapshot = self.buffer().read(cx).snapshot(cx);
18452 let mut highlights = self
18453 .gutter_highlights
18454 .remove(&TypeId::of::<T>())
18455 .map(|(_, highlights)| highlights)
18456 .unwrap_or_default();
18457 let ix = highlights.binary_search_by(|highlight| {
18458 Ordering::Equal
18459 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18460 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18461 });
18462 if let Err(ix) = ix {
18463 highlights.insert(ix, range);
18464 }
18465 self.gutter_highlights
18466 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18467 }
18468
18469 pub fn remove_gutter_highlights<T: 'static>(
18470 &mut self,
18471 ranges_to_remove: Vec<Range<Anchor>>,
18472 cx: &mut Context<Self>,
18473 ) {
18474 let snapshot = self.buffer().read(cx).snapshot(cx);
18475 let Some((color_fetcher, mut gutter_highlights)) =
18476 self.gutter_highlights.remove(&TypeId::of::<T>())
18477 else {
18478 return;
18479 };
18480 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18481 gutter_highlights.retain(|highlight| {
18482 while let Some(range_to_remove) = ranges_to_remove.peek() {
18483 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18484 Ordering::Less | Ordering::Equal => {
18485 ranges_to_remove.next();
18486 }
18487 Ordering::Greater => {
18488 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18489 Ordering::Less | Ordering::Equal => {
18490 return false;
18491 }
18492 Ordering::Greater => break,
18493 }
18494 }
18495 }
18496 }
18497
18498 true
18499 });
18500 self.gutter_highlights
18501 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18502 }
18503
18504 #[cfg(feature = "test-support")]
18505 pub fn all_text_background_highlights(
18506 &self,
18507 window: &mut Window,
18508 cx: &mut Context<Self>,
18509 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18510 let snapshot = self.snapshot(window, cx);
18511 let buffer = &snapshot.buffer_snapshot;
18512 let start = buffer.anchor_before(0);
18513 let end = buffer.anchor_after(buffer.len());
18514 let theme = cx.theme().colors();
18515 self.background_highlights_in_range(start..end, &snapshot, theme)
18516 }
18517
18518 #[cfg(feature = "test-support")]
18519 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18520 let snapshot = self.buffer().read(cx).snapshot(cx);
18521
18522 let highlights = self
18523 .background_highlights
18524 .get(&TypeId::of::<items::BufferSearchHighlights>());
18525
18526 if let Some((_color, ranges)) = highlights {
18527 ranges
18528 .iter()
18529 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18530 .collect_vec()
18531 } else {
18532 vec![]
18533 }
18534 }
18535
18536 fn document_highlights_for_position<'a>(
18537 &'a self,
18538 position: Anchor,
18539 buffer: &'a MultiBufferSnapshot,
18540 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18541 let read_highlights = self
18542 .background_highlights
18543 .get(&TypeId::of::<DocumentHighlightRead>())
18544 .map(|h| &h.1);
18545 let write_highlights = self
18546 .background_highlights
18547 .get(&TypeId::of::<DocumentHighlightWrite>())
18548 .map(|h| &h.1);
18549 let left_position = position.bias_left(buffer);
18550 let right_position = position.bias_right(buffer);
18551 read_highlights
18552 .into_iter()
18553 .chain(write_highlights)
18554 .flat_map(move |ranges| {
18555 let start_ix = match ranges.binary_search_by(|probe| {
18556 let cmp = probe.end.cmp(&left_position, buffer);
18557 if cmp.is_ge() {
18558 Ordering::Greater
18559 } else {
18560 Ordering::Less
18561 }
18562 }) {
18563 Ok(i) | Err(i) => i,
18564 };
18565
18566 ranges[start_ix..]
18567 .iter()
18568 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18569 })
18570 }
18571
18572 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18573 self.background_highlights
18574 .get(&TypeId::of::<T>())
18575 .map_or(false, |(_, highlights)| !highlights.is_empty())
18576 }
18577
18578 pub fn background_highlights_in_range(
18579 &self,
18580 search_range: Range<Anchor>,
18581 display_snapshot: &DisplaySnapshot,
18582 theme: &ThemeColors,
18583 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18584 let mut results = Vec::new();
18585 for (color_fetcher, ranges) in self.background_highlights.values() {
18586 let color = color_fetcher(theme);
18587 let start_ix = match ranges.binary_search_by(|probe| {
18588 let cmp = probe
18589 .end
18590 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18591 if cmp.is_gt() {
18592 Ordering::Greater
18593 } else {
18594 Ordering::Less
18595 }
18596 }) {
18597 Ok(i) | Err(i) => i,
18598 };
18599 for range in &ranges[start_ix..] {
18600 if range
18601 .start
18602 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18603 .is_ge()
18604 {
18605 break;
18606 }
18607
18608 let start = range.start.to_display_point(display_snapshot);
18609 let end = range.end.to_display_point(display_snapshot);
18610 results.push((start..end, color))
18611 }
18612 }
18613 results
18614 }
18615
18616 pub fn background_highlight_row_ranges<T: 'static>(
18617 &self,
18618 search_range: Range<Anchor>,
18619 display_snapshot: &DisplaySnapshot,
18620 count: usize,
18621 ) -> Vec<RangeInclusive<DisplayPoint>> {
18622 let mut results = Vec::new();
18623 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18624 return vec![];
18625 };
18626
18627 let start_ix = match ranges.binary_search_by(|probe| {
18628 let cmp = probe
18629 .end
18630 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18631 if cmp.is_gt() {
18632 Ordering::Greater
18633 } else {
18634 Ordering::Less
18635 }
18636 }) {
18637 Ok(i) | Err(i) => i,
18638 };
18639 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18640 if let (Some(start_display), Some(end_display)) = (start, end) {
18641 results.push(
18642 start_display.to_display_point(display_snapshot)
18643 ..=end_display.to_display_point(display_snapshot),
18644 );
18645 }
18646 };
18647 let mut start_row: Option<Point> = None;
18648 let mut end_row: Option<Point> = None;
18649 if ranges.len() > count {
18650 return Vec::new();
18651 }
18652 for range in &ranges[start_ix..] {
18653 if range
18654 .start
18655 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18656 .is_ge()
18657 {
18658 break;
18659 }
18660 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18661 if let Some(current_row) = &end_row {
18662 if end.row == current_row.row {
18663 continue;
18664 }
18665 }
18666 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18667 if start_row.is_none() {
18668 assert_eq!(end_row, None);
18669 start_row = Some(start);
18670 end_row = Some(end);
18671 continue;
18672 }
18673 if let Some(current_end) = end_row.as_mut() {
18674 if start.row > current_end.row + 1 {
18675 push_region(start_row, end_row);
18676 start_row = Some(start);
18677 end_row = Some(end);
18678 } else {
18679 // Merge two hunks.
18680 *current_end = end;
18681 }
18682 } else {
18683 unreachable!();
18684 }
18685 }
18686 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18687 push_region(start_row, end_row);
18688 results
18689 }
18690
18691 pub fn gutter_highlights_in_range(
18692 &self,
18693 search_range: Range<Anchor>,
18694 display_snapshot: &DisplaySnapshot,
18695 cx: &App,
18696 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18697 let mut results = Vec::new();
18698 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18699 let color = color_fetcher(cx);
18700 let start_ix = match ranges.binary_search_by(|probe| {
18701 let cmp = probe
18702 .end
18703 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18704 if cmp.is_gt() {
18705 Ordering::Greater
18706 } else {
18707 Ordering::Less
18708 }
18709 }) {
18710 Ok(i) | Err(i) => i,
18711 };
18712 for range in &ranges[start_ix..] {
18713 if range
18714 .start
18715 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18716 .is_ge()
18717 {
18718 break;
18719 }
18720
18721 let start = range.start.to_display_point(display_snapshot);
18722 let end = range.end.to_display_point(display_snapshot);
18723 results.push((start..end, color))
18724 }
18725 }
18726 results
18727 }
18728
18729 /// Get the text ranges corresponding to the redaction query
18730 pub fn redacted_ranges(
18731 &self,
18732 search_range: Range<Anchor>,
18733 display_snapshot: &DisplaySnapshot,
18734 cx: &App,
18735 ) -> Vec<Range<DisplayPoint>> {
18736 display_snapshot
18737 .buffer_snapshot
18738 .redacted_ranges(search_range, |file| {
18739 if let Some(file) = file {
18740 file.is_private()
18741 && EditorSettings::get(
18742 Some(SettingsLocation {
18743 worktree_id: file.worktree_id(cx),
18744 path: file.path().as_ref(),
18745 }),
18746 cx,
18747 )
18748 .redact_private_values
18749 } else {
18750 false
18751 }
18752 })
18753 .map(|range| {
18754 range.start.to_display_point(display_snapshot)
18755 ..range.end.to_display_point(display_snapshot)
18756 })
18757 .collect()
18758 }
18759
18760 pub fn highlight_text<T: 'static>(
18761 &mut self,
18762 ranges: Vec<Range<Anchor>>,
18763 style: HighlightStyle,
18764 cx: &mut Context<Self>,
18765 ) {
18766 self.display_map.update(cx, |map, _| {
18767 map.highlight_text(TypeId::of::<T>(), ranges, style)
18768 });
18769 cx.notify();
18770 }
18771
18772 pub(crate) fn highlight_inlays<T: 'static>(
18773 &mut self,
18774 highlights: Vec<InlayHighlight>,
18775 style: HighlightStyle,
18776 cx: &mut Context<Self>,
18777 ) {
18778 self.display_map.update(cx, |map, _| {
18779 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18780 });
18781 cx.notify();
18782 }
18783
18784 pub fn text_highlights<'a, T: 'static>(
18785 &'a self,
18786 cx: &'a App,
18787 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18788 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18789 }
18790
18791 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18792 let cleared = self
18793 .display_map
18794 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18795 if cleared {
18796 cx.notify();
18797 }
18798 }
18799
18800 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18801 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18802 && self.focus_handle.is_focused(window)
18803 }
18804
18805 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18806 self.show_cursor_when_unfocused = is_enabled;
18807 cx.notify();
18808 }
18809
18810 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18811 cx.notify();
18812 }
18813
18814 fn on_debug_session_event(
18815 &mut self,
18816 _session: Entity<Session>,
18817 event: &SessionEvent,
18818 cx: &mut Context<Self>,
18819 ) {
18820 match event {
18821 SessionEvent::InvalidateInlineValue => {
18822 self.refresh_inline_values(cx);
18823 }
18824 _ => {}
18825 }
18826 }
18827
18828 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18829 let Some(project) = self.project.clone() else {
18830 return;
18831 };
18832
18833 if !self.inline_value_cache.enabled {
18834 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18835 self.splice_inlays(&inlays, Vec::new(), cx);
18836 return;
18837 }
18838
18839 let current_execution_position = self
18840 .highlighted_rows
18841 .get(&TypeId::of::<ActiveDebugLine>())
18842 .and_then(|lines| lines.last().map(|line| line.range.start));
18843
18844 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18845 let inline_values = editor
18846 .update(cx, |editor, cx| {
18847 let Some(current_execution_position) = current_execution_position else {
18848 return Some(Task::ready(Ok(Vec::new())));
18849 };
18850
18851 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18852 let snapshot = buffer.snapshot(cx);
18853
18854 let excerpt = snapshot.excerpt_containing(
18855 current_execution_position..current_execution_position,
18856 )?;
18857
18858 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18859 })?;
18860
18861 let range =
18862 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18863
18864 project.inline_values(buffer, range, cx)
18865 })
18866 .ok()
18867 .flatten()?
18868 .await
18869 .context("refreshing debugger inlays")
18870 .log_err()?;
18871
18872 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18873
18874 for (buffer_id, inline_value) in inline_values
18875 .into_iter()
18876 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18877 {
18878 buffer_inline_values
18879 .entry(buffer_id)
18880 .or_default()
18881 .push(inline_value);
18882 }
18883
18884 editor
18885 .update(cx, |editor, cx| {
18886 let snapshot = editor.buffer.read(cx).snapshot(cx);
18887 let mut new_inlays = Vec::default();
18888
18889 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18890 let buffer_id = buffer_snapshot.remote_id();
18891 buffer_inline_values
18892 .get(&buffer_id)
18893 .into_iter()
18894 .flatten()
18895 .for_each(|hint| {
18896 let inlay = Inlay::debugger_hint(
18897 post_inc(&mut editor.next_inlay_id),
18898 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18899 hint.text(),
18900 );
18901
18902 new_inlays.push(inlay);
18903 });
18904 }
18905
18906 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18907 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18908
18909 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18910 })
18911 .ok()?;
18912 Some(())
18913 });
18914 }
18915
18916 fn on_buffer_event(
18917 &mut self,
18918 multibuffer: &Entity<MultiBuffer>,
18919 event: &multi_buffer::Event,
18920 window: &mut Window,
18921 cx: &mut Context<Self>,
18922 ) {
18923 match event {
18924 multi_buffer::Event::Edited {
18925 singleton_buffer_edited,
18926 edited_buffer,
18927 } => {
18928 self.scrollbar_marker_state.dirty = true;
18929 self.active_indent_guides_state.dirty = true;
18930 self.refresh_active_diagnostics(cx);
18931 self.refresh_code_actions(window, cx);
18932 self.refresh_selected_text_highlights(true, window, cx);
18933 refresh_matching_bracket_highlights(self, window, cx);
18934 if self.has_active_inline_completion() {
18935 self.update_visible_inline_completion(window, cx);
18936 }
18937 if let Some(project) = self.project.as_ref() {
18938 if let Some(edited_buffer) = edited_buffer {
18939 project.update(cx, |project, cx| {
18940 self.registered_buffers
18941 .entry(edited_buffer.read(cx).remote_id())
18942 .or_insert_with(|| {
18943 project
18944 .register_buffer_with_language_servers(&edited_buffer, cx)
18945 });
18946 });
18947 if edited_buffer.read(cx).file().is_some() {
18948 self.pull_diagnostics(
18949 Some(edited_buffer.read(cx).remote_id()),
18950 window,
18951 cx,
18952 );
18953 }
18954 }
18955 }
18956 cx.emit(EditorEvent::BufferEdited);
18957 cx.emit(SearchEvent::MatchesInvalidated);
18958 if *singleton_buffer_edited {
18959 if let Some(buffer) = edited_buffer {
18960 if buffer.read(cx).file().is_none() {
18961 cx.emit(EditorEvent::TitleChanged);
18962 }
18963 }
18964 if let Some(project) = &self.project {
18965 #[allow(clippy::mutable_key_type)]
18966 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18967 multibuffer
18968 .all_buffers()
18969 .into_iter()
18970 .filter_map(|buffer| {
18971 buffer.update(cx, |buffer, cx| {
18972 let language = buffer.language()?;
18973 let should_discard = project.update(cx, |project, cx| {
18974 project.is_local()
18975 && !project.has_language_servers_for(buffer, cx)
18976 });
18977 should_discard.not().then_some(language.clone())
18978 })
18979 })
18980 .collect::<HashSet<_>>()
18981 });
18982 if !languages_affected.is_empty() {
18983 self.refresh_inlay_hints(
18984 InlayHintRefreshReason::BufferEdited(languages_affected),
18985 cx,
18986 );
18987 }
18988 }
18989 }
18990
18991 let Some(project) = &self.project else { return };
18992 let (telemetry, is_via_ssh) = {
18993 let project = project.read(cx);
18994 let telemetry = project.client().telemetry().clone();
18995 let is_via_ssh = project.is_via_ssh();
18996 (telemetry, is_via_ssh)
18997 };
18998 refresh_linked_ranges(self, window, cx);
18999 telemetry.log_edit_event("editor", is_via_ssh);
19000 }
19001 multi_buffer::Event::ExcerptsAdded {
19002 buffer,
19003 predecessor,
19004 excerpts,
19005 } => {
19006 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19007 let buffer_id = buffer.read(cx).remote_id();
19008 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19009 if let Some(project) = &self.project {
19010 update_uncommitted_diff_for_buffer(
19011 cx.entity(),
19012 project,
19013 [buffer.clone()],
19014 self.buffer.clone(),
19015 cx,
19016 )
19017 .detach();
19018 }
19019 }
19020 cx.emit(EditorEvent::ExcerptsAdded {
19021 buffer: buffer.clone(),
19022 predecessor: *predecessor,
19023 excerpts: excerpts.clone(),
19024 });
19025 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19026 }
19027 multi_buffer::Event::ExcerptsRemoved {
19028 ids,
19029 removed_buffer_ids,
19030 } => {
19031 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19032 let buffer = self.buffer.read(cx);
19033 self.registered_buffers
19034 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19035 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19036 cx.emit(EditorEvent::ExcerptsRemoved {
19037 ids: ids.clone(),
19038 removed_buffer_ids: removed_buffer_ids.clone(),
19039 })
19040 }
19041 multi_buffer::Event::ExcerptsEdited {
19042 excerpt_ids,
19043 buffer_ids,
19044 } => {
19045 self.display_map.update(cx, |map, cx| {
19046 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19047 });
19048 cx.emit(EditorEvent::ExcerptsEdited {
19049 ids: excerpt_ids.clone(),
19050 })
19051 }
19052 multi_buffer::Event::ExcerptsExpanded { ids } => {
19053 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19054 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19055 }
19056 multi_buffer::Event::Reparsed(buffer_id) => {
19057 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19058 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19059
19060 cx.emit(EditorEvent::Reparsed(*buffer_id));
19061 }
19062 multi_buffer::Event::DiffHunksToggled => {
19063 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19064 }
19065 multi_buffer::Event::LanguageChanged(buffer_id) => {
19066 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19067 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19068 cx.emit(EditorEvent::Reparsed(*buffer_id));
19069 cx.notify();
19070 }
19071 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19072 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19073 multi_buffer::Event::FileHandleChanged
19074 | multi_buffer::Event::Reloaded
19075 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19076 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19077 multi_buffer::Event::DiagnosticsUpdated => {
19078 self.update_diagnostics_state(window, cx);
19079 }
19080 _ => {}
19081 };
19082 }
19083
19084 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19085 self.refresh_active_diagnostics(cx);
19086 self.refresh_inline_diagnostics(true, window, cx);
19087 self.scrollbar_marker_state.dirty = true;
19088 cx.notify();
19089 }
19090
19091 pub fn start_temporary_diff_override(&mut self) {
19092 self.load_diff_task.take();
19093 self.temporary_diff_override = true;
19094 }
19095
19096 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19097 self.temporary_diff_override = false;
19098 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19099 self.buffer.update(cx, |buffer, cx| {
19100 buffer.set_all_diff_hunks_collapsed(cx);
19101 });
19102
19103 if let Some(project) = self.project.clone() {
19104 self.load_diff_task = Some(
19105 update_uncommitted_diff_for_buffer(
19106 cx.entity(),
19107 &project,
19108 self.buffer.read(cx).all_buffers(),
19109 self.buffer.clone(),
19110 cx,
19111 )
19112 .shared(),
19113 );
19114 }
19115 }
19116
19117 fn on_display_map_changed(
19118 &mut self,
19119 _: Entity<DisplayMap>,
19120 _: &mut Window,
19121 cx: &mut Context<Self>,
19122 ) {
19123 cx.notify();
19124 }
19125
19126 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19127 let new_severity = if self.diagnostics_enabled() {
19128 EditorSettings::get_global(cx)
19129 .diagnostics_max_severity
19130 .unwrap_or(DiagnosticSeverity::Hint)
19131 } else {
19132 DiagnosticSeverity::Off
19133 };
19134 self.set_max_diagnostics_severity(new_severity, cx);
19135 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19136 self.update_edit_prediction_settings(cx);
19137 self.refresh_inline_completion(true, false, window, cx);
19138 self.refresh_inlay_hints(
19139 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19140 self.selections.newest_anchor().head(),
19141 &self.buffer.read(cx).snapshot(cx),
19142 cx,
19143 )),
19144 cx,
19145 );
19146
19147 let old_cursor_shape = self.cursor_shape;
19148
19149 {
19150 let editor_settings = EditorSettings::get_global(cx);
19151 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19152 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19153 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19154 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19155 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19156 }
19157
19158 if old_cursor_shape != self.cursor_shape {
19159 cx.emit(EditorEvent::CursorShapeChanged);
19160 }
19161
19162 let project_settings = ProjectSettings::get_global(cx);
19163 self.serialize_dirty_buffers =
19164 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19165
19166 if self.mode.is_full() {
19167 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19168 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19169 if self.show_inline_diagnostics != show_inline_diagnostics {
19170 self.show_inline_diagnostics = show_inline_diagnostics;
19171 self.refresh_inline_diagnostics(false, window, cx);
19172 }
19173
19174 if self.git_blame_inline_enabled != inline_blame_enabled {
19175 self.toggle_git_blame_inline_internal(false, window, cx);
19176 }
19177
19178 let minimap_settings = EditorSettings::get_global(cx).minimap;
19179 if self.minimap_visibility != MinimapVisibility::Disabled {
19180 if self.minimap_visibility.settings_visibility()
19181 != minimap_settings.minimap_enabled()
19182 {
19183 self.set_minimap_visibility(
19184 MinimapVisibility::for_mode(self.mode(), cx),
19185 window,
19186 cx,
19187 );
19188 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19189 minimap_entity.update(cx, |minimap_editor, cx| {
19190 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19191 })
19192 }
19193 }
19194 }
19195
19196 cx.notify();
19197 }
19198
19199 pub fn set_searchable(&mut self, searchable: bool) {
19200 self.searchable = searchable;
19201 }
19202
19203 pub fn searchable(&self) -> bool {
19204 self.searchable
19205 }
19206
19207 fn open_proposed_changes_editor(
19208 &mut self,
19209 _: &OpenProposedChangesEditor,
19210 window: &mut Window,
19211 cx: &mut Context<Self>,
19212 ) {
19213 let Some(workspace) = self.workspace() else {
19214 cx.propagate();
19215 return;
19216 };
19217
19218 let selections = self.selections.all::<usize>(cx);
19219 let multi_buffer = self.buffer.read(cx);
19220 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19221 let mut new_selections_by_buffer = HashMap::default();
19222 for selection in selections {
19223 for (buffer, range, _) in
19224 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19225 {
19226 let mut range = range.to_point(buffer);
19227 range.start.column = 0;
19228 range.end.column = buffer.line_len(range.end.row);
19229 new_selections_by_buffer
19230 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19231 .or_insert(Vec::new())
19232 .push(range)
19233 }
19234 }
19235
19236 let proposed_changes_buffers = new_selections_by_buffer
19237 .into_iter()
19238 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19239 .collect::<Vec<_>>();
19240 let proposed_changes_editor = cx.new(|cx| {
19241 ProposedChangesEditor::new(
19242 "Proposed changes",
19243 proposed_changes_buffers,
19244 self.project.clone(),
19245 window,
19246 cx,
19247 )
19248 });
19249
19250 window.defer(cx, move |window, cx| {
19251 workspace.update(cx, |workspace, cx| {
19252 workspace.active_pane().update(cx, |pane, cx| {
19253 pane.add_item(
19254 Box::new(proposed_changes_editor),
19255 true,
19256 true,
19257 None,
19258 window,
19259 cx,
19260 );
19261 });
19262 });
19263 });
19264 }
19265
19266 pub fn open_excerpts_in_split(
19267 &mut self,
19268 _: &OpenExcerptsSplit,
19269 window: &mut Window,
19270 cx: &mut Context<Self>,
19271 ) {
19272 self.open_excerpts_common(None, true, window, cx)
19273 }
19274
19275 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19276 self.open_excerpts_common(None, false, window, cx)
19277 }
19278
19279 fn open_excerpts_common(
19280 &mut self,
19281 jump_data: Option<JumpData>,
19282 split: bool,
19283 window: &mut Window,
19284 cx: &mut Context<Self>,
19285 ) {
19286 let Some(workspace) = self.workspace() else {
19287 cx.propagate();
19288 return;
19289 };
19290
19291 if self.buffer.read(cx).is_singleton() {
19292 cx.propagate();
19293 return;
19294 }
19295
19296 let mut new_selections_by_buffer = HashMap::default();
19297 match &jump_data {
19298 Some(JumpData::MultiBufferPoint {
19299 excerpt_id,
19300 position,
19301 anchor,
19302 line_offset_from_top,
19303 }) => {
19304 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19305 if let Some(buffer) = multi_buffer_snapshot
19306 .buffer_id_for_excerpt(*excerpt_id)
19307 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19308 {
19309 let buffer_snapshot = buffer.read(cx).snapshot();
19310 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19311 language::ToPoint::to_point(anchor, &buffer_snapshot)
19312 } else {
19313 buffer_snapshot.clip_point(*position, Bias::Left)
19314 };
19315 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19316 new_selections_by_buffer.insert(
19317 buffer,
19318 (
19319 vec![jump_to_offset..jump_to_offset],
19320 Some(*line_offset_from_top),
19321 ),
19322 );
19323 }
19324 }
19325 Some(JumpData::MultiBufferRow {
19326 row,
19327 line_offset_from_top,
19328 }) => {
19329 let point = MultiBufferPoint::new(row.0, 0);
19330 if let Some((buffer, buffer_point, _)) =
19331 self.buffer.read(cx).point_to_buffer_point(point, cx)
19332 {
19333 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19334 new_selections_by_buffer
19335 .entry(buffer)
19336 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19337 .0
19338 .push(buffer_offset..buffer_offset)
19339 }
19340 }
19341 None => {
19342 let selections = self.selections.all::<usize>(cx);
19343 let multi_buffer = self.buffer.read(cx);
19344 for selection in selections {
19345 for (snapshot, range, _, anchor) in multi_buffer
19346 .snapshot(cx)
19347 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19348 {
19349 if let Some(anchor) = anchor {
19350 // selection is in a deleted hunk
19351 let Some(buffer_id) = anchor.buffer_id else {
19352 continue;
19353 };
19354 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19355 continue;
19356 };
19357 let offset = text::ToOffset::to_offset(
19358 &anchor.text_anchor,
19359 &buffer_handle.read(cx).snapshot(),
19360 );
19361 let range = offset..offset;
19362 new_selections_by_buffer
19363 .entry(buffer_handle)
19364 .or_insert((Vec::new(), None))
19365 .0
19366 .push(range)
19367 } else {
19368 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19369 else {
19370 continue;
19371 };
19372 new_selections_by_buffer
19373 .entry(buffer_handle)
19374 .or_insert((Vec::new(), None))
19375 .0
19376 .push(range)
19377 }
19378 }
19379 }
19380 }
19381 }
19382
19383 new_selections_by_buffer
19384 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19385
19386 if new_selections_by_buffer.is_empty() {
19387 return;
19388 }
19389
19390 // We defer the pane interaction because we ourselves are a workspace item
19391 // and activating a new item causes the pane to call a method on us reentrantly,
19392 // which panics if we're on the stack.
19393 window.defer(cx, move |window, cx| {
19394 workspace.update(cx, |workspace, cx| {
19395 let pane = if split {
19396 workspace.adjacent_pane(window, cx)
19397 } else {
19398 workspace.active_pane().clone()
19399 };
19400
19401 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19402 let editor = buffer
19403 .read(cx)
19404 .file()
19405 .is_none()
19406 .then(|| {
19407 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19408 // so `workspace.open_project_item` will never find them, always opening a new editor.
19409 // Instead, we try to activate the existing editor in the pane first.
19410 let (editor, pane_item_index) =
19411 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19412 let editor = item.downcast::<Editor>()?;
19413 let singleton_buffer =
19414 editor.read(cx).buffer().read(cx).as_singleton()?;
19415 if singleton_buffer == buffer {
19416 Some((editor, i))
19417 } else {
19418 None
19419 }
19420 })?;
19421 pane.update(cx, |pane, cx| {
19422 pane.activate_item(pane_item_index, true, true, window, cx)
19423 });
19424 Some(editor)
19425 })
19426 .flatten()
19427 .unwrap_or_else(|| {
19428 workspace.open_project_item::<Self>(
19429 pane.clone(),
19430 buffer,
19431 true,
19432 true,
19433 window,
19434 cx,
19435 )
19436 });
19437
19438 editor.update(cx, |editor, cx| {
19439 let autoscroll = match scroll_offset {
19440 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19441 None => Autoscroll::newest(),
19442 };
19443 let nav_history = editor.nav_history.take();
19444 editor.change_selections(Some(autoscroll), window, cx, |s| {
19445 s.select_ranges(ranges);
19446 });
19447 editor.nav_history = nav_history;
19448 });
19449 }
19450 })
19451 });
19452 }
19453
19454 // For now, don't allow opening excerpts in buffers that aren't backed by
19455 // regular project files.
19456 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19457 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19458 }
19459
19460 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19461 let snapshot = self.buffer.read(cx).read(cx);
19462 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19463 Some(
19464 ranges
19465 .iter()
19466 .map(move |range| {
19467 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19468 })
19469 .collect(),
19470 )
19471 }
19472
19473 fn selection_replacement_ranges(
19474 &self,
19475 range: Range<OffsetUtf16>,
19476 cx: &mut App,
19477 ) -> Vec<Range<OffsetUtf16>> {
19478 let selections = self.selections.all::<OffsetUtf16>(cx);
19479 let newest_selection = selections
19480 .iter()
19481 .max_by_key(|selection| selection.id)
19482 .unwrap();
19483 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19484 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19485 let snapshot = self.buffer.read(cx).read(cx);
19486 selections
19487 .into_iter()
19488 .map(|mut selection| {
19489 selection.start.0 =
19490 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19491 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19492 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19493 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19494 })
19495 .collect()
19496 }
19497
19498 fn report_editor_event(
19499 &self,
19500 event_type: &'static str,
19501 file_extension: Option<String>,
19502 cx: &App,
19503 ) {
19504 if cfg!(any(test, feature = "test-support")) {
19505 return;
19506 }
19507
19508 let Some(project) = &self.project else { return };
19509
19510 // If None, we are in a file without an extension
19511 let file = self
19512 .buffer
19513 .read(cx)
19514 .as_singleton()
19515 .and_then(|b| b.read(cx).file());
19516 let file_extension = file_extension.or(file
19517 .as_ref()
19518 .and_then(|file| Path::new(file.file_name(cx)).extension())
19519 .and_then(|e| e.to_str())
19520 .map(|a| a.to_string()));
19521
19522 let vim_mode = vim_enabled(cx);
19523
19524 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19525 let copilot_enabled = edit_predictions_provider
19526 == language::language_settings::EditPredictionProvider::Copilot;
19527 let copilot_enabled_for_language = self
19528 .buffer
19529 .read(cx)
19530 .language_settings(cx)
19531 .show_edit_predictions;
19532
19533 let project = project.read(cx);
19534 telemetry::event!(
19535 event_type,
19536 file_extension,
19537 vim_mode,
19538 copilot_enabled,
19539 copilot_enabled_for_language,
19540 edit_predictions_provider,
19541 is_via_ssh = project.is_via_ssh(),
19542 );
19543 }
19544
19545 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19546 /// with each line being an array of {text, highlight} objects.
19547 fn copy_highlight_json(
19548 &mut self,
19549 _: &CopyHighlightJson,
19550 window: &mut Window,
19551 cx: &mut Context<Self>,
19552 ) {
19553 #[derive(Serialize)]
19554 struct Chunk<'a> {
19555 text: String,
19556 highlight: Option<&'a str>,
19557 }
19558
19559 let snapshot = self.buffer.read(cx).snapshot(cx);
19560 let range = self
19561 .selected_text_range(false, window, cx)
19562 .and_then(|selection| {
19563 if selection.range.is_empty() {
19564 None
19565 } else {
19566 Some(selection.range)
19567 }
19568 })
19569 .unwrap_or_else(|| 0..snapshot.len());
19570
19571 let chunks = snapshot.chunks(range, true);
19572 let mut lines = Vec::new();
19573 let mut line: VecDeque<Chunk> = VecDeque::new();
19574
19575 let Some(style) = self.style.as_ref() else {
19576 return;
19577 };
19578
19579 for chunk in chunks {
19580 let highlight = chunk
19581 .syntax_highlight_id
19582 .and_then(|id| id.name(&style.syntax));
19583 let mut chunk_lines = chunk.text.split('\n').peekable();
19584 while let Some(text) = chunk_lines.next() {
19585 let mut merged_with_last_token = false;
19586 if let Some(last_token) = line.back_mut() {
19587 if last_token.highlight == highlight {
19588 last_token.text.push_str(text);
19589 merged_with_last_token = true;
19590 }
19591 }
19592
19593 if !merged_with_last_token {
19594 line.push_back(Chunk {
19595 text: text.into(),
19596 highlight,
19597 });
19598 }
19599
19600 if chunk_lines.peek().is_some() {
19601 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19602 line.pop_front();
19603 }
19604 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19605 line.pop_back();
19606 }
19607
19608 lines.push(mem::take(&mut line));
19609 }
19610 }
19611 }
19612
19613 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19614 return;
19615 };
19616 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19617 }
19618
19619 pub fn open_context_menu(
19620 &mut self,
19621 _: &OpenContextMenu,
19622 window: &mut Window,
19623 cx: &mut Context<Self>,
19624 ) {
19625 self.request_autoscroll(Autoscroll::newest(), cx);
19626 let position = self.selections.newest_display(cx).start;
19627 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19628 }
19629
19630 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19631 &self.inlay_hint_cache
19632 }
19633
19634 pub fn replay_insert_event(
19635 &mut self,
19636 text: &str,
19637 relative_utf16_range: Option<Range<isize>>,
19638 window: &mut Window,
19639 cx: &mut Context<Self>,
19640 ) {
19641 if !self.input_enabled {
19642 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19643 return;
19644 }
19645 if let Some(relative_utf16_range) = relative_utf16_range {
19646 let selections = self.selections.all::<OffsetUtf16>(cx);
19647 self.change_selections(None, window, cx, |s| {
19648 let new_ranges = selections.into_iter().map(|range| {
19649 let start = OffsetUtf16(
19650 range
19651 .head()
19652 .0
19653 .saturating_add_signed(relative_utf16_range.start),
19654 );
19655 let end = OffsetUtf16(
19656 range
19657 .head()
19658 .0
19659 .saturating_add_signed(relative_utf16_range.end),
19660 );
19661 start..end
19662 });
19663 s.select_ranges(new_ranges);
19664 });
19665 }
19666
19667 self.handle_input(text, window, cx);
19668 }
19669
19670 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19671 let Some(provider) = self.semantics_provider.as_ref() else {
19672 return false;
19673 };
19674
19675 let mut supports = false;
19676 self.buffer().update(cx, |this, cx| {
19677 this.for_each_buffer(|buffer| {
19678 supports |= provider.supports_inlay_hints(buffer, cx);
19679 });
19680 });
19681
19682 supports
19683 }
19684
19685 pub fn is_focused(&self, window: &Window) -> bool {
19686 self.focus_handle.is_focused(window)
19687 }
19688
19689 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19690 cx.emit(EditorEvent::Focused);
19691
19692 if let Some(descendant) = self
19693 .last_focused_descendant
19694 .take()
19695 .and_then(|descendant| descendant.upgrade())
19696 {
19697 window.focus(&descendant);
19698 } else {
19699 if let Some(blame) = self.blame.as_ref() {
19700 blame.update(cx, GitBlame::focus)
19701 }
19702
19703 self.blink_manager.update(cx, BlinkManager::enable);
19704 self.show_cursor_names(window, cx);
19705 self.buffer.update(cx, |buffer, cx| {
19706 buffer.finalize_last_transaction(cx);
19707 if self.leader_id.is_none() {
19708 buffer.set_active_selections(
19709 &self.selections.disjoint_anchors(),
19710 self.selections.line_mode,
19711 self.cursor_shape,
19712 cx,
19713 );
19714 }
19715 });
19716 }
19717 }
19718
19719 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19720 cx.emit(EditorEvent::FocusedIn)
19721 }
19722
19723 fn handle_focus_out(
19724 &mut self,
19725 event: FocusOutEvent,
19726 _window: &mut Window,
19727 cx: &mut Context<Self>,
19728 ) {
19729 if event.blurred != self.focus_handle {
19730 self.last_focused_descendant = Some(event.blurred);
19731 }
19732 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19733 }
19734
19735 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19736 self.blink_manager.update(cx, BlinkManager::disable);
19737 self.buffer
19738 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19739
19740 if let Some(blame) = self.blame.as_ref() {
19741 blame.update(cx, GitBlame::blur)
19742 }
19743 if !self.hover_state.focused(window, cx) {
19744 hide_hover(self, cx);
19745 }
19746 if !self
19747 .context_menu
19748 .borrow()
19749 .as_ref()
19750 .is_some_and(|context_menu| context_menu.focused(window, cx))
19751 {
19752 self.hide_context_menu(window, cx);
19753 }
19754 self.discard_inline_completion(false, cx);
19755 cx.emit(EditorEvent::Blurred);
19756 cx.notify();
19757 }
19758
19759 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19760 let mut pending: String = window
19761 .pending_input_keystrokes()
19762 .into_iter()
19763 .flatten()
19764 .filter_map(|keystroke| {
19765 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19766 keystroke.key_char.clone()
19767 } else {
19768 None
19769 }
19770 })
19771 .collect();
19772
19773 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19774 pending = "".to_string();
19775 }
19776
19777 let existing_pending = self
19778 .text_highlights::<PendingInput>(cx)
19779 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19780 if existing_pending.is_none() && pending.is_empty() {
19781 return;
19782 }
19783 let transaction =
19784 self.transact(window, cx, |this, window, cx| {
19785 let selections = this.selections.all::<usize>(cx);
19786 let edits = selections
19787 .iter()
19788 .map(|selection| (selection.end..selection.end, pending.clone()));
19789 this.edit(edits, cx);
19790 this.change_selections(None, window, cx, |s| {
19791 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19792 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19793 }));
19794 });
19795 if let Some(existing_ranges) = existing_pending {
19796 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19797 this.edit(edits, cx);
19798 }
19799 });
19800
19801 let snapshot = self.snapshot(window, cx);
19802 let ranges = self
19803 .selections
19804 .all::<usize>(cx)
19805 .into_iter()
19806 .map(|selection| {
19807 snapshot.buffer_snapshot.anchor_after(selection.end)
19808 ..snapshot
19809 .buffer_snapshot
19810 .anchor_before(selection.end + pending.len())
19811 })
19812 .collect();
19813
19814 if pending.is_empty() {
19815 self.clear_highlights::<PendingInput>(cx);
19816 } else {
19817 self.highlight_text::<PendingInput>(
19818 ranges,
19819 HighlightStyle {
19820 underline: Some(UnderlineStyle {
19821 thickness: px(1.),
19822 color: None,
19823 wavy: false,
19824 }),
19825 ..Default::default()
19826 },
19827 cx,
19828 );
19829 }
19830
19831 self.ime_transaction = self.ime_transaction.or(transaction);
19832 if let Some(transaction) = self.ime_transaction {
19833 self.buffer.update(cx, |buffer, cx| {
19834 buffer.group_until_transaction(transaction, cx);
19835 });
19836 }
19837
19838 if self.text_highlights::<PendingInput>(cx).is_none() {
19839 self.ime_transaction.take();
19840 }
19841 }
19842
19843 pub fn register_action_renderer(
19844 &mut self,
19845 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19846 ) -> Subscription {
19847 let id = self.next_editor_action_id.post_inc();
19848 self.editor_actions
19849 .borrow_mut()
19850 .insert(id, Box::new(listener));
19851
19852 let editor_actions = self.editor_actions.clone();
19853 Subscription::new(move || {
19854 editor_actions.borrow_mut().remove(&id);
19855 })
19856 }
19857
19858 pub fn register_action<A: Action>(
19859 &mut self,
19860 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19861 ) -> Subscription {
19862 let id = self.next_editor_action_id.post_inc();
19863 let listener = Arc::new(listener);
19864 self.editor_actions.borrow_mut().insert(
19865 id,
19866 Box::new(move |_, window, _| {
19867 let listener = listener.clone();
19868 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19869 let action = action.downcast_ref().unwrap();
19870 if phase == DispatchPhase::Bubble {
19871 listener(action, window, cx)
19872 }
19873 })
19874 }),
19875 );
19876
19877 let editor_actions = self.editor_actions.clone();
19878 Subscription::new(move || {
19879 editor_actions.borrow_mut().remove(&id);
19880 })
19881 }
19882
19883 pub fn file_header_size(&self) -> u32 {
19884 FILE_HEADER_HEIGHT
19885 }
19886
19887 pub fn restore(
19888 &mut self,
19889 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19890 window: &mut Window,
19891 cx: &mut Context<Self>,
19892 ) {
19893 let workspace = self.workspace();
19894 let project = self.project.as_ref();
19895 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19896 let mut tasks = Vec::new();
19897 for (buffer_id, changes) in revert_changes {
19898 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19899 buffer.update(cx, |buffer, cx| {
19900 buffer.edit(
19901 changes
19902 .into_iter()
19903 .map(|(range, text)| (range, text.to_string())),
19904 None,
19905 cx,
19906 );
19907 });
19908
19909 if let Some(project) =
19910 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19911 {
19912 project.update(cx, |project, cx| {
19913 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19914 })
19915 }
19916 }
19917 }
19918 tasks
19919 });
19920 cx.spawn_in(window, async move |_, cx| {
19921 for (buffer, task) in save_tasks {
19922 let result = task.await;
19923 if result.is_err() {
19924 let Some(path) = buffer
19925 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19926 .ok()
19927 else {
19928 continue;
19929 };
19930 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19931 let Some(task) = cx
19932 .update_window_entity(&workspace, |workspace, window, cx| {
19933 workspace
19934 .open_path_preview(path, None, false, false, false, window, cx)
19935 })
19936 .ok()
19937 else {
19938 continue;
19939 };
19940 task.await.log_err();
19941 }
19942 }
19943 }
19944 })
19945 .detach();
19946 self.change_selections(None, window, cx, |selections| selections.refresh());
19947 }
19948
19949 pub fn to_pixel_point(
19950 &self,
19951 source: multi_buffer::Anchor,
19952 editor_snapshot: &EditorSnapshot,
19953 window: &mut Window,
19954 ) -> Option<gpui::Point<Pixels>> {
19955 let source_point = source.to_display_point(editor_snapshot);
19956 self.display_to_pixel_point(source_point, editor_snapshot, window)
19957 }
19958
19959 pub fn display_to_pixel_point(
19960 &self,
19961 source: DisplayPoint,
19962 editor_snapshot: &EditorSnapshot,
19963 window: &mut Window,
19964 ) -> Option<gpui::Point<Pixels>> {
19965 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19966 let text_layout_details = self.text_layout_details(window);
19967 let scroll_top = text_layout_details
19968 .scroll_anchor
19969 .scroll_position(editor_snapshot)
19970 .y;
19971
19972 if source.row().as_f32() < scroll_top.floor() {
19973 return None;
19974 }
19975 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19976 let source_y = line_height * (source.row().as_f32() - scroll_top);
19977 Some(gpui::Point::new(source_x, source_y))
19978 }
19979
19980 pub fn has_visible_completions_menu(&self) -> bool {
19981 !self.edit_prediction_preview_is_active()
19982 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19983 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19984 })
19985 }
19986
19987 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19988 if self.mode.is_minimap() {
19989 return;
19990 }
19991 self.addons
19992 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19993 }
19994
19995 pub fn unregister_addon<T: Addon>(&mut self) {
19996 self.addons.remove(&std::any::TypeId::of::<T>());
19997 }
19998
19999 pub fn addon<T: Addon>(&self) -> Option<&T> {
20000 let type_id = std::any::TypeId::of::<T>();
20001 self.addons
20002 .get(&type_id)
20003 .and_then(|item| item.to_any().downcast_ref::<T>())
20004 }
20005
20006 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20007 let type_id = std::any::TypeId::of::<T>();
20008 self.addons
20009 .get_mut(&type_id)
20010 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20011 }
20012
20013 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20014 let text_layout_details = self.text_layout_details(window);
20015 let style = &text_layout_details.editor_style;
20016 let font_id = window.text_system().resolve_font(&style.text.font());
20017 let font_size = style.text.font_size.to_pixels(window.rem_size());
20018 let line_height = style.text.line_height_in_pixels(window.rem_size());
20019 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20020
20021 gpui::Size::new(em_width, line_height)
20022 }
20023
20024 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20025 self.load_diff_task.clone()
20026 }
20027
20028 fn read_metadata_from_db(
20029 &mut self,
20030 item_id: u64,
20031 workspace_id: WorkspaceId,
20032 window: &mut Window,
20033 cx: &mut Context<Editor>,
20034 ) {
20035 if self.is_singleton(cx)
20036 && !self.mode.is_minimap()
20037 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20038 {
20039 let buffer_snapshot = OnceCell::new();
20040
20041 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20042 if !folds.is_empty() {
20043 let snapshot =
20044 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20045 self.fold_ranges(
20046 folds
20047 .into_iter()
20048 .map(|(start, end)| {
20049 snapshot.clip_offset(start, Bias::Left)
20050 ..snapshot.clip_offset(end, Bias::Right)
20051 })
20052 .collect(),
20053 false,
20054 window,
20055 cx,
20056 );
20057 }
20058 }
20059
20060 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20061 if !selections.is_empty() {
20062 let snapshot =
20063 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20064 // skip adding the initial selection to selection history
20065 self.selection_history.mode = SelectionHistoryMode::Skipping;
20066 self.change_selections(None, window, cx, |s| {
20067 s.select_ranges(selections.into_iter().map(|(start, end)| {
20068 snapshot.clip_offset(start, Bias::Left)
20069 ..snapshot.clip_offset(end, Bias::Right)
20070 }));
20071 });
20072 self.selection_history.mode = SelectionHistoryMode::Normal;
20073 }
20074 };
20075 }
20076
20077 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20078 }
20079}
20080
20081fn vim_enabled(cx: &App) -> bool {
20082 cx.global::<SettingsStore>()
20083 .raw_user_settings()
20084 .get("vim_mode")
20085 == Some(&serde_json::Value::Bool(true))
20086}
20087
20088fn process_completion_for_edit(
20089 completion: &Completion,
20090 intent: CompletionIntent,
20091 buffer: &Entity<Buffer>,
20092 cursor_position: &text::Anchor,
20093 cx: &mut Context<Editor>,
20094) -> CompletionEdit {
20095 let buffer = buffer.read(cx);
20096 let buffer_snapshot = buffer.snapshot();
20097 let (snippet, new_text) = if completion.is_snippet() {
20098 // Workaround for typescript language server issues so that methods don't expand within
20099 // strings and functions with type expressions. The previous point is used because the query
20100 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20101 let mut snippet_source = completion.new_text.clone();
20102 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20103 previous_point.column = previous_point.column.saturating_sub(1);
20104 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20105 if scope.prefers_label_for_snippet_in_completion() {
20106 if let Some(label) = completion.label() {
20107 if matches!(
20108 completion.kind(),
20109 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20110 ) {
20111 snippet_source = label;
20112 }
20113 }
20114 }
20115 }
20116 match Snippet::parse(&snippet_source).log_err() {
20117 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20118 None => (None, completion.new_text.clone()),
20119 }
20120 } else {
20121 (None, completion.new_text.clone())
20122 };
20123
20124 let mut range_to_replace = {
20125 let replace_range = &completion.replace_range;
20126 if let CompletionSource::Lsp {
20127 insert_range: Some(insert_range),
20128 ..
20129 } = &completion.source
20130 {
20131 debug_assert_eq!(
20132 insert_range.start, replace_range.start,
20133 "insert_range and replace_range should start at the same position"
20134 );
20135 debug_assert!(
20136 insert_range
20137 .start
20138 .cmp(&cursor_position, &buffer_snapshot)
20139 .is_le(),
20140 "insert_range should start before or at cursor position"
20141 );
20142 debug_assert!(
20143 replace_range
20144 .start
20145 .cmp(&cursor_position, &buffer_snapshot)
20146 .is_le(),
20147 "replace_range should start before or at cursor position"
20148 );
20149 debug_assert!(
20150 insert_range
20151 .end
20152 .cmp(&cursor_position, &buffer_snapshot)
20153 .is_le(),
20154 "insert_range should end before or at cursor position"
20155 );
20156
20157 let should_replace = match intent {
20158 CompletionIntent::CompleteWithInsert => false,
20159 CompletionIntent::CompleteWithReplace => true,
20160 CompletionIntent::Complete | CompletionIntent::Compose => {
20161 let insert_mode =
20162 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20163 .completions
20164 .lsp_insert_mode;
20165 match insert_mode {
20166 LspInsertMode::Insert => false,
20167 LspInsertMode::Replace => true,
20168 LspInsertMode::ReplaceSubsequence => {
20169 let mut text_to_replace = buffer.chars_for_range(
20170 buffer.anchor_before(replace_range.start)
20171 ..buffer.anchor_after(replace_range.end),
20172 );
20173 let mut current_needle = text_to_replace.next();
20174 for haystack_ch in completion.label.text.chars() {
20175 if let Some(needle_ch) = current_needle {
20176 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20177 current_needle = text_to_replace.next();
20178 }
20179 }
20180 }
20181 current_needle.is_none()
20182 }
20183 LspInsertMode::ReplaceSuffix => {
20184 if replace_range
20185 .end
20186 .cmp(&cursor_position, &buffer_snapshot)
20187 .is_gt()
20188 {
20189 let range_after_cursor = *cursor_position..replace_range.end;
20190 let text_after_cursor = buffer
20191 .text_for_range(
20192 buffer.anchor_before(range_after_cursor.start)
20193 ..buffer.anchor_after(range_after_cursor.end),
20194 )
20195 .collect::<String>()
20196 .to_ascii_lowercase();
20197 completion
20198 .label
20199 .text
20200 .to_ascii_lowercase()
20201 .ends_with(&text_after_cursor)
20202 } else {
20203 true
20204 }
20205 }
20206 }
20207 }
20208 };
20209
20210 if should_replace {
20211 replace_range.clone()
20212 } else {
20213 insert_range.clone()
20214 }
20215 } else {
20216 replace_range.clone()
20217 }
20218 };
20219
20220 if range_to_replace
20221 .end
20222 .cmp(&cursor_position, &buffer_snapshot)
20223 .is_lt()
20224 {
20225 range_to_replace.end = *cursor_position;
20226 }
20227
20228 CompletionEdit {
20229 new_text,
20230 replace_range: range_to_replace.to_offset(&buffer),
20231 snippet,
20232 }
20233}
20234
20235struct CompletionEdit {
20236 new_text: String,
20237 replace_range: Range<usize>,
20238 snippet: Option<Snippet>,
20239}
20240
20241fn insert_extra_newline_brackets(
20242 buffer: &MultiBufferSnapshot,
20243 range: Range<usize>,
20244 language: &language::LanguageScope,
20245) -> bool {
20246 let leading_whitespace_len = buffer
20247 .reversed_chars_at(range.start)
20248 .take_while(|c| c.is_whitespace() && *c != '\n')
20249 .map(|c| c.len_utf8())
20250 .sum::<usize>();
20251 let trailing_whitespace_len = buffer
20252 .chars_at(range.end)
20253 .take_while(|c| c.is_whitespace() && *c != '\n')
20254 .map(|c| c.len_utf8())
20255 .sum::<usize>();
20256 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20257
20258 language.brackets().any(|(pair, enabled)| {
20259 let pair_start = pair.start.trim_end();
20260 let pair_end = pair.end.trim_start();
20261
20262 enabled
20263 && pair.newline
20264 && buffer.contains_str_at(range.end, pair_end)
20265 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20266 })
20267}
20268
20269fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20270 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20271 [(buffer, range, _)] => (*buffer, range.clone()),
20272 _ => return false,
20273 };
20274 let pair = {
20275 let mut result: Option<BracketMatch> = None;
20276
20277 for pair in buffer
20278 .all_bracket_ranges(range.clone())
20279 .filter(move |pair| {
20280 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20281 })
20282 {
20283 let len = pair.close_range.end - pair.open_range.start;
20284
20285 if let Some(existing) = &result {
20286 let existing_len = existing.close_range.end - existing.open_range.start;
20287 if len > existing_len {
20288 continue;
20289 }
20290 }
20291
20292 result = Some(pair);
20293 }
20294
20295 result
20296 };
20297 let Some(pair) = pair else {
20298 return false;
20299 };
20300 pair.newline_only
20301 && buffer
20302 .chars_for_range(pair.open_range.end..range.start)
20303 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20304 .all(|c| c.is_whitespace() && c != '\n')
20305}
20306
20307fn update_uncommitted_diff_for_buffer(
20308 editor: Entity<Editor>,
20309 project: &Entity<Project>,
20310 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20311 buffer: Entity<MultiBuffer>,
20312 cx: &mut App,
20313) -> Task<()> {
20314 let mut tasks = Vec::new();
20315 project.update(cx, |project, cx| {
20316 for buffer in buffers {
20317 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20318 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20319 }
20320 }
20321 });
20322 cx.spawn(async move |cx| {
20323 let diffs = future::join_all(tasks).await;
20324 if editor
20325 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20326 .unwrap_or(false)
20327 {
20328 return;
20329 }
20330
20331 buffer
20332 .update(cx, |buffer, cx| {
20333 for diff in diffs.into_iter().flatten() {
20334 buffer.add_diff(diff, cx);
20335 }
20336 })
20337 .ok();
20338 })
20339}
20340
20341fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20342 let tab_size = tab_size.get() as usize;
20343 let mut width = offset;
20344
20345 for ch in text.chars() {
20346 width += if ch == '\t' {
20347 tab_size - (width % tab_size)
20348 } else {
20349 1
20350 };
20351 }
20352
20353 width - offset
20354}
20355
20356#[cfg(test)]
20357mod tests {
20358 use super::*;
20359
20360 #[test]
20361 fn test_string_size_with_expanded_tabs() {
20362 let nz = |val| NonZeroU32::new(val).unwrap();
20363 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20364 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20365 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20366 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20367 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20368 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20369 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20370 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20371 }
20372}
20373
20374/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20375struct WordBreakingTokenizer<'a> {
20376 input: &'a str,
20377}
20378
20379impl<'a> WordBreakingTokenizer<'a> {
20380 fn new(input: &'a str) -> Self {
20381 Self { input }
20382 }
20383}
20384
20385fn is_char_ideographic(ch: char) -> bool {
20386 use unicode_script::Script::*;
20387 use unicode_script::UnicodeScript;
20388 matches!(ch.script(), Han | Tangut | Yi)
20389}
20390
20391fn is_grapheme_ideographic(text: &str) -> bool {
20392 text.chars().any(is_char_ideographic)
20393}
20394
20395fn is_grapheme_whitespace(text: &str) -> bool {
20396 text.chars().any(|x| x.is_whitespace())
20397}
20398
20399fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20400 text.chars().next().map_or(false, |ch| {
20401 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20402 })
20403}
20404
20405#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20406enum WordBreakToken<'a> {
20407 Word { token: &'a str, grapheme_len: usize },
20408 InlineWhitespace { token: &'a str, grapheme_len: usize },
20409 Newline,
20410}
20411
20412impl<'a> Iterator for WordBreakingTokenizer<'a> {
20413 /// Yields a span, the count of graphemes in the token, and whether it was
20414 /// whitespace. Note that it also breaks at word boundaries.
20415 type Item = WordBreakToken<'a>;
20416
20417 fn next(&mut self) -> Option<Self::Item> {
20418 use unicode_segmentation::UnicodeSegmentation;
20419 if self.input.is_empty() {
20420 return None;
20421 }
20422
20423 let mut iter = self.input.graphemes(true).peekable();
20424 let mut offset = 0;
20425 let mut grapheme_len = 0;
20426 if let Some(first_grapheme) = iter.next() {
20427 let is_newline = first_grapheme == "\n";
20428 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20429 offset += first_grapheme.len();
20430 grapheme_len += 1;
20431 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20432 if let Some(grapheme) = iter.peek().copied() {
20433 if should_stay_with_preceding_ideograph(grapheme) {
20434 offset += grapheme.len();
20435 grapheme_len += 1;
20436 }
20437 }
20438 } else {
20439 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20440 let mut next_word_bound = words.peek().copied();
20441 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20442 next_word_bound = words.next();
20443 }
20444 while let Some(grapheme) = iter.peek().copied() {
20445 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20446 break;
20447 };
20448 if is_grapheme_whitespace(grapheme) != is_whitespace
20449 || (grapheme == "\n") != is_newline
20450 {
20451 break;
20452 };
20453 offset += grapheme.len();
20454 grapheme_len += 1;
20455 iter.next();
20456 }
20457 }
20458 let token = &self.input[..offset];
20459 self.input = &self.input[offset..];
20460 if token == "\n" {
20461 Some(WordBreakToken::Newline)
20462 } else if is_whitespace {
20463 Some(WordBreakToken::InlineWhitespace {
20464 token,
20465 grapheme_len,
20466 })
20467 } else {
20468 Some(WordBreakToken::Word {
20469 token,
20470 grapheme_len,
20471 })
20472 }
20473 } else {
20474 None
20475 }
20476 }
20477}
20478
20479#[test]
20480fn test_word_breaking_tokenizer() {
20481 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20482 ("", &[]),
20483 (" ", &[whitespace(" ", 2)]),
20484 ("Ʒ", &[word("Ʒ", 1)]),
20485 ("Ǽ", &[word("Ǽ", 1)]),
20486 ("⋑", &[word("⋑", 1)]),
20487 ("⋑⋑", &[word("⋑⋑", 2)]),
20488 (
20489 "原理,进而",
20490 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20491 ),
20492 (
20493 "hello world",
20494 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20495 ),
20496 (
20497 "hello, world",
20498 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20499 ),
20500 (
20501 " hello world",
20502 &[
20503 whitespace(" ", 2),
20504 word("hello", 5),
20505 whitespace(" ", 1),
20506 word("world", 5),
20507 ],
20508 ),
20509 (
20510 "这是什么 \n 钢笔",
20511 &[
20512 word("这", 1),
20513 word("是", 1),
20514 word("什", 1),
20515 word("么", 1),
20516 whitespace(" ", 1),
20517 newline(),
20518 whitespace(" ", 1),
20519 word("钢", 1),
20520 word("笔", 1),
20521 ],
20522 ),
20523 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20524 ];
20525
20526 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20527 WordBreakToken::Word {
20528 token,
20529 grapheme_len,
20530 }
20531 }
20532
20533 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20534 WordBreakToken::InlineWhitespace {
20535 token,
20536 grapheme_len,
20537 }
20538 }
20539
20540 fn newline() -> WordBreakToken<'static> {
20541 WordBreakToken::Newline
20542 }
20543
20544 for (input, result) in tests {
20545 assert_eq!(
20546 WordBreakingTokenizer::new(input)
20547 .collect::<Vec<_>>()
20548 .as_slice(),
20549 *result,
20550 );
20551 }
20552}
20553
20554fn wrap_with_prefix(
20555 line_prefix: String,
20556 unwrapped_text: String,
20557 wrap_column: usize,
20558 tab_size: NonZeroU32,
20559 preserve_existing_whitespace: bool,
20560) -> String {
20561 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20562 let mut wrapped_text = String::new();
20563 let mut current_line = line_prefix.clone();
20564
20565 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20566 let mut current_line_len = line_prefix_len;
20567 let mut in_whitespace = false;
20568 for token in tokenizer {
20569 let have_preceding_whitespace = in_whitespace;
20570 match token {
20571 WordBreakToken::Word {
20572 token,
20573 grapheme_len,
20574 } => {
20575 in_whitespace = false;
20576 if current_line_len + grapheme_len > wrap_column
20577 && current_line_len != line_prefix_len
20578 {
20579 wrapped_text.push_str(current_line.trim_end());
20580 wrapped_text.push('\n');
20581 current_line.truncate(line_prefix.len());
20582 current_line_len = line_prefix_len;
20583 }
20584 current_line.push_str(token);
20585 current_line_len += grapheme_len;
20586 }
20587 WordBreakToken::InlineWhitespace {
20588 mut token,
20589 mut grapheme_len,
20590 } => {
20591 in_whitespace = true;
20592 if have_preceding_whitespace && !preserve_existing_whitespace {
20593 continue;
20594 }
20595 if !preserve_existing_whitespace {
20596 token = " ";
20597 grapheme_len = 1;
20598 }
20599 if current_line_len + grapheme_len > wrap_column {
20600 wrapped_text.push_str(current_line.trim_end());
20601 wrapped_text.push('\n');
20602 current_line.truncate(line_prefix.len());
20603 current_line_len = line_prefix_len;
20604 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20605 current_line.push_str(token);
20606 current_line_len += grapheme_len;
20607 }
20608 }
20609 WordBreakToken::Newline => {
20610 in_whitespace = true;
20611 if preserve_existing_whitespace {
20612 wrapped_text.push_str(current_line.trim_end());
20613 wrapped_text.push('\n');
20614 current_line.truncate(line_prefix.len());
20615 current_line_len = line_prefix_len;
20616 } else if have_preceding_whitespace {
20617 continue;
20618 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20619 {
20620 wrapped_text.push_str(current_line.trim_end());
20621 wrapped_text.push('\n');
20622 current_line.truncate(line_prefix.len());
20623 current_line_len = line_prefix_len;
20624 } else if current_line_len != line_prefix_len {
20625 current_line.push(' ');
20626 current_line_len += 1;
20627 }
20628 }
20629 }
20630 }
20631
20632 if !current_line.is_empty() {
20633 wrapped_text.push_str(¤t_line);
20634 }
20635 wrapped_text
20636}
20637
20638#[test]
20639fn test_wrap_with_prefix() {
20640 assert_eq!(
20641 wrap_with_prefix(
20642 "# ".to_string(),
20643 "abcdefg".to_string(),
20644 4,
20645 NonZeroU32::new(4).unwrap(),
20646 false,
20647 ),
20648 "# abcdefg"
20649 );
20650 assert_eq!(
20651 wrap_with_prefix(
20652 "".to_string(),
20653 "\thello world".to_string(),
20654 8,
20655 NonZeroU32::new(4).unwrap(),
20656 false,
20657 ),
20658 "hello\nworld"
20659 );
20660 assert_eq!(
20661 wrap_with_prefix(
20662 "// ".to_string(),
20663 "xx \nyy zz aa bb cc".to_string(),
20664 12,
20665 NonZeroU32::new(4).unwrap(),
20666 false,
20667 ),
20668 "// xx yy zz\n// aa bb cc"
20669 );
20670 assert_eq!(
20671 wrap_with_prefix(
20672 String::new(),
20673 "这是什么 \n 钢笔".to_string(),
20674 3,
20675 NonZeroU32::new(4).unwrap(),
20676 false,
20677 ),
20678 "这是什\n么 钢\n笔"
20679 );
20680}
20681
20682pub trait CollaborationHub {
20683 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20684 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20685 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20686}
20687
20688impl CollaborationHub for Entity<Project> {
20689 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20690 self.read(cx).collaborators()
20691 }
20692
20693 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20694 self.read(cx).user_store().read(cx).participant_indices()
20695 }
20696
20697 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20698 let this = self.read(cx);
20699 let user_ids = this.collaborators().values().map(|c| c.user_id);
20700 this.user_store().read(cx).participant_names(user_ids, cx)
20701 }
20702}
20703
20704pub trait SemanticsProvider {
20705 fn hover(
20706 &self,
20707 buffer: &Entity<Buffer>,
20708 position: text::Anchor,
20709 cx: &mut App,
20710 ) -> Option<Task<Vec<project::Hover>>>;
20711
20712 fn inline_values(
20713 &self,
20714 buffer_handle: Entity<Buffer>,
20715 range: Range<text::Anchor>,
20716 cx: &mut App,
20717 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20718
20719 fn inlay_hints(
20720 &self,
20721 buffer_handle: Entity<Buffer>,
20722 range: Range<text::Anchor>,
20723 cx: &mut App,
20724 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20725
20726 fn resolve_inlay_hint(
20727 &self,
20728 hint: InlayHint,
20729 buffer_handle: Entity<Buffer>,
20730 server_id: LanguageServerId,
20731 cx: &mut App,
20732 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20733
20734 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20735
20736 fn document_highlights(
20737 &self,
20738 buffer: &Entity<Buffer>,
20739 position: text::Anchor,
20740 cx: &mut App,
20741 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20742
20743 fn definitions(
20744 &self,
20745 buffer: &Entity<Buffer>,
20746 position: text::Anchor,
20747 kind: GotoDefinitionKind,
20748 cx: &mut App,
20749 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20750
20751 fn range_for_rename(
20752 &self,
20753 buffer: &Entity<Buffer>,
20754 position: text::Anchor,
20755 cx: &mut App,
20756 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20757
20758 fn perform_rename(
20759 &self,
20760 buffer: &Entity<Buffer>,
20761 position: text::Anchor,
20762 new_name: String,
20763 cx: &mut App,
20764 ) -> Option<Task<Result<ProjectTransaction>>>;
20765
20766 fn pull_diagnostics_for_buffer(
20767 &self,
20768 buffer: Entity<Buffer>,
20769 cx: &mut App,
20770 ) -> Task<anyhow::Result<()>>;
20771}
20772
20773pub trait CompletionProvider {
20774 fn completions(
20775 &self,
20776 excerpt_id: ExcerptId,
20777 buffer: &Entity<Buffer>,
20778 buffer_position: text::Anchor,
20779 trigger: CompletionContext,
20780 window: &mut Window,
20781 cx: &mut Context<Editor>,
20782 ) -> Task<Result<Vec<CompletionResponse>>>;
20783
20784 fn resolve_completions(
20785 &self,
20786 _buffer: Entity<Buffer>,
20787 _completion_indices: Vec<usize>,
20788 _completions: Rc<RefCell<Box<[Completion]>>>,
20789 _cx: &mut Context<Editor>,
20790 ) -> Task<Result<bool>> {
20791 Task::ready(Ok(false))
20792 }
20793
20794 fn apply_additional_edits_for_completion(
20795 &self,
20796 _buffer: Entity<Buffer>,
20797 _completions: Rc<RefCell<Box<[Completion]>>>,
20798 _completion_index: usize,
20799 _push_to_history: bool,
20800 _cx: &mut Context<Editor>,
20801 ) -> Task<Result<Option<language::Transaction>>> {
20802 Task::ready(Ok(None))
20803 }
20804
20805 fn is_completion_trigger(
20806 &self,
20807 buffer: &Entity<Buffer>,
20808 position: language::Anchor,
20809 text: &str,
20810 trigger_in_words: bool,
20811 menu_is_open: bool,
20812 cx: &mut Context<Editor>,
20813 ) -> bool;
20814
20815 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20816
20817 fn sort_completions(&self) -> bool {
20818 true
20819 }
20820
20821 fn filter_completions(&self) -> bool {
20822 true
20823 }
20824}
20825
20826pub trait CodeActionProvider {
20827 fn id(&self) -> Arc<str>;
20828
20829 fn code_actions(
20830 &self,
20831 buffer: &Entity<Buffer>,
20832 range: Range<text::Anchor>,
20833 window: &mut Window,
20834 cx: &mut App,
20835 ) -> Task<Result<Vec<CodeAction>>>;
20836
20837 fn apply_code_action(
20838 &self,
20839 buffer_handle: Entity<Buffer>,
20840 action: CodeAction,
20841 excerpt_id: ExcerptId,
20842 push_to_history: bool,
20843 window: &mut Window,
20844 cx: &mut App,
20845 ) -> Task<Result<ProjectTransaction>>;
20846}
20847
20848impl CodeActionProvider for Entity<Project> {
20849 fn id(&self) -> Arc<str> {
20850 "project".into()
20851 }
20852
20853 fn code_actions(
20854 &self,
20855 buffer: &Entity<Buffer>,
20856 range: Range<text::Anchor>,
20857 _window: &mut Window,
20858 cx: &mut App,
20859 ) -> Task<Result<Vec<CodeAction>>> {
20860 self.update(cx, |project, cx| {
20861 let code_lens = project.code_lens(buffer, range.clone(), cx);
20862 let code_actions = project.code_actions(buffer, range, None, cx);
20863 cx.background_spawn(async move {
20864 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20865 Ok(code_lens
20866 .context("code lens fetch")?
20867 .into_iter()
20868 .chain(code_actions.context("code action fetch")?)
20869 .collect())
20870 })
20871 })
20872 }
20873
20874 fn apply_code_action(
20875 &self,
20876 buffer_handle: Entity<Buffer>,
20877 action: CodeAction,
20878 _excerpt_id: ExcerptId,
20879 push_to_history: bool,
20880 _window: &mut Window,
20881 cx: &mut App,
20882 ) -> Task<Result<ProjectTransaction>> {
20883 self.update(cx, |project, cx| {
20884 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20885 })
20886 }
20887}
20888
20889fn snippet_completions(
20890 project: &Project,
20891 buffer: &Entity<Buffer>,
20892 buffer_position: text::Anchor,
20893 cx: &mut App,
20894) -> Task<Result<CompletionResponse>> {
20895 let languages = buffer.read(cx).languages_at(buffer_position);
20896 let snippet_store = project.snippets().read(cx);
20897
20898 let scopes: Vec<_> = languages
20899 .iter()
20900 .filter_map(|language| {
20901 let language_name = language.lsp_id();
20902 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20903
20904 if snippets.is_empty() {
20905 None
20906 } else {
20907 Some((language.default_scope(), snippets))
20908 }
20909 })
20910 .collect();
20911
20912 if scopes.is_empty() {
20913 return Task::ready(Ok(CompletionResponse {
20914 completions: vec![],
20915 is_incomplete: false,
20916 }));
20917 }
20918
20919 let snapshot = buffer.read(cx).text_snapshot();
20920 let chars: String = snapshot
20921 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20922 .collect();
20923 let executor = cx.background_executor().clone();
20924
20925 cx.background_spawn(async move {
20926 let mut is_incomplete = false;
20927 let mut completions: Vec<Completion> = Vec::new();
20928 for (scope, snippets) in scopes.into_iter() {
20929 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20930 let mut last_word = chars
20931 .chars()
20932 .take_while(|c| classifier.is_word(*c))
20933 .collect::<String>();
20934 last_word = last_word.chars().rev().collect();
20935
20936 if last_word.is_empty() {
20937 return Ok(CompletionResponse {
20938 completions: vec![],
20939 is_incomplete: true,
20940 });
20941 }
20942
20943 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20944 let to_lsp = |point: &text::Anchor| {
20945 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20946 point_to_lsp(end)
20947 };
20948 let lsp_end = to_lsp(&buffer_position);
20949
20950 let candidates = snippets
20951 .iter()
20952 .enumerate()
20953 .flat_map(|(ix, snippet)| {
20954 snippet
20955 .prefix
20956 .iter()
20957 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20958 })
20959 .collect::<Vec<StringMatchCandidate>>();
20960
20961 const MAX_RESULTS: usize = 100;
20962 let mut matches = fuzzy::match_strings(
20963 &candidates,
20964 &last_word,
20965 last_word.chars().any(|c| c.is_uppercase()),
20966 MAX_RESULTS,
20967 &Default::default(),
20968 executor.clone(),
20969 )
20970 .await;
20971
20972 if matches.len() >= MAX_RESULTS {
20973 is_incomplete = true;
20974 }
20975
20976 // Remove all candidates where the query's start does not match the start of any word in the candidate
20977 if let Some(query_start) = last_word.chars().next() {
20978 matches.retain(|string_match| {
20979 split_words(&string_match.string).any(|word| {
20980 // Check that the first codepoint of the word as lowercase matches the first
20981 // codepoint of the query as lowercase
20982 word.chars()
20983 .flat_map(|codepoint| codepoint.to_lowercase())
20984 .zip(query_start.to_lowercase())
20985 .all(|(word_cp, query_cp)| word_cp == query_cp)
20986 })
20987 });
20988 }
20989
20990 let matched_strings = matches
20991 .into_iter()
20992 .map(|m| m.string)
20993 .collect::<HashSet<_>>();
20994
20995 completions.extend(snippets.iter().filter_map(|snippet| {
20996 let matching_prefix = snippet
20997 .prefix
20998 .iter()
20999 .find(|prefix| matched_strings.contains(*prefix))?;
21000 let start = as_offset - last_word.len();
21001 let start = snapshot.anchor_before(start);
21002 let range = start..buffer_position;
21003 let lsp_start = to_lsp(&start);
21004 let lsp_range = lsp::Range {
21005 start: lsp_start,
21006 end: lsp_end,
21007 };
21008 Some(Completion {
21009 replace_range: range,
21010 new_text: snippet.body.clone(),
21011 source: CompletionSource::Lsp {
21012 insert_range: None,
21013 server_id: LanguageServerId(usize::MAX),
21014 resolved: true,
21015 lsp_completion: Box::new(lsp::CompletionItem {
21016 label: snippet.prefix.first().unwrap().clone(),
21017 kind: Some(CompletionItemKind::SNIPPET),
21018 label_details: snippet.description.as_ref().map(|description| {
21019 lsp::CompletionItemLabelDetails {
21020 detail: Some(description.clone()),
21021 description: None,
21022 }
21023 }),
21024 insert_text_format: Some(InsertTextFormat::SNIPPET),
21025 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21026 lsp::InsertReplaceEdit {
21027 new_text: snippet.body.clone(),
21028 insert: lsp_range,
21029 replace: lsp_range,
21030 },
21031 )),
21032 filter_text: Some(snippet.body.clone()),
21033 sort_text: Some(char::MAX.to_string()),
21034 ..lsp::CompletionItem::default()
21035 }),
21036 lsp_defaults: None,
21037 },
21038 label: CodeLabel {
21039 text: matching_prefix.clone(),
21040 runs: Vec::new(),
21041 filter_range: 0..matching_prefix.len(),
21042 },
21043 icon_path: None,
21044 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21045 single_line: snippet.name.clone().into(),
21046 plain_text: snippet
21047 .description
21048 .clone()
21049 .map(|description| description.into()),
21050 }),
21051 insert_text_mode: None,
21052 confirm: None,
21053 })
21054 }))
21055 }
21056
21057 Ok(CompletionResponse {
21058 completions,
21059 is_incomplete,
21060 })
21061 })
21062}
21063
21064impl CompletionProvider for Entity<Project> {
21065 fn completions(
21066 &self,
21067 _excerpt_id: ExcerptId,
21068 buffer: &Entity<Buffer>,
21069 buffer_position: text::Anchor,
21070 options: CompletionContext,
21071 _window: &mut Window,
21072 cx: &mut Context<Editor>,
21073 ) -> Task<Result<Vec<CompletionResponse>>> {
21074 self.update(cx, |project, cx| {
21075 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21076 let project_completions = project.completions(buffer, buffer_position, options, cx);
21077 cx.background_spawn(async move {
21078 let mut responses = project_completions.await?;
21079 let snippets = snippets.await?;
21080 if !snippets.completions.is_empty() {
21081 responses.push(snippets);
21082 }
21083 Ok(responses)
21084 })
21085 })
21086 }
21087
21088 fn resolve_completions(
21089 &self,
21090 buffer: Entity<Buffer>,
21091 completion_indices: Vec<usize>,
21092 completions: Rc<RefCell<Box<[Completion]>>>,
21093 cx: &mut Context<Editor>,
21094 ) -> Task<Result<bool>> {
21095 self.update(cx, |project, cx| {
21096 project.lsp_store().update(cx, |lsp_store, cx| {
21097 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21098 })
21099 })
21100 }
21101
21102 fn apply_additional_edits_for_completion(
21103 &self,
21104 buffer: Entity<Buffer>,
21105 completions: Rc<RefCell<Box<[Completion]>>>,
21106 completion_index: usize,
21107 push_to_history: bool,
21108 cx: &mut Context<Editor>,
21109 ) -> Task<Result<Option<language::Transaction>>> {
21110 self.update(cx, |project, cx| {
21111 project.lsp_store().update(cx, |lsp_store, cx| {
21112 lsp_store.apply_additional_edits_for_completion(
21113 buffer,
21114 completions,
21115 completion_index,
21116 push_to_history,
21117 cx,
21118 )
21119 })
21120 })
21121 }
21122
21123 fn is_completion_trigger(
21124 &self,
21125 buffer: &Entity<Buffer>,
21126 position: language::Anchor,
21127 text: &str,
21128 trigger_in_words: bool,
21129 menu_is_open: bool,
21130 cx: &mut Context<Editor>,
21131 ) -> bool {
21132 let mut chars = text.chars();
21133 let char = if let Some(char) = chars.next() {
21134 char
21135 } else {
21136 return false;
21137 };
21138 if chars.next().is_some() {
21139 return false;
21140 }
21141
21142 let buffer = buffer.read(cx);
21143 let snapshot = buffer.snapshot();
21144 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21145 return false;
21146 }
21147 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21148 if trigger_in_words && classifier.is_word(char) {
21149 return true;
21150 }
21151
21152 buffer.completion_triggers().contains(text)
21153 }
21154}
21155
21156impl SemanticsProvider for Entity<Project> {
21157 fn hover(
21158 &self,
21159 buffer: &Entity<Buffer>,
21160 position: text::Anchor,
21161 cx: &mut App,
21162 ) -> Option<Task<Vec<project::Hover>>> {
21163 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21164 }
21165
21166 fn document_highlights(
21167 &self,
21168 buffer: &Entity<Buffer>,
21169 position: text::Anchor,
21170 cx: &mut App,
21171 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21172 Some(self.update(cx, |project, cx| {
21173 project.document_highlights(buffer, position, cx)
21174 }))
21175 }
21176
21177 fn definitions(
21178 &self,
21179 buffer: &Entity<Buffer>,
21180 position: text::Anchor,
21181 kind: GotoDefinitionKind,
21182 cx: &mut App,
21183 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21184 Some(self.update(cx, |project, cx| match kind {
21185 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21186 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21187 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21188 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21189 }))
21190 }
21191
21192 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21193 // TODO: make this work for remote projects
21194 self.update(cx, |project, cx| {
21195 if project
21196 .active_debug_session(cx)
21197 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21198 {
21199 return true;
21200 }
21201
21202 buffer.update(cx, |buffer, cx| {
21203 project.any_language_server_supports_inlay_hints(buffer, cx)
21204 })
21205 })
21206 }
21207
21208 fn inline_values(
21209 &self,
21210 buffer_handle: Entity<Buffer>,
21211
21212 range: Range<text::Anchor>,
21213 cx: &mut App,
21214 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21215 self.update(cx, |project, cx| {
21216 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21217
21218 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21219 })
21220 }
21221
21222 fn inlay_hints(
21223 &self,
21224 buffer_handle: Entity<Buffer>,
21225 range: Range<text::Anchor>,
21226 cx: &mut App,
21227 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21228 Some(self.update(cx, |project, cx| {
21229 project.inlay_hints(buffer_handle, range, cx)
21230 }))
21231 }
21232
21233 fn resolve_inlay_hint(
21234 &self,
21235 hint: InlayHint,
21236 buffer_handle: Entity<Buffer>,
21237 server_id: LanguageServerId,
21238 cx: &mut App,
21239 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21240 Some(self.update(cx, |project, cx| {
21241 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21242 }))
21243 }
21244
21245 fn range_for_rename(
21246 &self,
21247 buffer: &Entity<Buffer>,
21248 position: text::Anchor,
21249 cx: &mut App,
21250 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21251 Some(self.update(cx, |project, cx| {
21252 let buffer = buffer.clone();
21253 let task = project.prepare_rename(buffer.clone(), position, cx);
21254 cx.spawn(async move |_, cx| {
21255 Ok(match task.await? {
21256 PrepareRenameResponse::Success(range) => Some(range),
21257 PrepareRenameResponse::InvalidPosition => None,
21258 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21259 // Fallback on using TreeSitter info to determine identifier range
21260 buffer.read_with(cx, |buffer, _| {
21261 let snapshot = buffer.snapshot();
21262 let (range, kind) = snapshot.surrounding_word(position);
21263 if kind != Some(CharKind::Word) {
21264 return None;
21265 }
21266 Some(
21267 snapshot.anchor_before(range.start)
21268 ..snapshot.anchor_after(range.end),
21269 )
21270 })?
21271 }
21272 })
21273 })
21274 }))
21275 }
21276
21277 fn perform_rename(
21278 &self,
21279 buffer: &Entity<Buffer>,
21280 position: text::Anchor,
21281 new_name: String,
21282 cx: &mut App,
21283 ) -> Option<Task<Result<ProjectTransaction>>> {
21284 Some(self.update(cx, |project, cx| {
21285 project.perform_rename(buffer.clone(), position, new_name, cx)
21286 }))
21287 }
21288
21289 fn pull_diagnostics_for_buffer(
21290 &self,
21291 buffer: Entity<Buffer>,
21292 cx: &mut App,
21293 ) -> Task<anyhow::Result<()>> {
21294 let diagnostics = self.update(cx, |project, cx| {
21295 project
21296 .lsp_store()
21297 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21298 });
21299 let project = self.clone();
21300 cx.spawn(async move |cx| {
21301 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21302 project.update(cx, |project, cx| {
21303 project.lsp_store().update(cx, |lsp_store, cx| {
21304 for diagnostics_set in diagnostics {
21305 let LspPullDiagnostics::Response {
21306 server_id,
21307 uri,
21308 diagnostics,
21309 } = diagnostics_set
21310 else {
21311 continue;
21312 };
21313
21314 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21315 let disk_based_sources = adapter
21316 .as_ref()
21317 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21318 .unwrap_or(&[]);
21319 match diagnostics {
21320 PulledDiagnostics::Unchanged { result_id } => {
21321 lsp_store
21322 .merge_diagnostics(
21323 server_id,
21324 lsp::PublishDiagnosticsParams {
21325 uri: uri.clone(),
21326 diagnostics: Vec::new(),
21327 version: None,
21328 },
21329 Some(result_id),
21330 DiagnosticSourceKind::Pulled,
21331 disk_based_sources,
21332 |_, _| true,
21333 cx,
21334 )
21335 .log_err();
21336 }
21337 PulledDiagnostics::Changed {
21338 diagnostics,
21339 result_id,
21340 } => {
21341 lsp_store
21342 .merge_diagnostics(
21343 server_id,
21344 lsp::PublishDiagnosticsParams {
21345 uri: uri.clone(),
21346 diagnostics,
21347 version: None,
21348 },
21349 result_id,
21350 DiagnosticSourceKind::Pulled,
21351 disk_based_sources,
21352 |old_diagnostic, _| match old_diagnostic.source_kind {
21353 DiagnosticSourceKind::Pulled => false,
21354 DiagnosticSourceKind::Other
21355 | DiagnosticSourceKind::Pushed => true,
21356 },
21357 cx,
21358 )
21359 .log_err();
21360 }
21361 }
21362 }
21363 })
21364 })
21365 })
21366 }
21367}
21368
21369fn inlay_hint_settings(
21370 location: Anchor,
21371 snapshot: &MultiBufferSnapshot,
21372 cx: &mut Context<Editor>,
21373) -> InlayHintSettings {
21374 let file = snapshot.file_at(location);
21375 let language = snapshot.language_at(location).map(|l| l.name());
21376 language_settings(language, file, cx).inlay_hints
21377}
21378
21379fn consume_contiguous_rows(
21380 contiguous_row_selections: &mut Vec<Selection<Point>>,
21381 selection: &Selection<Point>,
21382 display_map: &DisplaySnapshot,
21383 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21384) -> (MultiBufferRow, MultiBufferRow) {
21385 contiguous_row_selections.push(selection.clone());
21386 let start_row = MultiBufferRow(selection.start.row);
21387 let mut end_row = ending_row(selection, display_map);
21388
21389 while let Some(next_selection) = selections.peek() {
21390 if next_selection.start.row <= end_row.0 {
21391 end_row = ending_row(next_selection, display_map);
21392 contiguous_row_selections.push(selections.next().unwrap().clone());
21393 } else {
21394 break;
21395 }
21396 }
21397 (start_row, end_row)
21398}
21399
21400fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21401 if next_selection.end.column > 0 || next_selection.is_empty() {
21402 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21403 } else {
21404 MultiBufferRow(next_selection.end.row)
21405 }
21406}
21407
21408impl EditorSnapshot {
21409 pub fn remote_selections_in_range<'a>(
21410 &'a self,
21411 range: &'a Range<Anchor>,
21412 collaboration_hub: &dyn CollaborationHub,
21413 cx: &'a App,
21414 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21415 let participant_names = collaboration_hub.user_names(cx);
21416 let participant_indices = collaboration_hub.user_participant_indices(cx);
21417 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21418 let collaborators_by_replica_id = collaborators_by_peer_id
21419 .values()
21420 .map(|collaborator| (collaborator.replica_id, collaborator))
21421 .collect::<HashMap<_, _>>();
21422 self.buffer_snapshot
21423 .selections_in_range(range, false)
21424 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21425 if replica_id == AGENT_REPLICA_ID {
21426 Some(RemoteSelection {
21427 replica_id,
21428 selection,
21429 cursor_shape,
21430 line_mode,
21431 collaborator_id: CollaboratorId::Agent,
21432 user_name: Some("Agent".into()),
21433 color: cx.theme().players().agent(),
21434 })
21435 } else {
21436 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21437 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21438 let user_name = participant_names.get(&collaborator.user_id).cloned();
21439 Some(RemoteSelection {
21440 replica_id,
21441 selection,
21442 cursor_shape,
21443 line_mode,
21444 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21445 user_name,
21446 color: if let Some(index) = participant_index {
21447 cx.theme().players().color_for_participant(index.0)
21448 } else {
21449 cx.theme().players().absent()
21450 },
21451 })
21452 }
21453 })
21454 }
21455
21456 pub fn hunks_for_ranges(
21457 &self,
21458 ranges: impl IntoIterator<Item = Range<Point>>,
21459 ) -> Vec<MultiBufferDiffHunk> {
21460 let mut hunks = Vec::new();
21461 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21462 HashMap::default();
21463 for query_range in ranges {
21464 let query_rows =
21465 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21466 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21467 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21468 ) {
21469 // Include deleted hunks that are adjacent to the query range, because
21470 // otherwise they would be missed.
21471 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21472 if hunk.status().is_deleted() {
21473 intersects_range |= hunk.row_range.start == query_rows.end;
21474 intersects_range |= hunk.row_range.end == query_rows.start;
21475 }
21476 if intersects_range {
21477 if !processed_buffer_rows
21478 .entry(hunk.buffer_id)
21479 .or_default()
21480 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21481 {
21482 continue;
21483 }
21484 hunks.push(hunk);
21485 }
21486 }
21487 }
21488
21489 hunks
21490 }
21491
21492 fn display_diff_hunks_for_rows<'a>(
21493 &'a self,
21494 display_rows: Range<DisplayRow>,
21495 folded_buffers: &'a HashSet<BufferId>,
21496 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21497 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21498 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21499
21500 self.buffer_snapshot
21501 .diff_hunks_in_range(buffer_start..buffer_end)
21502 .filter_map(|hunk| {
21503 if folded_buffers.contains(&hunk.buffer_id) {
21504 return None;
21505 }
21506
21507 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21508 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21509
21510 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21511 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21512
21513 let display_hunk = if hunk_display_start.column() != 0 {
21514 DisplayDiffHunk::Folded {
21515 display_row: hunk_display_start.row(),
21516 }
21517 } else {
21518 let mut end_row = hunk_display_end.row();
21519 if hunk_display_end.column() > 0 {
21520 end_row.0 += 1;
21521 }
21522 let is_created_file = hunk.is_created_file();
21523 DisplayDiffHunk::Unfolded {
21524 status: hunk.status(),
21525 diff_base_byte_range: hunk.diff_base_byte_range,
21526 display_row_range: hunk_display_start.row()..end_row,
21527 multi_buffer_range: Anchor::range_in_buffer(
21528 hunk.excerpt_id,
21529 hunk.buffer_id,
21530 hunk.buffer_range,
21531 ),
21532 is_created_file,
21533 }
21534 };
21535
21536 Some(display_hunk)
21537 })
21538 }
21539
21540 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21541 self.display_snapshot.buffer_snapshot.language_at(position)
21542 }
21543
21544 pub fn is_focused(&self) -> bool {
21545 self.is_focused
21546 }
21547
21548 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21549 self.placeholder_text.as_ref()
21550 }
21551
21552 pub fn scroll_position(&self) -> gpui::Point<f32> {
21553 self.scroll_anchor.scroll_position(&self.display_snapshot)
21554 }
21555
21556 fn gutter_dimensions(
21557 &self,
21558 font_id: FontId,
21559 font_size: Pixels,
21560 max_line_number_width: Pixels,
21561 cx: &App,
21562 ) -> Option<GutterDimensions> {
21563 if !self.show_gutter {
21564 return None;
21565 }
21566
21567 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21568 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21569
21570 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21571 matches!(
21572 ProjectSettings::get_global(cx).git.git_gutter,
21573 Some(GitGutterSetting::TrackedFiles)
21574 )
21575 });
21576 let gutter_settings = EditorSettings::get_global(cx).gutter;
21577 let show_line_numbers = self
21578 .show_line_numbers
21579 .unwrap_or(gutter_settings.line_numbers);
21580 let line_gutter_width = if show_line_numbers {
21581 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21582 let min_width_for_number_on_gutter =
21583 em_advance * gutter_settings.min_line_number_digits as f32;
21584 max_line_number_width.max(min_width_for_number_on_gutter)
21585 } else {
21586 0.0.into()
21587 };
21588
21589 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21590 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21591
21592 let git_blame_entries_width =
21593 self.git_blame_gutter_max_author_length
21594 .map(|max_author_length| {
21595 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21596 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21597
21598 /// The number of characters to dedicate to gaps and margins.
21599 const SPACING_WIDTH: usize = 4;
21600
21601 let max_char_count = max_author_length.min(renderer.max_author_length())
21602 + ::git::SHORT_SHA_LENGTH
21603 + MAX_RELATIVE_TIMESTAMP.len()
21604 + SPACING_WIDTH;
21605
21606 em_advance * max_char_count
21607 });
21608
21609 let is_singleton = self.buffer_snapshot.is_singleton();
21610
21611 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21612 left_padding += if !is_singleton {
21613 em_width * 4.0
21614 } else if show_runnables || show_breakpoints {
21615 em_width * 3.0
21616 } else if show_git_gutter && show_line_numbers {
21617 em_width * 2.0
21618 } else if show_git_gutter || show_line_numbers {
21619 em_width
21620 } else {
21621 px(0.)
21622 };
21623
21624 let shows_folds = is_singleton && gutter_settings.folds;
21625
21626 let right_padding = if shows_folds && show_line_numbers {
21627 em_width * 4.0
21628 } else if shows_folds || (!is_singleton && show_line_numbers) {
21629 em_width * 3.0
21630 } else if show_line_numbers {
21631 em_width
21632 } else {
21633 px(0.)
21634 };
21635
21636 Some(GutterDimensions {
21637 left_padding,
21638 right_padding,
21639 width: line_gutter_width + left_padding + right_padding,
21640 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21641 git_blame_entries_width,
21642 })
21643 }
21644
21645 pub fn render_crease_toggle(
21646 &self,
21647 buffer_row: MultiBufferRow,
21648 row_contains_cursor: bool,
21649 editor: Entity<Editor>,
21650 window: &mut Window,
21651 cx: &mut App,
21652 ) -> Option<AnyElement> {
21653 let folded = self.is_line_folded(buffer_row);
21654 let mut is_foldable = false;
21655
21656 if let Some(crease) = self
21657 .crease_snapshot
21658 .query_row(buffer_row, &self.buffer_snapshot)
21659 {
21660 is_foldable = true;
21661 match crease {
21662 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21663 if let Some(render_toggle) = render_toggle {
21664 let toggle_callback =
21665 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21666 if folded {
21667 editor.update(cx, |editor, cx| {
21668 editor.fold_at(buffer_row, window, cx)
21669 });
21670 } else {
21671 editor.update(cx, |editor, cx| {
21672 editor.unfold_at(buffer_row, window, cx)
21673 });
21674 }
21675 });
21676 return Some((render_toggle)(
21677 buffer_row,
21678 folded,
21679 toggle_callback,
21680 window,
21681 cx,
21682 ));
21683 }
21684 }
21685 }
21686 }
21687
21688 is_foldable |= self.starts_indent(buffer_row);
21689
21690 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21691 Some(
21692 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21693 .toggle_state(folded)
21694 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21695 if folded {
21696 this.unfold_at(buffer_row, window, cx);
21697 } else {
21698 this.fold_at(buffer_row, window, cx);
21699 }
21700 }))
21701 .into_any_element(),
21702 )
21703 } else {
21704 None
21705 }
21706 }
21707
21708 pub fn render_crease_trailer(
21709 &self,
21710 buffer_row: MultiBufferRow,
21711 window: &mut Window,
21712 cx: &mut App,
21713 ) -> Option<AnyElement> {
21714 let folded = self.is_line_folded(buffer_row);
21715 if let Crease::Inline { render_trailer, .. } = self
21716 .crease_snapshot
21717 .query_row(buffer_row, &self.buffer_snapshot)?
21718 {
21719 let render_trailer = render_trailer.as_ref()?;
21720 Some(render_trailer(buffer_row, folded, window, cx))
21721 } else {
21722 None
21723 }
21724 }
21725}
21726
21727impl Deref for EditorSnapshot {
21728 type Target = DisplaySnapshot;
21729
21730 fn deref(&self) -> &Self::Target {
21731 &self.display_snapshot
21732 }
21733}
21734
21735#[derive(Clone, Debug, PartialEq, Eq)]
21736pub enum EditorEvent {
21737 InputIgnored {
21738 text: Arc<str>,
21739 },
21740 InputHandled {
21741 utf16_range_to_replace: Option<Range<isize>>,
21742 text: Arc<str>,
21743 },
21744 ExcerptsAdded {
21745 buffer: Entity<Buffer>,
21746 predecessor: ExcerptId,
21747 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21748 },
21749 ExcerptsRemoved {
21750 ids: Vec<ExcerptId>,
21751 removed_buffer_ids: Vec<BufferId>,
21752 },
21753 BufferFoldToggled {
21754 ids: Vec<ExcerptId>,
21755 folded: bool,
21756 },
21757 ExcerptsEdited {
21758 ids: Vec<ExcerptId>,
21759 },
21760 ExcerptsExpanded {
21761 ids: Vec<ExcerptId>,
21762 },
21763 BufferEdited,
21764 Edited {
21765 transaction_id: clock::Lamport,
21766 },
21767 Reparsed(BufferId),
21768 Focused,
21769 FocusedIn,
21770 Blurred,
21771 DirtyChanged,
21772 Saved,
21773 TitleChanged,
21774 DiffBaseChanged,
21775 SelectionsChanged {
21776 local: bool,
21777 },
21778 ScrollPositionChanged {
21779 local: bool,
21780 autoscroll: bool,
21781 },
21782 Closed,
21783 TransactionUndone {
21784 transaction_id: clock::Lamport,
21785 },
21786 TransactionBegun {
21787 transaction_id: clock::Lamport,
21788 },
21789 Reloaded,
21790 CursorShapeChanged,
21791 PushedToNavHistory {
21792 anchor: Anchor,
21793 is_deactivate: bool,
21794 },
21795}
21796
21797impl EventEmitter<EditorEvent> for Editor {}
21798
21799impl Focusable for Editor {
21800 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21801 self.focus_handle.clone()
21802 }
21803}
21804
21805impl Render for Editor {
21806 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21807 let settings = ThemeSettings::get_global(cx);
21808
21809 let mut text_style = match self.mode {
21810 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21811 color: cx.theme().colors().editor_foreground,
21812 font_family: settings.ui_font.family.clone(),
21813 font_features: settings.ui_font.features.clone(),
21814 font_fallbacks: settings.ui_font.fallbacks.clone(),
21815 font_size: rems(0.875).into(),
21816 font_weight: settings.ui_font.weight,
21817 line_height: relative(settings.buffer_line_height.value()),
21818 ..Default::default()
21819 },
21820 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21821 color: cx.theme().colors().editor_foreground,
21822 font_family: settings.buffer_font.family.clone(),
21823 font_features: settings.buffer_font.features.clone(),
21824 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21825 font_size: settings.buffer_font_size(cx).into(),
21826 font_weight: settings.buffer_font.weight,
21827 line_height: relative(settings.buffer_line_height.value()),
21828 ..Default::default()
21829 },
21830 };
21831 if let Some(text_style_refinement) = &self.text_style_refinement {
21832 text_style.refine(text_style_refinement)
21833 }
21834
21835 let background = match self.mode {
21836 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21837 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21838 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21839 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21840 };
21841
21842 EditorElement::new(
21843 &cx.entity(),
21844 EditorStyle {
21845 background,
21846 local_player: cx.theme().players().local(),
21847 text: text_style,
21848 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21849 syntax: cx.theme().syntax().clone(),
21850 status: cx.theme().status().clone(),
21851 inlay_hints_style: make_inlay_hints_style(cx),
21852 inline_completion_styles: make_suggestion_styles(cx),
21853 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21854 show_underlines: !self.mode.is_minimap(),
21855 },
21856 )
21857 }
21858}
21859
21860impl EntityInputHandler for Editor {
21861 fn text_for_range(
21862 &mut self,
21863 range_utf16: Range<usize>,
21864 adjusted_range: &mut Option<Range<usize>>,
21865 _: &mut Window,
21866 cx: &mut Context<Self>,
21867 ) -> Option<String> {
21868 let snapshot = self.buffer.read(cx).read(cx);
21869 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21870 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21871 if (start.0..end.0) != range_utf16 {
21872 adjusted_range.replace(start.0..end.0);
21873 }
21874 Some(snapshot.text_for_range(start..end).collect())
21875 }
21876
21877 fn selected_text_range(
21878 &mut self,
21879 ignore_disabled_input: bool,
21880 _: &mut Window,
21881 cx: &mut Context<Self>,
21882 ) -> Option<UTF16Selection> {
21883 // Prevent the IME menu from appearing when holding down an alphabetic key
21884 // while input is disabled.
21885 if !ignore_disabled_input && !self.input_enabled {
21886 return None;
21887 }
21888
21889 let selection = self.selections.newest::<OffsetUtf16>(cx);
21890 let range = selection.range();
21891
21892 Some(UTF16Selection {
21893 range: range.start.0..range.end.0,
21894 reversed: selection.reversed,
21895 })
21896 }
21897
21898 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21899 let snapshot = self.buffer.read(cx).read(cx);
21900 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21901 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21902 }
21903
21904 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21905 self.clear_highlights::<InputComposition>(cx);
21906 self.ime_transaction.take();
21907 }
21908
21909 fn replace_text_in_range(
21910 &mut self,
21911 range_utf16: Option<Range<usize>>,
21912 text: &str,
21913 window: &mut Window,
21914 cx: &mut Context<Self>,
21915 ) {
21916 if !self.input_enabled {
21917 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21918 return;
21919 }
21920
21921 self.transact(window, cx, |this, window, cx| {
21922 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21923 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21924 Some(this.selection_replacement_ranges(range_utf16, cx))
21925 } else {
21926 this.marked_text_ranges(cx)
21927 };
21928
21929 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21930 let newest_selection_id = this.selections.newest_anchor().id;
21931 this.selections
21932 .all::<OffsetUtf16>(cx)
21933 .iter()
21934 .zip(ranges_to_replace.iter())
21935 .find_map(|(selection, range)| {
21936 if selection.id == newest_selection_id {
21937 Some(
21938 (range.start.0 as isize - selection.head().0 as isize)
21939 ..(range.end.0 as isize - selection.head().0 as isize),
21940 )
21941 } else {
21942 None
21943 }
21944 })
21945 });
21946
21947 cx.emit(EditorEvent::InputHandled {
21948 utf16_range_to_replace: range_to_replace,
21949 text: text.into(),
21950 });
21951
21952 if let Some(new_selected_ranges) = new_selected_ranges {
21953 this.change_selections(None, window, cx, |selections| {
21954 selections.select_ranges(new_selected_ranges)
21955 });
21956 this.backspace(&Default::default(), window, cx);
21957 }
21958
21959 this.handle_input(text, window, cx);
21960 });
21961
21962 if let Some(transaction) = self.ime_transaction {
21963 self.buffer.update(cx, |buffer, cx| {
21964 buffer.group_until_transaction(transaction, cx);
21965 });
21966 }
21967
21968 self.unmark_text(window, cx);
21969 }
21970
21971 fn replace_and_mark_text_in_range(
21972 &mut self,
21973 range_utf16: Option<Range<usize>>,
21974 text: &str,
21975 new_selected_range_utf16: Option<Range<usize>>,
21976 window: &mut Window,
21977 cx: &mut Context<Self>,
21978 ) {
21979 if !self.input_enabled {
21980 return;
21981 }
21982
21983 let transaction = self.transact(window, cx, |this, window, cx| {
21984 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21985 let snapshot = this.buffer.read(cx).read(cx);
21986 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21987 for marked_range in &mut marked_ranges {
21988 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21989 marked_range.start.0 += relative_range_utf16.start;
21990 marked_range.start =
21991 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21992 marked_range.end =
21993 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21994 }
21995 }
21996 Some(marked_ranges)
21997 } else if let Some(range_utf16) = range_utf16 {
21998 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21999 Some(this.selection_replacement_ranges(range_utf16, cx))
22000 } else {
22001 None
22002 };
22003
22004 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22005 let newest_selection_id = this.selections.newest_anchor().id;
22006 this.selections
22007 .all::<OffsetUtf16>(cx)
22008 .iter()
22009 .zip(ranges_to_replace.iter())
22010 .find_map(|(selection, range)| {
22011 if selection.id == newest_selection_id {
22012 Some(
22013 (range.start.0 as isize - selection.head().0 as isize)
22014 ..(range.end.0 as isize - selection.head().0 as isize),
22015 )
22016 } else {
22017 None
22018 }
22019 })
22020 });
22021
22022 cx.emit(EditorEvent::InputHandled {
22023 utf16_range_to_replace: range_to_replace,
22024 text: text.into(),
22025 });
22026
22027 if let Some(ranges) = ranges_to_replace {
22028 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22029 }
22030
22031 let marked_ranges = {
22032 let snapshot = this.buffer.read(cx).read(cx);
22033 this.selections
22034 .disjoint_anchors()
22035 .iter()
22036 .map(|selection| {
22037 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22038 })
22039 .collect::<Vec<_>>()
22040 };
22041
22042 if text.is_empty() {
22043 this.unmark_text(window, cx);
22044 } else {
22045 this.highlight_text::<InputComposition>(
22046 marked_ranges.clone(),
22047 HighlightStyle {
22048 underline: Some(UnderlineStyle {
22049 thickness: px(1.),
22050 color: None,
22051 wavy: false,
22052 }),
22053 ..Default::default()
22054 },
22055 cx,
22056 );
22057 }
22058
22059 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22060 let use_autoclose = this.use_autoclose;
22061 let use_auto_surround = this.use_auto_surround;
22062 this.set_use_autoclose(false);
22063 this.set_use_auto_surround(false);
22064 this.handle_input(text, window, cx);
22065 this.set_use_autoclose(use_autoclose);
22066 this.set_use_auto_surround(use_auto_surround);
22067
22068 if let Some(new_selected_range) = new_selected_range_utf16 {
22069 let snapshot = this.buffer.read(cx).read(cx);
22070 let new_selected_ranges = marked_ranges
22071 .into_iter()
22072 .map(|marked_range| {
22073 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22074 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22075 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22076 snapshot.clip_offset_utf16(new_start, Bias::Left)
22077 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22078 })
22079 .collect::<Vec<_>>();
22080
22081 drop(snapshot);
22082 this.change_selections(None, window, cx, |selections| {
22083 selections.select_ranges(new_selected_ranges)
22084 });
22085 }
22086 });
22087
22088 self.ime_transaction = self.ime_transaction.or(transaction);
22089 if let Some(transaction) = self.ime_transaction {
22090 self.buffer.update(cx, |buffer, cx| {
22091 buffer.group_until_transaction(transaction, cx);
22092 });
22093 }
22094
22095 if self.text_highlights::<InputComposition>(cx).is_none() {
22096 self.ime_transaction.take();
22097 }
22098 }
22099
22100 fn bounds_for_range(
22101 &mut self,
22102 range_utf16: Range<usize>,
22103 element_bounds: gpui::Bounds<Pixels>,
22104 window: &mut Window,
22105 cx: &mut Context<Self>,
22106 ) -> Option<gpui::Bounds<Pixels>> {
22107 let text_layout_details = self.text_layout_details(window);
22108 let gpui::Size {
22109 width: em_width,
22110 height: line_height,
22111 } = self.character_size(window);
22112
22113 let snapshot = self.snapshot(window, cx);
22114 let scroll_position = snapshot.scroll_position();
22115 let scroll_left = scroll_position.x * em_width;
22116
22117 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22118 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22119 + self.gutter_dimensions.width
22120 + self.gutter_dimensions.margin;
22121 let y = line_height * (start.row().as_f32() - scroll_position.y);
22122
22123 Some(Bounds {
22124 origin: element_bounds.origin + point(x, y),
22125 size: size(em_width, line_height),
22126 })
22127 }
22128
22129 fn character_index_for_point(
22130 &mut self,
22131 point: gpui::Point<Pixels>,
22132 _window: &mut Window,
22133 _cx: &mut Context<Self>,
22134 ) -> Option<usize> {
22135 let position_map = self.last_position_map.as_ref()?;
22136 if !position_map.text_hitbox.contains(&point) {
22137 return None;
22138 }
22139 let display_point = position_map.point_for_position(point).previous_valid;
22140 let anchor = position_map
22141 .snapshot
22142 .display_point_to_anchor(display_point, Bias::Left);
22143 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22144 Some(utf16_offset.0)
22145 }
22146}
22147
22148trait SelectionExt {
22149 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22150 fn spanned_rows(
22151 &self,
22152 include_end_if_at_line_start: bool,
22153 map: &DisplaySnapshot,
22154 ) -> Range<MultiBufferRow>;
22155}
22156
22157impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22158 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22159 let start = self
22160 .start
22161 .to_point(&map.buffer_snapshot)
22162 .to_display_point(map);
22163 let end = self
22164 .end
22165 .to_point(&map.buffer_snapshot)
22166 .to_display_point(map);
22167 if self.reversed {
22168 end..start
22169 } else {
22170 start..end
22171 }
22172 }
22173
22174 fn spanned_rows(
22175 &self,
22176 include_end_if_at_line_start: bool,
22177 map: &DisplaySnapshot,
22178 ) -> Range<MultiBufferRow> {
22179 let start = self.start.to_point(&map.buffer_snapshot);
22180 let mut end = self.end.to_point(&map.buffer_snapshot);
22181 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22182 end.row -= 1;
22183 }
22184
22185 let buffer_start = map.prev_line_boundary(start).0;
22186 let buffer_end = map.next_line_boundary(end).0;
22187 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22188 }
22189}
22190
22191impl<T: InvalidationRegion> InvalidationStack<T> {
22192 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22193 where
22194 S: Clone + ToOffset,
22195 {
22196 while let Some(region) = self.last() {
22197 let all_selections_inside_invalidation_ranges =
22198 if selections.len() == region.ranges().len() {
22199 selections
22200 .iter()
22201 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22202 .all(|(selection, invalidation_range)| {
22203 let head = selection.head().to_offset(buffer);
22204 invalidation_range.start <= head && invalidation_range.end >= head
22205 })
22206 } else {
22207 false
22208 };
22209
22210 if all_selections_inside_invalidation_ranges {
22211 break;
22212 } else {
22213 self.pop();
22214 }
22215 }
22216 }
22217}
22218
22219impl<T> Default for InvalidationStack<T> {
22220 fn default() -> Self {
22221 Self(Default::default())
22222 }
22223}
22224
22225impl<T> Deref for InvalidationStack<T> {
22226 type Target = Vec<T>;
22227
22228 fn deref(&self) -> &Self::Target {
22229 &self.0
22230 }
22231}
22232
22233impl<T> DerefMut for InvalidationStack<T> {
22234 fn deref_mut(&mut self) -> &mut Self::Target {
22235 &mut self.0
22236 }
22237}
22238
22239impl InvalidationRegion for SnippetState {
22240 fn ranges(&self) -> &[Range<Anchor>] {
22241 &self.ranges[self.active_index]
22242 }
22243}
22244
22245fn inline_completion_edit_text(
22246 current_snapshot: &BufferSnapshot,
22247 edits: &[(Range<Anchor>, String)],
22248 edit_preview: &EditPreview,
22249 include_deletions: bool,
22250 cx: &App,
22251) -> HighlightedText {
22252 let edits = edits
22253 .iter()
22254 .map(|(anchor, text)| {
22255 (
22256 anchor.start.text_anchor..anchor.end.text_anchor,
22257 text.clone(),
22258 )
22259 })
22260 .collect::<Vec<_>>();
22261
22262 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22263}
22264
22265pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22266 match severity {
22267 lsp::DiagnosticSeverity::ERROR => colors.error,
22268 lsp::DiagnosticSeverity::WARNING => colors.warning,
22269 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22270 lsp::DiagnosticSeverity::HINT => colors.info,
22271 _ => colors.ignored,
22272 }
22273}
22274
22275pub fn styled_runs_for_code_label<'a>(
22276 label: &'a CodeLabel,
22277 syntax_theme: &'a theme::SyntaxTheme,
22278) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22279 let fade_out = HighlightStyle {
22280 fade_out: Some(0.35),
22281 ..Default::default()
22282 };
22283
22284 let mut prev_end = label.filter_range.end;
22285 label
22286 .runs
22287 .iter()
22288 .enumerate()
22289 .flat_map(move |(ix, (range, highlight_id))| {
22290 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22291 style
22292 } else {
22293 return Default::default();
22294 };
22295 let mut muted_style = style;
22296 muted_style.highlight(fade_out);
22297
22298 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22299 if range.start >= label.filter_range.end {
22300 if range.start > prev_end {
22301 runs.push((prev_end..range.start, fade_out));
22302 }
22303 runs.push((range.clone(), muted_style));
22304 } else if range.end <= label.filter_range.end {
22305 runs.push((range.clone(), style));
22306 } else {
22307 runs.push((range.start..label.filter_range.end, style));
22308 runs.push((label.filter_range.end..range.end, muted_style));
22309 }
22310 prev_end = cmp::max(prev_end, range.end);
22311
22312 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22313 runs.push((prev_end..label.text.len(), fade_out));
22314 }
22315
22316 runs
22317 })
22318}
22319
22320pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22321 let mut prev_index = 0;
22322 let mut prev_codepoint: Option<char> = None;
22323 text.char_indices()
22324 .chain([(text.len(), '\0')])
22325 .filter_map(move |(index, codepoint)| {
22326 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22327 let is_boundary = index == text.len()
22328 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22329 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22330 if is_boundary {
22331 let chunk = &text[prev_index..index];
22332 prev_index = index;
22333 Some(chunk)
22334 } else {
22335 None
22336 }
22337 })
22338}
22339
22340pub trait RangeToAnchorExt: Sized {
22341 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22342
22343 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22344 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22345 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22346 }
22347}
22348
22349impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22350 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22351 let start_offset = self.start.to_offset(snapshot);
22352 let end_offset = self.end.to_offset(snapshot);
22353 if start_offset == end_offset {
22354 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22355 } else {
22356 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22357 }
22358 }
22359}
22360
22361pub trait RowExt {
22362 fn as_f32(&self) -> f32;
22363
22364 fn next_row(&self) -> Self;
22365
22366 fn previous_row(&self) -> Self;
22367
22368 fn minus(&self, other: Self) -> u32;
22369}
22370
22371impl RowExt for DisplayRow {
22372 fn as_f32(&self) -> f32 {
22373 self.0 as f32
22374 }
22375
22376 fn next_row(&self) -> Self {
22377 Self(self.0 + 1)
22378 }
22379
22380 fn previous_row(&self) -> Self {
22381 Self(self.0.saturating_sub(1))
22382 }
22383
22384 fn minus(&self, other: Self) -> u32 {
22385 self.0 - other.0
22386 }
22387}
22388
22389impl RowExt for MultiBufferRow {
22390 fn as_f32(&self) -> f32 {
22391 self.0 as f32
22392 }
22393
22394 fn next_row(&self) -> Self {
22395 Self(self.0 + 1)
22396 }
22397
22398 fn previous_row(&self) -> Self {
22399 Self(self.0.saturating_sub(1))
22400 }
22401
22402 fn minus(&self, other: Self) -> u32 {
22403 self.0 - other.0
22404 }
22405}
22406
22407trait RowRangeExt {
22408 type Row;
22409
22410 fn len(&self) -> usize;
22411
22412 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22413}
22414
22415impl RowRangeExt for Range<MultiBufferRow> {
22416 type Row = MultiBufferRow;
22417
22418 fn len(&self) -> usize {
22419 (self.end.0 - self.start.0) as usize
22420 }
22421
22422 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22423 (self.start.0..self.end.0).map(MultiBufferRow)
22424 }
22425}
22426
22427impl RowRangeExt for Range<DisplayRow> {
22428 type Row = DisplayRow;
22429
22430 fn len(&self) -> usize {
22431 (self.end.0 - self.start.0) as usize
22432 }
22433
22434 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22435 (self.start.0..self.end.0).map(DisplayRow)
22436 }
22437}
22438
22439/// If select range has more than one line, we
22440/// just point the cursor to range.start.
22441fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22442 if range.start.row == range.end.row {
22443 range
22444 } else {
22445 range.start..range.start
22446 }
22447}
22448pub struct KillRing(ClipboardItem);
22449impl Global for KillRing {}
22450
22451const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22452
22453enum BreakpointPromptEditAction {
22454 Log,
22455 Condition,
22456 HitCondition,
22457}
22458
22459struct BreakpointPromptEditor {
22460 pub(crate) prompt: Entity<Editor>,
22461 editor: WeakEntity<Editor>,
22462 breakpoint_anchor: Anchor,
22463 breakpoint: Breakpoint,
22464 edit_action: BreakpointPromptEditAction,
22465 block_ids: HashSet<CustomBlockId>,
22466 editor_margins: Arc<Mutex<EditorMargins>>,
22467 _subscriptions: Vec<Subscription>,
22468}
22469
22470impl BreakpointPromptEditor {
22471 const MAX_LINES: u8 = 4;
22472
22473 fn new(
22474 editor: WeakEntity<Editor>,
22475 breakpoint_anchor: Anchor,
22476 breakpoint: Breakpoint,
22477 edit_action: BreakpointPromptEditAction,
22478 window: &mut Window,
22479 cx: &mut Context<Self>,
22480 ) -> Self {
22481 let base_text = match edit_action {
22482 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22483 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22484 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22485 }
22486 .map(|msg| msg.to_string())
22487 .unwrap_or_default();
22488
22489 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22490 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22491
22492 let prompt = cx.new(|cx| {
22493 let mut prompt = Editor::new(
22494 EditorMode::AutoHeight {
22495 max_lines: Self::MAX_LINES as usize,
22496 },
22497 buffer,
22498 None,
22499 window,
22500 cx,
22501 );
22502 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22503 prompt.set_show_cursor_when_unfocused(false, cx);
22504 prompt.set_placeholder_text(
22505 match edit_action {
22506 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22507 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22508 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22509 },
22510 cx,
22511 );
22512
22513 prompt
22514 });
22515
22516 Self {
22517 prompt,
22518 editor,
22519 breakpoint_anchor,
22520 breakpoint,
22521 edit_action,
22522 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22523 block_ids: Default::default(),
22524 _subscriptions: vec![],
22525 }
22526 }
22527
22528 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22529 self.block_ids.extend(block_ids)
22530 }
22531
22532 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22533 if let Some(editor) = self.editor.upgrade() {
22534 let message = self
22535 .prompt
22536 .read(cx)
22537 .buffer
22538 .read(cx)
22539 .as_singleton()
22540 .expect("A multi buffer in breakpoint prompt isn't possible")
22541 .read(cx)
22542 .as_rope()
22543 .to_string();
22544
22545 editor.update(cx, |editor, cx| {
22546 editor.edit_breakpoint_at_anchor(
22547 self.breakpoint_anchor,
22548 self.breakpoint.clone(),
22549 match self.edit_action {
22550 BreakpointPromptEditAction::Log => {
22551 BreakpointEditAction::EditLogMessage(message.into())
22552 }
22553 BreakpointPromptEditAction::Condition => {
22554 BreakpointEditAction::EditCondition(message.into())
22555 }
22556 BreakpointPromptEditAction::HitCondition => {
22557 BreakpointEditAction::EditHitCondition(message.into())
22558 }
22559 },
22560 cx,
22561 );
22562
22563 editor.remove_blocks(self.block_ids.clone(), None, cx);
22564 cx.focus_self(window);
22565 });
22566 }
22567 }
22568
22569 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22570 self.editor
22571 .update(cx, |editor, cx| {
22572 editor.remove_blocks(self.block_ids.clone(), None, cx);
22573 window.focus(&editor.focus_handle);
22574 })
22575 .log_err();
22576 }
22577
22578 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22579 let settings = ThemeSettings::get_global(cx);
22580 let text_style = TextStyle {
22581 color: if self.prompt.read(cx).read_only(cx) {
22582 cx.theme().colors().text_disabled
22583 } else {
22584 cx.theme().colors().text
22585 },
22586 font_family: settings.buffer_font.family.clone(),
22587 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22588 font_size: settings.buffer_font_size(cx).into(),
22589 font_weight: settings.buffer_font.weight,
22590 line_height: relative(settings.buffer_line_height.value()),
22591 ..Default::default()
22592 };
22593 EditorElement::new(
22594 &self.prompt,
22595 EditorStyle {
22596 background: cx.theme().colors().editor_background,
22597 local_player: cx.theme().players().local(),
22598 text: text_style,
22599 ..Default::default()
22600 },
22601 )
22602 }
22603}
22604
22605impl Render for BreakpointPromptEditor {
22606 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22607 let editor_margins = *self.editor_margins.lock();
22608 let gutter_dimensions = editor_margins.gutter;
22609 h_flex()
22610 .key_context("Editor")
22611 .bg(cx.theme().colors().editor_background)
22612 .border_y_1()
22613 .border_color(cx.theme().status().info_border)
22614 .size_full()
22615 .py(window.line_height() / 2.5)
22616 .on_action(cx.listener(Self::confirm))
22617 .on_action(cx.listener(Self::cancel))
22618 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22619 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22620 }
22621}
22622
22623impl Focusable for BreakpointPromptEditor {
22624 fn focus_handle(&self, cx: &App) -> FocusHandle {
22625 self.prompt.focus_handle(cx)
22626 }
22627}
22628
22629fn all_edits_insertions_or_deletions(
22630 edits: &Vec<(Range<Anchor>, String)>,
22631 snapshot: &MultiBufferSnapshot,
22632) -> bool {
22633 let mut all_insertions = true;
22634 let mut all_deletions = true;
22635
22636 for (range, new_text) in edits.iter() {
22637 let range_is_empty = range.to_offset(&snapshot).is_empty();
22638 let text_is_empty = new_text.is_empty();
22639
22640 if range_is_empty != text_is_empty {
22641 if range_is_empty {
22642 all_deletions = false;
22643 } else {
22644 all_insertions = false;
22645 }
22646 } else {
22647 return false;
22648 }
22649
22650 if !all_insertions && !all_deletions {
22651 return false;
22652 }
22653 }
22654 all_insertions || all_deletions
22655}
22656
22657struct MissingEditPredictionKeybindingTooltip;
22658
22659impl Render for MissingEditPredictionKeybindingTooltip {
22660 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22661 ui::tooltip_container(window, cx, |container, _, cx| {
22662 container
22663 .flex_shrink_0()
22664 .max_w_80()
22665 .min_h(rems_from_px(124.))
22666 .justify_between()
22667 .child(
22668 v_flex()
22669 .flex_1()
22670 .text_ui_sm(cx)
22671 .child(Label::new("Conflict with Accept Keybinding"))
22672 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22673 )
22674 .child(
22675 h_flex()
22676 .pb_1()
22677 .gap_1()
22678 .items_end()
22679 .w_full()
22680 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22681 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22682 }))
22683 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22684 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22685 })),
22686 )
22687 })
22688 }
22689}
22690
22691#[derive(Debug, Clone, Copy, PartialEq)]
22692pub struct LineHighlight {
22693 pub background: Background,
22694 pub border: Option<gpui::Hsla>,
22695 pub include_gutter: bool,
22696 pub type_id: Option<TypeId>,
22697}
22698
22699fn render_diff_hunk_controls(
22700 row: u32,
22701 status: &DiffHunkStatus,
22702 hunk_range: Range<Anchor>,
22703 is_created_file: bool,
22704 line_height: Pixels,
22705 editor: &Entity<Editor>,
22706 _window: &mut Window,
22707 cx: &mut App,
22708) -> AnyElement {
22709 h_flex()
22710 .h(line_height)
22711 .mr_1()
22712 .gap_1()
22713 .px_0p5()
22714 .pb_1()
22715 .border_x_1()
22716 .border_b_1()
22717 .border_color(cx.theme().colors().border_variant)
22718 .rounded_b_lg()
22719 .bg(cx.theme().colors().editor_background)
22720 .gap_1()
22721 .block_mouse_except_scroll()
22722 .shadow_md()
22723 .child(if status.has_secondary_hunk() {
22724 Button::new(("stage", row as u64), "Stage")
22725 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22726 .tooltip({
22727 let focus_handle = editor.focus_handle(cx);
22728 move |window, cx| {
22729 Tooltip::for_action_in(
22730 "Stage Hunk",
22731 &::git::ToggleStaged,
22732 &focus_handle,
22733 window,
22734 cx,
22735 )
22736 }
22737 })
22738 .on_click({
22739 let editor = editor.clone();
22740 move |_event, _window, cx| {
22741 editor.update(cx, |editor, cx| {
22742 editor.stage_or_unstage_diff_hunks(
22743 true,
22744 vec![hunk_range.start..hunk_range.start],
22745 cx,
22746 );
22747 });
22748 }
22749 })
22750 } else {
22751 Button::new(("unstage", row as u64), "Unstage")
22752 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22753 .tooltip({
22754 let focus_handle = editor.focus_handle(cx);
22755 move |window, cx| {
22756 Tooltip::for_action_in(
22757 "Unstage Hunk",
22758 &::git::ToggleStaged,
22759 &focus_handle,
22760 window,
22761 cx,
22762 )
22763 }
22764 })
22765 .on_click({
22766 let editor = editor.clone();
22767 move |_event, _window, cx| {
22768 editor.update(cx, |editor, cx| {
22769 editor.stage_or_unstage_diff_hunks(
22770 false,
22771 vec![hunk_range.start..hunk_range.start],
22772 cx,
22773 );
22774 });
22775 }
22776 })
22777 })
22778 .child(
22779 Button::new(("restore", row as u64), "Restore")
22780 .tooltip({
22781 let focus_handle = editor.focus_handle(cx);
22782 move |window, cx| {
22783 Tooltip::for_action_in(
22784 "Restore Hunk",
22785 &::git::Restore,
22786 &focus_handle,
22787 window,
22788 cx,
22789 )
22790 }
22791 })
22792 .on_click({
22793 let editor = editor.clone();
22794 move |_event, window, cx| {
22795 editor.update(cx, |editor, cx| {
22796 let snapshot = editor.snapshot(window, cx);
22797 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22798 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22799 });
22800 }
22801 })
22802 .disabled(is_created_file),
22803 )
22804 .when(
22805 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22806 |el| {
22807 el.child(
22808 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22809 .shape(IconButtonShape::Square)
22810 .icon_size(IconSize::Small)
22811 // .disabled(!has_multiple_hunks)
22812 .tooltip({
22813 let focus_handle = editor.focus_handle(cx);
22814 move |window, cx| {
22815 Tooltip::for_action_in(
22816 "Next Hunk",
22817 &GoToHunk,
22818 &focus_handle,
22819 window,
22820 cx,
22821 )
22822 }
22823 })
22824 .on_click({
22825 let editor = editor.clone();
22826 move |_event, window, cx| {
22827 editor.update(cx, |editor, cx| {
22828 let snapshot = editor.snapshot(window, cx);
22829 let position =
22830 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22831 editor.go_to_hunk_before_or_after_position(
22832 &snapshot,
22833 position,
22834 Direction::Next,
22835 window,
22836 cx,
22837 );
22838 editor.expand_selected_diff_hunks(cx);
22839 });
22840 }
22841 }),
22842 )
22843 .child(
22844 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22845 .shape(IconButtonShape::Square)
22846 .icon_size(IconSize::Small)
22847 // .disabled(!has_multiple_hunks)
22848 .tooltip({
22849 let focus_handle = editor.focus_handle(cx);
22850 move |window, cx| {
22851 Tooltip::for_action_in(
22852 "Previous Hunk",
22853 &GoToPreviousHunk,
22854 &focus_handle,
22855 window,
22856 cx,
22857 )
22858 }
22859 })
22860 .on_click({
22861 let editor = editor.clone();
22862 move |_event, window, cx| {
22863 editor.update(cx, |editor, cx| {
22864 let snapshot = editor.snapshot(window, cx);
22865 let point =
22866 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22867 editor.go_to_hunk_before_or_after_position(
22868 &snapshot,
22869 point,
22870 Direction::Prev,
22871 window,
22872 cx,
22873 );
22874 editor.expand_selected_diff_hunks(cx);
22875 });
22876 }
22877 }),
22878 )
22879 },
22880 )
22881 .into_any_element()
22882}