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 mouse_down_time: Instant,
916 },
917 /// State when the mouse is dragging the selection in the editor.
918 Dragging {
919 selection: Selection<Anchor>,
920 drop_cursor: Selection<Anchor>,
921 hide_drop_cursor: bool,
922 },
923}
924
925/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
926/// a breakpoint on them.
927#[derive(Clone, Copy, Debug, PartialEq, Eq)]
928struct PhantomBreakpointIndicator {
929 display_row: DisplayRow,
930 /// There's a small debounce between hovering over the line and showing the indicator.
931 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
932 is_active: bool,
933 collides_with_existing_breakpoint: bool,
934}
935
936/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
937///
938/// See the [module level documentation](self) for more information.
939pub struct Editor {
940 focus_handle: FocusHandle,
941 last_focused_descendant: Option<WeakFocusHandle>,
942 /// The text buffer being edited
943 buffer: Entity<MultiBuffer>,
944 /// Map of how text in the buffer should be displayed.
945 /// Handles soft wraps, folds, fake inlay text insertions, etc.
946 pub display_map: Entity<DisplayMap>,
947 pub selections: SelectionsCollection,
948 pub scroll_manager: ScrollManager,
949 /// When inline assist editors are linked, they all render cursors because
950 /// typing enters text into each of them, even the ones that aren't focused.
951 pub(crate) show_cursor_when_unfocused: bool,
952 columnar_selection_tail: Option<Anchor>,
953 columnar_display_point: Option<DisplayPoint>,
954 add_selections_state: Option<AddSelectionsState>,
955 select_next_state: Option<SelectNextState>,
956 select_prev_state: Option<SelectNextState>,
957 selection_history: SelectionHistory,
958 defer_selection_effects: bool,
959 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
960 autoclose_regions: Vec<AutocloseRegion>,
961 snippet_stack: InvalidationStack<SnippetState>,
962 select_syntax_node_history: SelectSyntaxNodeHistory,
963 ime_transaction: Option<TransactionId>,
964 pub diagnostics_max_severity: DiagnosticSeverity,
965 active_diagnostics: ActiveDiagnostic,
966 show_inline_diagnostics: bool,
967 inline_diagnostics_update: Task<()>,
968 inline_diagnostics_enabled: bool,
969 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
970 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
971 hard_wrap: Option<usize>,
972
973 // TODO: make this a access method
974 pub project: Option<Entity<Project>>,
975 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
976 completion_provider: Option<Rc<dyn CompletionProvider>>,
977 collaboration_hub: Option<Box<dyn CollaborationHub>>,
978 blink_manager: Entity<BlinkManager>,
979 show_cursor_names: bool,
980 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
981 pub show_local_selections: bool,
982 mode: EditorMode,
983 show_breadcrumbs: bool,
984 show_gutter: bool,
985 show_scrollbars: ScrollbarAxes,
986 minimap_visibility: MinimapVisibility,
987 offset_content: bool,
988 disable_expand_excerpt_buttons: bool,
989 show_line_numbers: Option<bool>,
990 use_relative_line_numbers: Option<bool>,
991 show_git_diff_gutter: Option<bool>,
992 show_code_actions: Option<bool>,
993 show_runnables: Option<bool>,
994 show_breakpoints: Option<bool>,
995 show_wrap_guides: Option<bool>,
996 show_indent_guides: Option<bool>,
997 placeholder_text: Option<Arc<str>>,
998 highlight_order: usize,
999 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1000 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1001 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1002 scrollbar_marker_state: ScrollbarMarkerState,
1003 active_indent_guides_state: ActiveIndentGuidesState,
1004 nav_history: Option<ItemNavHistory>,
1005 context_menu: RefCell<Option<CodeContextMenu>>,
1006 context_menu_options: Option<ContextMenuOptions>,
1007 mouse_context_menu: Option<MouseContextMenu>,
1008 completion_tasks: Vec<(CompletionId, Task<()>)>,
1009 inline_blame_popover: Option<InlineBlamePopover>,
1010 signature_help_state: SignatureHelpState,
1011 auto_signature_help: Option<bool>,
1012 find_all_references_task_sources: Vec<Anchor>,
1013 next_completion_id: CompletionId,
1014 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1015 code_actions_task: Option<Task<Result<()>>>,
1016 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1017 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1018 document_highlights_task: Option<Task<()>>,
1019 linked_editing_range_task: Option<Task<Option<()>>>,
1020 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1021 pending_rename: Option<RenameState>,
1022 searchable: bool,
1023 cursor_shape: CursorShape,
1024 current_line_highlight: Option<CurrentLineHighlight>,
1025 collapse_matches: bool,
1026 autoindent_mode: Option<AutoindentMode>,
1027 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1028 input_enabled: bool,
1029 use_modal_editing: bool,
1030 read_only: bool,
1031 leader_id: Option<CollaboratorId>,
1032 remote_id: Option<ViewId>,
1033 pub hover_state: HoverState,
1034 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1035 gutter_hovered: bool,
1036 hovered_link_state: Option<HoveredLinkState>,
1037 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1038 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1039 active_inline_completion: Option<InlineCompletionState>,
1040 /// Used to prevent flickering as the user types while the menu is open
1041 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1042 edit_prediction_settings: EditPredictionSettings,
1043 inline_completions_hidden_for_vim_mode: bool,
1044 show_inline_completions_override: Option<bool>,
1045 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1046 edit_prediction_preview: EditPredictionPreview,
1047 edit_prediction_indent_conflict: bool,
1048 edit_prediction_requires_modifier_in_indent_conflict: bool,
1049 inlay_hint_cache: InlayHintCache,
1050 next_inlay_id: usize,
1051 _subscriptions: Vec<Subscription>,
1052 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1053 gutter_dimensions: GutterDimensions,
1054 style: Option<EditorStyle>,
1055 text_style_refinement: Option<TextStyleRefinement>,
1056 next_editor_action_id: EditorActionId,
1057 editor_actions: Rc<
1058 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1059 >,
1060 use_autoclose: bool,
1061 use_auto_surround: bool,
1062 auto_replace_emoji_shortcode: bool,
1063 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1064 show_git_blame_gutter: bool,
1065 show_git_blame_inline: bool,
1066 show_git_blame_inline_delay_task: Option<Task<()>>,
1067 git_blame_inline_enabled: bool,
1068 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1069 serialize_dirty_buffers: bool,
1070 show_selection_menu: Option<bool>,
1071 blame: Option<Entity<GitBlame>>,
1072 blame_subscription: Option<Subscription>,
1073 custom_context_menu: Option<
1074 Box<
1075 dyn 'static
1076 + Fn(
1077 &mut Self,
1078 DisplayPoint,
1079 &mut Window,
1080 &mut Context<Self>,
1081 ) -> Option<Entity<ui::ContextMenu>>,
1082 >,
1083 >,
1084 last_bounds: Option<Bounds<Pixels>>,
1085 last_position_map: Option<Rc<PositionMap>>,
1086 expect_bounds_change: Option<Bounds<Pixels>>,
1087 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1088 tasks_update_task: Option<Task<()>>,
1089 breakpoint_store: Option<Entity<BreakpointStore>>,
1090 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1091 pull_diagnostics_task: Task<()>,
1092 in_project_search: bool,
1093 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1094 breadcrumb_header: Option<String>,
1095 focused_block: Option<FocusedBlock>,
1096 next_scroll_position: NextScrollCursorCenterTopBottom,
1097 addons: HashMap<TypeId, Box<dyn Addon>>,
1098 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1099 load_diff_task: Option<Shared<Task<()>>>,
1100 /// Whether we are temporarily displaying a diff other than git's
1101 temporary_diff_override: bool,
1102 selection_mark_mode: bool,
1103 toggle_fold_multiple_buffers: Task<()>,
1104 _scroll_cursor_center_top_bottom_task: Task<()>,
1105 serialize_selections: Task<()>,
1106 serialize_folds: Task<()>,
1107 mouse_cursor_hidden: bool,
1108 minimap: Option<Entity<Self>>,
1109 hide_mouse_mode: HideMouseMode,
1110 pub change_list: ChangeList,
1111 inline_value_cache: InlineValueCache,
1112 selection_drag_state: SelectionDragState,
1113 drag_and_drop_selection_enabled: bool,
1114}
1115
1116#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1117enum NextScrollCursorCenterTopBottom {
1118 #[default]
1119 Center,
1120 Top,
1121 Bottom,
1122}
1123
1124impl NextScrollCursorCenterTopBottom {
1125 fn next(&self) -> Self {
1126 match self {
1127 Self::Center => Self::Top,
1128 Self::Top => Self::Bottom,
1129 Self::Bottom => Self::Center,
1130 }
1131 }
1132}
1133
1134#[derive(Clone)]
1135pub struct EditorSnapshot {
1136 pub mode: EditorMode,
1137 show_gutter: bool,
1138 show_line_numbers: Option<bool>,
1139 show_git_diff_gutter: Option<bool>,
1140 show_code_actions: Option<bool>,
1141 show_runnables: Option<bool>,
1142 show_breakpoints: Option<bool>,
1143 git_blame_gutter_max_author_length: Option<usize>,
1144 pub display_snapshot: DisplaySnapshot,
1145 pub placeholder_text: Option<Arc<str>>,
1146 is_focused: bool,
1147 scroll_anchor: ScrollAnchor,
1148 ongoing_scroll: OngoingScroll,
1149 current_line_highlight: CurrentLineHighlight,
1150 gutter_hovered: bool,
1151}
1152
1153#[derive(Default, Debug, Clone, Copy)]
1154pub struct GutterDimensions {
1155 pub left_padding: Pixels,
1156 pub right_padding: Pixels,
1157 pub width: Pixels,
1158 pub margin: Pixels,
1159 pub git_blame_entries_width: Option<Pixels>,
1160}
1161
1162impl GutterDimensions {
1163 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1164 Self {
1165 margin: Self::default_gutter_margin(font_id, font_size, cx),
1166 ..Default::default()
1167 }
1168 }
1169
1170 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1171 -cx.text_system().descent(font_id, font_size)
1172 }
1173 /// The full width of the space taken up by the gutter.
1174 pub fn full_width(&self) -> Pixels {
1175 self.margin + self.width
1176 }
1177
1178 /// The width of the space reserved for the fold indicators,
1179 /// use alongside 'justify_end' and `gutter_width` to
1180 /// right align content with the line numbers
1181 pub fn fold_area_width(&self) -> Pixels {
1182 self.margin + self.right_padding
1183 }
1184}
1185
1186#[derive(Debug)]
1187pub struct RemoteSelection {
1188 pub replica_id: ReplicaId,
1189 pub selection: Selection<Anchor>,
1190 pub cursor_shape: CursorShape,
1191 pub collaborator_id: CollaboratorId,
1192 pub line_mode: bool,
1193 pub user_name: Option<SharedString>,
1194 pub color: PlayerColor,
1195}
1196
1197#[derive(Clone, Debug)]
1198struct SelectionHistoryEntry {
1199 selections: Arc<[Selection<Anchor>]>,
1200 select_next_state: Option<SelectNextState>,
1201 select_prev_state: Option<SelectNextState>,
1202 add_selections_state: Option<AddSelectionsState>,
1203}
1204
1205#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1206enum SelectionHistoryMode {
1207 Normal,
1208 Undoing,
1209 Redoing,
1210 Skipping,
1211}
1212
1213#[derive(Clone, PartialEq, Eq, Hash)]
1214struct HoveredCursor {
1215 replica_id: u16,
1216 selection_id: usize,
1217}
1218
1219impl Default for SelectionHistoryMode {
1220 fn default() -> Self {
1221 Self::Normal
1222 }
1223}
1224
1225struct DeferredSelectionEffectsState {
1226 changed: bool,
1227 should_update_completions: bool,
1228 autoscroll: Option<Autoscroll>,
1229 old_cursor_position: Anchor,
1230 history_entry: SelectionHistoryEntry,
1231}
1232
1233#[derive(Default)]
1234struct SelectionHistory {
1235 #[allow(clippy::type_complexity)]
1236 selections_by_transaction:
1237 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1238 mode: SelectionHistoryMode,
1239 undo_stack: VecDeque<SelectionHistoryEntry>,
1240 redo_stack: VecDeque<SelectionHistoryEntry>,
1241}
1242
1243impl SelectionHistory {
1244 #[track_caller]
1245 fn insert_transaction(
1246 &mut self,
1247 transaction_id: TransactionId,
1248 selections: Arc<[Selection<Anchor>]>,
1249 ) {
1250 if selections.is_empty() {
1251 log::error!(
1252 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1253 std::panic::Location::caller()
1254 );
1255 return;
1256 }
1257 self.selections_by_transaction
1258 .insert(transaction_id, (selections, None));
1259 }
1260
1261 #[allow(clippy::type_complexity)]
1262 fn transaction(
1263 &self,
1264 transaction_id: TransactionId,
1265 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1266 self.selections_by_transaction.get(&transaction_id)
1267 }
1268
1269 #[allow(clippy::type_complexity)]
1270 fn transaction_mut(
1271 &mut self,
1272 transaction_id: TransactionId,
1273 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1274 self.selections_by_transaction.get_mut(&transaction_id)
1275 }
1276
1277 fn push(&mut self, entry: SelectionHistoryEntry) {
1278 if !entry.selections.is_empty() {
1279 match self.mode {
1280 SelectionHistoryMode::Normal => {
1281 self.push_undo(entry);
1282 self.redo_stack.clear();
1283 }
1284 SelectionHistoryMode::Undoing => self.push_redo(entry),
1285 SelectionHistoryMode::Redoing => self.push_undo(entry),
1286 SelectionHistoryMode::Skipping => {}
1287 }
1288 }
1289 }
1290
1291 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1292 if self
1293 .undo_stack
1294 .back()
1295 .map_or(true, |e| e.selections != entry.selections)
1296 {
1297 self.undo_stack.push_back(entry);
1298 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1299 self.undo_stack.pop_front();
1300 }
1301 }
1302 }
1303
1304 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1305 if self
1306 .redo_stack
1307 .back()
1308 .map_or(true, |e| e.selections != entry.selections)
1309 {
1310 self.redo_stack.push_back(entry);
1311 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1312 self.redo_stack.pop_front();
1313 }
1314 }
1315 }
1316}
1317
1318#[derive(Clone, Copy)]
1319pub struct RowHighlightOptions {
1320 pub autoscroll: bool,
1321 pub include_gutter: bool,
1322}
1323
1324impl Default for RowHighlightOptions {
1325 fn default() -> Self {
1326 Self {
1327 autoscroll: Default::default(),
1328 include_gutter: true,
1329 }
1330 }
1331}
1332
1333struct RowHighlight {
1334 index: usize,
1335 range: Range<Anchor>,
1336 color: Hsla,
1337 options: RowHighlightOptions,
1338 type_id: TypeId,
1339}
1340
1341#[derive(Clone, Debug)]
1342struct AddSelectionsState {
1343 groups: Vec<AddSelectionsGroup>,
1344}
1345
1346#[derive(Clone, Debug)]
1347struct AddSelectionsGroup {
1348 above: bool,
1349 stack: Vec<usize>,
1350}
1351
1352#[derive(Clone)]
1353struct SelectNextState {
1354 query: AhoCorasick,
1355 wordwise: bool,
1356 done: bool,
1357}
1358
1359impl std::fmt::Debug for SelectNextState {
1360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1361 f.debug_struct(std::any::type_name::<Self>())
1362 .field("wordwise", &self.wordwise)
1363 .field("done", &self.done)
1364 .finish()
1365 }
1366}
1367
1368#[derive(Debug)]
1369struct AutocloseRegion {
1370 selection_id: usize,
1371 range: Range<Anchor>,
1372 pair: BracketPair,
1373}
1374
1375#[derive(Debug)]
1376struct SnippetState {
1377 ranges: Vec<Vec<Range<Anchor>>>,
1378 active_index: usize,
1379 choices: Vec<Option<Vec<String>>>,
1380}
1381
1382#[doc(hidden)]
1383pub struct RenameState {
1384 pub range: Range<Anchor>,
1385 pub old_name: Arc<str>,
1386 pub editor: Entity<Editor>,
1387 block_id: CustomBlockId,
1388}
1389
1390struct InvalidationStack<T>(Vec<T>);
1391
1392struct RegisteredInlineCompletionProvider {
1393 provider: Arc<dyn InlineCompletionProviderHandle>,
1394 _subscription: Subscription,
1395}
1396
1397#[derive(Debug, PartialEq, Eq)]
1398pub struct ActiveDiagnosticGroup {
1399 pub active_range: Range<Anchor>,
1400 pub active_message: String,
1401 pub group_id: usize,
1402 pub blocks: HashSet<CustomBlockId>,
1403}
1404
1405#[derive(Debug, PartialEq, Eq)]
1406
1407pub(crate) enum ActiveDiagnostic {
1408 None,
1409 All,
1410 Group(ActiveDiagnosticGroup),
1411}
1412
1413#[derive(Serialize, Deserialize, Clone, Debug)]
1414pub struct ClipboardSelection {
1415 /// The number of bytes in this selection.
1416 pub len: usize,
1417 /// Whether this was a full-line selection.
1418 pub is_entire_line: bool,
1419 /// The indentation of the first line when this content was originally copied.
1420 pub first_line_indent: u32,
1421}
1422
1423// selections, scroll behavior, was newest selection reversed
1424type SelectSyntaxNodeHistoryState = (
1425 Box<[Selection<usize>]>,
1426 SelectSyntaxNodeScrollBehavior,
1427 bool,
1428);
1429
1430#[derive(Default)]
1431struct SelectSyntaxNodeHistory {
1432 stack: Vec<SelectSyntaxNodeHistoryState>,
1433 // disable temporarily to allow changing selections without losing the stack
1434 pub disable_clearing: bool,
1435}
1436
1437impl SelectSyntaxNodeHistory {
1438 pub fn try_clear(&mut self) {
1439 if !self.disable_clearing {
1440 self.stack.clear();
1441 }
1442 }
1443
1444 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1445 self.stack.push(selection);
1446 }
1447
1448 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1449 self.stack.pop()
1450 }
1451}
1452
1453enum SelectSyntaxNodeScrollBehavior {
1454 CursorTop,
1455 FitSelection,
1456 CursorBottom,
1457}
1458
1459#[derive(Debug)]
1460pub(crate) struct NavigationData {
1461 cursor_anchor: Anchor,
1462 cursor_position: Point,
1463 scroll_anchor: ScrollAnchor,
1464 scroll_top_row: u32,
1465}
1466
1467#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1468pub enum GotoDefinitionKind {
1469 Symbol,
1470 Declaration,
1471 Type,
1472 Implementation,
1473}
1474
1475#[derive(Debug, Clone)]
1476enum InlayHintRefreshReason {
1477 ModifiersChanged(bool),
1478 Toggle(bool),
1479 SettingsChange(InlayHintSettings),
1480 NewLinesShown,
1481 BufferEdited(HashSet<Arc<Language>>),
1482 RefreshRequested,
1483 ExcerptsRemoved(Vec<ExcerptId>),
1484}
1485
1486impl InlayHintRefreshReason {
1487 fn description(&self) -> &'static str {
1488 match self {
1489 Self::ModifiersChanged(_) => "modifiers changed",
1490 Self::Toggle(_) => "toggle",
1491 Self::SettingsChange(_) => "settings change",
1492 Self::NewLinesShown => "new lines shown",
1493 Self::BufferEdited(_) => "buffer edited",
1494 Self::RefreshRequested => "refresh requested",
1495 Self::ExcerptsRemoved(_) => "excerpts removed",
1496 }
1497 }
1498}
1499
1500pub enum FormatTarget {
1501 Buffers,
1502 Ranges(Vec<Range<MultiBufferPoint>>),
1503}
1504
1505pub(crate) struct FocusedBlock {
1506 id: BlockId,
1507 focus_handle: WeakFocusHandle,
1508}
1509
1510#[derive(Clone)]
1511enum JumpData {
1512 MultiBufferRow {
1513 row: MultiBufferRow,
1514 line_offset_from_top: u32,
1515 },
1516 MultiBufferPoint {
1517 excerpt_id: ExcerptId,
1518 position: Point,
1519 anchor: text::Anchor,
1520 line_offset_from_top: u32,
1521 },
1522}
1523
1524pub enum MultibufferSelectionMode {
1525 First,
1526 All,
1527}
1528
1529#[derive(Clone, Copy, Debug, Default)]
1530pub struct RewrapOptions {
1531 pub override_language_settings: bool,
1532 pub preserve_existing_whitespace: bool,
1533}
1534
1535impl Editor {
1536 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1537 let buffer = cx.new(|cx| Buffer::local("", cx));
1538 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1539 Self::new(
1540 EditorMode::SingleLine { auto_width: false },
1541 buffer,
1542 None,
1543 window,
1544 cx,
1545 )
1546 }
1547
1548 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1549 let buffer = cx.new(|cx| Buffer::local("", cx));
1550 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1551 Self::new(EditorMode::full(), buffer, None, window, cx)
1552 }
1553
1554 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1555 let buffer = cx.new(|cx| Buffer::local("", cx));
1556 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1557 Self::new(
1558 EditorMode::SingleLine { auto_width: true },
1559 buffer,
1560 None,
1561 window,
1562 cx,
1563 )
1564 }
1565
1566 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1567 let buffer = cx.new(|cx| Buffer::local("", cx));
1568 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1569 Self::new(
1570 EditorMode::AutoHeight { max_lines },
1571 buffer,
1572 None,
1573 window,
1574 cx,
1575 )
1576 }
1577
1578 pub fn for_buffer(
1579 buffer: Entity<Buffer>,
1580 project: Option<Entity<Project>>,
1581 window: &mut Window,
1582 cx: &mut Context<Self>,
1583 ) -> Self {
1584 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1585 Self::new(EditorMode::full(), buffer, project, window, cx)
1586 }
1587
1588 pub fn for_multibuffer(
1589 buffer: Entity<MultiBuffer>,
1590 project: Option<Entity<Project>>,
1591 window: &mut Window,
1592 cx: &mut Context<Self>,
1593 ) -> Self {
1594 Self::new(EditorMode::full(), buffer, project, window, cx)
1595 }
1596
1597 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1598 let mut clone = Self::new(
1599 self.mode.clone(),
1600 self.buffer.clone(),
1601 self.project.clone(),
1602 window,
1603 cx,
1604 );
1605 self.display_map.update(cx, |display_map, cx| {
1606 let snapshot = display_map.snapshot(cx);
1607 clone.display_map.update(cx, |display_map, cx| {
1608 display_map.set_state(&snapshot, cx);
1609 });
1610 });
1611 clone.folds_did_change(cx);
1612 clone.selections.clone_state(&self.selections);
1613 clone.scroll_manager.clone_state(&self.scroll_manager);
1614 clone.searchable = self.searchable;
1615 clone.read_only = self.read_only;
1616 clone
1617 }
1618
1619 pub fn new(
1620 mode: EditorMode,
1621 buffer: Entity<MultiBuffer>,
1622 project: Option<Entity<Project>>,
1623 window: &mut Window,
1624 cx: &mut Context<Self>,
1625 ) -> Self {
1626 Editor::new_internal(mode, buffer, project, None, window, cx)
1627 }
1628
1629 fn new_internal(
1630 mode: EditorMode,
1631 buffer: Entity<MultiBuffer>,
1632 project: Option<Entity<Project>>,
1633 display_map: Option<Entity<DisplayMap>>,
1634 window: &mut Window,
1635 cx: &mut Context<Self>,
1636 ) -> Self {
1637 debug_assert!(
1638 display_map.is_none() || mode.is_minimap(),
1639 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1640 );
1641
1642 let full_mode = mode.is_full();
1643 let diagnostics_max_severity = if full_mode {
1644 EditorSettings::get_global(cx)
1645 .diagnostics_max_severity
1646 .unwrap_or(DiagnosticSeverity::Hint)
1647 } else {
1648 DiagnosticSeverity::Off
1649 };
1650 let style = window.text_style();
1651 let font_size = style.font_size.to_pixels(window.rem_size());
1652 let editor = cx.entity().downgrade();
1653 let fold_placeholder = FoldPlaceholder {
1654 constrain_width: true,
1655 render: Arc::new(move |fold_id, fold_range, cx| {
1656 let editor = editor.clone();
1657 div()
1658 .id(fold_id)
1659 .bg(cx.theme().colors().ghost_element_background)
1660 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1661 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1662 .rounded_xs()
1663 .size_full()
1664 .cursor_pointer()
1665 .child("⋯")
1666 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1667 .on_click(move |_, _window, cx| {
1668 editor
1669 .update(cx, |editor, cx| {
1670 editor.unfold_ranges(
1671 &[fold_range.start..fold_range.end],
1672 true,
1673 false,
1674 cx,
1675 );
1676 cx.stop_propagation();
1677 })
1678 .ok();
1679 })
1680 .into_any()
1681 }),
1682 merge_adjacent: true,
1683 ..FoldPlaceholder::default()
1684 };
1685 let display_map = display_map.unwrap_or_else(|| {
1686 cx.new(|cx| {
1687 DisplayMap::new(
1688 buffer.clone(),
1689 style.font(),
1690 font_size,
1691 None,
1692 FILE_HEADER_HEIGHT,
1693 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1694 fold_placeholder,
1695 diagnostics_max_severity,
1696 cx,
1697 )
1698 })
1699 });
1700
1701 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1702
1703 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1704
1705 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1706 .then(|| language_settings::SoftWrap::None);
1707
1708 let mut project_subscriptions = Vec::new();
1709 if mode.is_full() {
1710 if let Some(project) = project.as_ref() {
1711 project_subscriptions.push(cx.subscribe_in(
1712 project,
1713 window,
1714 |editor, _, event, window, cx| match event {
1715 project::Event::RefreshCodeLens => {
1716 // we always query lens with actions, without storing them, always refreshing them
1717 }
1718 project::Event::RefreshInlayHints => {
1719 editor
1720 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1721 }
1722 project::Event::LanguageServerAdded(..)
1723 | project::Event::LanguageServerRemoved(..) => {
1724 if editor.tasks_update_task.is_none() {
1725 editor.tasks_update_task =
1726 Some(editor.refresh_runnables(window, cx));
1727 }
1728 editor.pull_diagnostics(None, window, cx);
1729 }
1730 project::Event::SnippetEdit(id, snippet_edits) => {
1731 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1732 let focus_handle = editor.focus_handle(cx);
1733 if focus_handle.is_focused(window) {
1734 let snapshot = buffer.read(cx).snapshot();
1735 for (range, snippet) in snippet_edits {
1736 let editor_range =
1737 language::range_from_lsp(*range).to_offset(&snapshot);
1738 editor
1739 .insert_snippet(
1740 &[editor_range],
1741 snippet.clone(),
1742 window,
1743 cx,
1744 )
1745 .ok();
1746 }
1747 }
1748 }
1749 }
1750 _ => {}
1751 },
1752 ));
1753 if let Some(task_inventory) = project
1754 .read(cx)
1755 .task_store()
1756 .read(cx)
1757 .task_inventory()
1758 .cloned()
1759 {
1760 project_subscriptions.push(cx.observe_in(
1761 &task_inventory,
1762 window,
1763 |editor, _, window, cx| {
1764 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1765 },
1766 ));
1767 };
1768
1769 project_subscriptions.push(cx.subscribe_in(
1770 &project.read(cx).breakpoint_store(),
1771 window,
1772 |editor, _, event, window, cx| match event {
1773 BreakpointStoreEvent::ClearDebugLines => {
1774 editor.clear_row_highlights::<ActiveDebugLine>();
1775 editor.refresh_inline_values(cx);
1776 }
1777 BreakpointStoreEvent::SetDebugLine => {
1778 if editor.go_to_active_debug_line(window, cx) {
1779 cx.stop_propagation();
1780 }
1781
1782 editor.refresh_inline_values(cx);
1783 }
1784 _ => {}
1785 },
1786 ));
1787 }
1788 }
1789
1790 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1791
1792 let inlay_hint_settings =
1793 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1794 let focus_handle = cx.focus_handle();
1795 cx.on_focus(&focus_handle, window, Self::handle_focus)
1796 .detach();
1797 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1798 .detach();
1799 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1800 .detach();
1801 cx.on_blur(&focus_handle, window, Self::handle_blur)
1802 .detach();
1803 cx.observe_pending_input(window, Self::observe_pending_input)
1804 .detach();
1805
1806 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1807 Some(false)
1808 } else {
1809 None
1810 };
1811
1812 let breakpoint_store = match (&mode, project.as_ref()) {
1813 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1814 _ => None,
1815 };
1816
1817 let mut code_action_providers = Vec::new();
1818 let mut load_uncommitted_diff = None;
1819 if let Some(project) = project.clone() {
1820 load_uncommitted_diff = Some(
1821 update_uncommitted_diff_for_buffer(
1822 cx.entity(),
1823 &project,
1824 buffer.read(cx).all_buffers(),
1825 buffer.clone(),
1826 cx,
1827 )
1828 .shared(),
1829 );
1830 code_action_providers.push(Rc::new(project) as Rc<_>);
1831 }
1832
1833 let mut editor = Self {
1834 focus_handle,
1835 show_cursor_when_unfocused: false,
1836 last_focused_descendant: None,
1837 buffer: buffer.clone(),
1838 display_map: display_map.clone(),
1839 selections,
1840 scroll_manager: ScrollManager::new(cx),
1841 columnar_selection_tail: None,
1842 columnar_display_point: None,
1843 add_selections_state: None,
1844 select_next_state: None,
1845 select_prev_state: None,
1846 selection_history: SelectionHistory::default(),
1847 defer_selection_effects: false,
1848 deferred_selection_effects_state: None,
1849 autoclose_regions: Vec::new(),
1850 snippet_stack: InvalidationStack::default(),
1851 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1852 ime_transaction: None,
1853 active_diagnostics: ActiveDiagnostic::None,
1854 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1855 inline_diagnostics_update: Task::ready(()),
1856 inline_diagnostics: Vec::new(),
1857 soft_wrap_mode_override,
1858 diagnostics_max_severity,
1859 hard_wrap: None,
1860 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1861 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1862 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1863 project,
1864 blink_manager: blink_manager.clone(),
1865 show_local_selections: true,
1866 show_scrollbars: ScrollbarAxes {
1867 horizontal: full_mode,
1868 vertical: full_mode,
1869 },
1870 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1871 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1872 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1873 show_gutter: mode.is_full(),
1874 show_line_numbers: None,
1875 use_relative_line_numbers: None,
1876 disable_expand_excerpt_buttons: false,
1877 show_git_diff_gutter: None,
1878 show_code_actions: None,
1879 show_runnables: None,
1880 show_breakpoints: None,
1881 show_wrap_guides: None,
1882 show_indent_guides,
1883 placeholder_text: None,
1884 highlight_order: 0,
1885 highlighted_rows: HashMap::default(),
1886 background_highlights: TreeMap::default(),
1887 gutter_highlights: TreeMap::default(),
1888 scrollbar_marker_state: ScrollbarMarkerState::default(),
1889 active_indent_guides_state: ActiveIndentGuidesState::default(),
1890 nav_history: None,
1891 context_menu: RefCell::new(None),
1892 context_menu_options: None,
1893 mouse_context_menu: None,
1894 completion_tasks: Vec::new(),
1895 inline_blame_popover: None,
1896 signature_help_state: SignatureHelpState::default(),
1897 auto_signature_help: None,
1898 find_all_references_task_sources: Vec::new(),
1899 next_completion_id: 0,
1900 next_inlay_id: 0,
1901 code_action_providers,
1902 available_code_actions: None,
1903 code_actions_task: None,
1904 quick_selection_highlight_task: None,
1905 debounced_selection_highlight_task: None,
1906 document_highlights_task: None,
1907 linked_editing_range_task: None,
1908 pending_rename: None,
1909 searchable: true,
1910 cursor_shape: EditorSettings::get_global(cx)
1911 .cursor_shape
1912 .unwrap_or_default(),
1913 current_line_highlight: None,
1914 autoindent_mode: Some(AutoindentMode::EachLine),
1915 collapse_matches: false,
1916 workspace: None,
1917 input_enabled: true,
1918 use_modal_editing: mode.is_full(),
1919 read_only: mode.is_minimap(),
1920 use_autoclose: true,
1921 use_auto_surround: true,
1922 auto_replace_emoji_shortcode: false,
1923 jsx_tag_auto_close_enabled_in_any_buffer: false,
1924 leader_id: None,
1925 remote_id: None,
1926 hover_state: HoverState::default(),
1927 pending_mouse_down: None,
1928 hovered_link_state: None,
1929 edit_prediction_provider: None,
1930 active_inline_completion: None,
1931 stale_inline_completion_in_menu: None,
1932 edit_prediction_preview: EditPredictionPreview::Inactive {
1933 released_too_fast: false,
1934 },
1935 inline_diagnostics_enabled: mode.is_full(),
1936 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1937 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1938
1939 gutter_hovered: false,
1940 pixel_position_of_newest_cursor: None,
1941 last_bounds: None,
1942 last_position_map: None,
1943 expect_bounds_change: None,
1944 gutter_dimensions: GutterDimensions::default(),
1945 style: None,
1946 show_cursor_names: false,
1947 hovered_cursors: HashMap::default(),
1948 next_editor_action_id: EditorActionId::default(),
1949 editor_actions: Rc::default(),
1950 inline_completions_hidden_for_vim_mode: false,
1951 show_inline_completions_override: None,
1952 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1953 edit_prediction_settings: EditPredictionSettings::Disabled,
1954 edit_prediction_indent_conflict: false,
1955 edit_prediction_requires_modifier_in_indent_conflict: true,
1956 custom_context_menu: None,
1957 show_git_blame_gutter: false,
1958 show_git_blame_inline: false,
1959 show_selection_menu: None,
1960 show_git_blame_inline_delay_task: None,
1961 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1962 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1963 serialize_dirty_buffers: !mode.is_minimap()
1964 && ProjectSettings::get_global(cx)
1965 .session
1966 .restore_unsaved_buffers,
1967 blame: None,
1968 blame_subscription: None,
1969 tasks: BTreeMap::default(),
1970
1971 breakpoint_store,
1972 gutter_breakpoint_indicator: (None, None),
1973 _subscriptions: vec![
1974 cx.observe(&buffer, Self::on_buffer_changed),
1975 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1976 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1977 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1978 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1979 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1980 cx.observe_window_activation(window, |editor, window, cx| {
1981 let active = window.is_window_active();
1982 editor.blink_manager.update(cx, |blink_manager, cx| {
1983 if active {
1984 blink_manager.enable(cx);
1985 } else {
1986 blink_manager.disable(cx);
1987 }
1988 });
1989 if active {
1990 editor.show_mouse_cursor();
1991 }
1992 }),
1993 ],
1994 tasks_update_task: None,
1995 pull_diagnostics_task: Task::ready(()),
1996 linked_edit_ranges: Default::default(),
1997 in_project_search: false,
1998 previous_search_ranges: None,
1999 breadcrumb_header: None,
2000 focused_block: None,
2001 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2002 addons: HashMap::default(),
2003 registered_buffers: HashMap::default(),
2004 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2005 selection_mark_mode: false,
2006 toggle_fold_multiple_buffers: Task::ready(()),
2007 serialize_selections: Task::ready(()),
2008 serialize_folds: Task::ready(()),
2009 text_style_refinement: None,
2010 load_diff_task: load_uncommitted_diff,
2011 temporary_diff_override: false,
2012 mouse_cursor_hidden: false,
2013 minimap: None,
2014 hide_mouse_mode: EditorSettings::get_global(cx)
2015 .hide_mouse
2016 .unwrap_or_default(),
2017 change_list: ChangeList::new(),
2018 mode,
2019 selection_drag_state: SelectionDragState::None,
2020 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2021 };
2022 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2023 editor
2024 ._subscriptions
2025 .push(cx.observe(breakpoints, |_, _, cx| {
2026 cx.notify();
2027 }));
2028 }
2029 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2030 editor._subscriptions.extend(project_subscriptions);
2031
2032 editor._subscriptions.push(cx.subscribe_in(
2033 &cx.entity(),
2034 window,
2035 |editor, _, e: &EditorEvent, window, cx| match e {
2036 EditorEvent::ScrollPositionChanged { local, .. } => {
2037 if *local {
2038 let new_anchor = editor.scroll_manager.anchor();
2039 let snapshot = editor.snapshot(window, cx);
2040 editor.update_restoration_data(cx, move |data| {
2041 data.scroll_position = (
2042 new_anchor.top_row(&snapshot.buffer_snapshot),
2043 new_anchor.offset,
2044 );
2045 });
2046 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2047 editor.inline_blame_popover.take();
2048 }
2049 }
2050 EditorEvent::Edited { .. } => {
2051 if !vim_enabled(cx) {
2052 let (map, selections) = editor.selections.all_adjusted_display(cx);
2053 let pop_state = editor
2054 .change_list
2055 .last()
2056 .map(|previous| {
2057 previous.len() == selections.len()
2058 && previous.iter().enumerate().all(|(ix, p)| {
2059 p.to_display_point(&map).row()
2060 == selections[ix].head().row()
2061 })
2062 })
2063 .unwrap_or(false);
2064 let new_positions = selections
2065 .into_iter()
2066 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2067 .collect();
2068 editor
2069 .change_list
2070 .push_to_change_list(pop_state, new_positions);
2071 }
2072 }
2073 _ => (),
2074 },
2075 ));
2076
2077 if let Some(dap_store) = editor
2078 .project
2079 .as_ref()
2080 .map(|project| project.read(cx).dap_store())
2081 {
2082 let weak_editor = cx.weak_entity();
2083
2084 editor
2085 ._subscriptions
2086 .push(
2087 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2088 let session_entity = cx.entity();
2089 weak_editor
2090 .update(cx, |editor, cx| {
2091 editor._subscriptions.push(
2092 cx.subscribe(&session_entity, Self::on_debug_session_event),
2093 );
2094 })
2095 .ok();
2096 }),
2097 );
2098
2099 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2100 editor
2101 ._subscriptions
2102 .push(cx.subscribe(&session, Self::on_debug_session_event));
2103 }
2104 }
2105
2106 // skip adding the initial selection to selection history
2107 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2108 editor.end_selection(window, cx);
2109 editor.selection_history.mode = SelectionHistoryMode::Normal;
2110
2111 editor.scroll_manager.show_scrollbars(window, cx);
2112 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2113
2114 if full_mode {
2115 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2116 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2117
2118 if editor.git_blame_inline_enabled {
2119 editor.start_git_blame_inline(false, window, cx);
2120 }
2121
2122 editor.go_to_active_debug_line(window, cx);
2123
2124 if let Some(buffer) = buffer.read(cx).as_singleton() {
2125 if let Some(project) = editor.project.as_ref() {
2126 let handle = project.update(cx, |project, cx| {
2127 project.register_buffer_with_language_servers(&buffer, cx)
2128 });
2129 editor
2130 .registered_buffers
2131 .insert(buffer.read(cx).remote_id(), handle);
2132 }
2133 }
2134
2135 editor.minimap =
2136 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2137 editor.pull_diagnostics(None, window, cx);
2138 }
2139
2140 editor.report_editor_event("Editor Opened", None, cx);
2141 editor
2142 }
2143
2144 pub fn deploy_mouse_context_menu(
2145 &mut self,
2146 position: gpui::Point<Pixels>,
2147 context_menu: Entity<ContextMenu>,
2148 window: &mut Window,
2149 cx: &mut Context<Self>,
2150 ) {
2151 self.mouse_context_menu = Some(MouseContextMenu::new(
2152 self,
2153 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2154 context_menu,
2155 window,
2156 cx,
2157 ));
2158 }
2159
2160 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2161 self.mouse_context_menu
2162 .as_ref()
2163 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2164 }
2165
2166 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2167 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2168 }
2169
2170 fn key_context_internal(
2171 &self,
2172 has_active_edit_prediction: bool,
2173 window: &Window,
2174 cx: &App,
2175 ) -> KeyContext {
2176 let mut key_context = KeyContext::new_with_defaults();
2177 key_context.add("Editor");
2178 let mode = match self.mode {
2179 EditorMode::SingleLine { .. } => "single_line",
2180 EditorMode::AutoHeight { .. } => "auto_height",
2181 EditorMode::Minimap { .. } => "minimap",
2182 EditorMode::Full { .. } => "full",
2183 };
2184
2185 if EditorSettings::jupyter_enabled(cx) {
2186 key_context.add("jupyter");
2187 }
2188
2189 key_context.set("mode", mode);
2190 if self.pending_rename.is_some() {
2191 key_context.add("renaming");
2192 }
2193
2194 match self.context_menu.borrow().as_ref() {
2195 Some(CodeContextMenu::Completions(_)) => {
2196 key_context.add("menu");
2197 key_context.add("showing_completions");
2198 }
2199 Some(CodeContextMenu::CodeActions(_)) => {
2200 key_context.add("menu");
2201 key_context.add("showing_code_actions")
2202 }
2203 None => {}
2204 }
2205
2206 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2207 if !self.focus_handle(cx).contains_focused(window, cx)
2208 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2209 {
2210 for addon in self.addons.values() {
2211 addon.extend_key_context(&mut key_context, cx)
2212 }
2213 }
2214
2215 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2216 if let Some(extension) = singleton_buffer
2217 .read(cx)
2218 .file()
2219 .and_then(|file| file.path().extension()?.to_str())
2220 {
2221 key_context.set("extension", extension.to_string());
2222 }
2223 } else {
2224 key_context.add("multibuffer");
2225 }
2226
2227 if has_active_edit_prediction {
2228 if self.edit_prediction_in_conflict() {
2229 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2230 } else {
2231 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2232 key_context.add("copilot_suggestion");
2233 }
2234 }
2235
2236 if self.selection_mark_mode {
2237 key_context.add("selection_mode");
2238 }
2239
2240 key_context
2241 }
2242
2243 fn show_mouse_cursor(&mut self) {
2244 self.mouse_cursor_hidden = false;
2245 }
2246
2247 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2248 self.mouse_cursor_hidden = match origin {
2249 HideMouseCursorOrigin::TypingAction => {
2250 matches!(
2251 self.hide_mouse_mode,
2252 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2253 )
2254 }
2255 HideMouseCursorOrigin::MovementAction => {
2256 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2257 }
2258 };
2259 }
2260
2261 pub fn edit_prediction_in_conflict(&self) -> bool {
2262 if !self.show_edit_predictions_in_menu() {
2263 return false;
2264 }
2265
2266 let showing_completions = self
2267 .context_menu
2268 .borrow()
2269 .as_ref()
2270 .map_or(false, |context| {
2271 matches!(context, CodeContextMenu::Completions(_))
2272 });
2273
2274 showing_completions
2275 || self.edit_prediction_requires_modifier()
2276 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2277 // bindings to insert tab characters.
2278 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2279 }
2280
2281 pub fn accept_edit_prediction_keybind(
2282 &self,
2283 accept_partial: bool,
2284 window: &Window,
2285 cx: &App,
2286 ) -> AcceptEditPredictionBinding {
2287 let key_context = self.key_context_internal(true, window, cx);
2288 let in_conflict = self.edit_prediction_in_conflict();
2289
2290 let bindings = if accept_partial {
2291 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2292 } else {
2293 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2294 };
2295
2296 // TODO: if the binding contains multiple keystrokes, display all of them, not
2297 // just the first one.
2298 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2299 !in_conflict
2300 || binding
2301 .keystrokes()
2302 .first()
2303 .map_or(false, |keystroke| keystroke.modifiers.modified())
2304 }))
2305 }
2306
2307 pub fn new_file(
2308 workspace: &mut Workspace,
2309 _: &workspace::NewFile,
2310 window: &mut Window,
2311 cx: &mut Context<Workspace>,
2312 ) {
2313 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2314 "Failed to create buffer",
2315 window,
2316 cx,
2317 |e, _, _| match e.error_code() {
2318 ErrorCode::RemoteUpgradeRequired => Some(format!(
2319 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2320 e.error_tag("required").unwrap_or("the latest version")
2321 )),
2322 _ => None,
2323 },
2324 );
2325 }
2326
2327 pub fn new_in_workspace(
2328 workspace: &mut Workspace,
2329 window: &mut Window,
2330 cx: &mut Context<Workspace>,
2331 ) -> Task<Result<Entity<Editor>>> {
2332 let project = workspace.project().clone();
2333 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2334
2335 cx.spawn_in(window, async move |workspace, cx| {
2336 let buffer = create.await?;
2337 workspace.update_in(cx, |workspace, window, cx| {
2338 let editor =
2339 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2340 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2341 editor
2342 })
2343 })
2344 }
2345
2346 fn new_file_vertical(
2347 workspace: &mut Workspace,
2348 _: &workspace::NewFileSplitVertical,
2349 window: &mut Window,
2350 cx: &mut Context<Workspace>,
2351 ) {
2352 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2353 }
2354
2355 fn new_file_horizontal(
2356 workspace: &mut Workspace,
2357 _: &workspace::NewFileSplitHorizontal,
2358 window: &mut Window,
2359 cx: &mut Context<Workspace>,
2360 ) {
2361 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2362 }
2363
2364 fn new_file_in_direction(
2365 workspace: &mut Workspace,
2366 direction: SplitDirection,
2367 window: &mut Window,
2368 cx: &mut Context<Workspace>,
2369 ) {
2370 let project = workspace.project().clone();
2371 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2372
2373 cx.spawn_in(window, async move |workspace, cx| {
2374 let buffer = create.await?;
2375 workspace.update_in(cx, move |workspace, window, cx| {
2376 workspace.split_item(
2377 direction,
2378 Box::new(
2379 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2380 ),
2381 window,
2382 cx,
2383 )
2384 })?;
2385 anyhow::Ok(())
2386 })
2387 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2388 match e.error_code() {
2389 ErrorCode::RemoteUpgradeRequired => Some(format!(
2390 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2391 e.error_tag("required").unwrap_or("the latest version")
2392 )),
2393 _ => None,
2394 }
2395 });
2396 }
2397
2398 pub fn leader_id(&self) -> Option<CollaboratorId> {
2399 self.leader_id
2400 }
2401
2402 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2403 &self.buffer
2404 }
2405
2406 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2407 self.workspace.as_ref()?.0.upgrade()
2408 }
2409
2410 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2411 self.buffer().read(cx).title(cx)
2412 }
2413
2414 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2415 let git_blame_gutter_max_author_length = self
2416 .render_git_blame_gutter(cx)
2417 .then(|| {
2418 if let Some(blame) = self.blame.as_ref() {
2419 let max_author_length =
2420 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2421 Some(max_author_length)
2422 } else {
2423 None
2424 }
2425 })
2426 .flatten();
2427
2428 EditorSnapshot {
2429 mode: self.mode.clone(),
2430 show_gutter: self.show_gutter,
2431 show_line_numbers: self.show_line_numbers,
2432 show_git_diff_gutter: self.show_git_diff_gutter,
2433 show_code_actions: self.show_code_actions,
2434 show_runnables: self.show_runnables,
2435 show_breakpoints: self.show_breakpoints,
2436 git_blame_gutter_max_author_length,
2437 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2438 scroll_anchor: self.scroll_manager.anchor(),
2439 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2440 placeholder_text: self.placeholder_text.clone(),
2441 is_focused: self.focus_handle.is_focused(window),
2442 current_line_highlight: self
2443 .current_line_highlight
2444 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2445 gutter_hovered: self.gutter_hovered,
2446 }
2447 }
2448
2449 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2450 self.buffer.read(cx).language_at(point, cx)
2451 }
2452
2453 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2454 self.buffer.read(cx).read(cx).file_at(point).cloned()
2455 }
2456
2457 pub fn active_excerpt(
2458 &self,
2459 cx: &App,
2460 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2461 self.buffer
2462 .read(cx)
2463 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2464 }
2465
2466 pub fn mode(&self) -> &EditorMode {
2467 &self.mode
2468 }
2469
2470 pub fn set_mode(&mut self, mode: EditorMode) {
2471 self.mode = mode;
2472 }
2473
2474 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2475 self.collaboration_hub.as_deref()
2476 }
2477
2478 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2479 self.collaboration_hub = Some(hub);
2480 }
2481
2482 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2483 self.in_project_search = in_project_search;
2484 }
2485
2486 pub fn set_custom_context_menu(
2487 &mut self,
2488 f: impl 'static
2489 + Fn(
2490 &mut Self,
2491 DisplayPoint,
2492 &mut Window,
2493 &mut Context<Self>,
2494 ) -> Option<Entity<ui::ContextMenu>>,
2495 ) {
2496 self.custom_context_menu = Some(Box::new(f))
2497 }
2498
2499 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2500 self.completion_provider = provider;
2501 }
2502
2503 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2504 self.semantics_provider.clone()
2505 }
2506
2507 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2508 self.semantics_provider = provider;
2509 }
2510
2511 pub fn set_edit_prediction_provider<T>(
2512 &mut self,
2513 provider: Option<Entity<T>>,
2514 window: &mut Window,
2515 cx: &mut Context<Self>,
2516 ) where
2517 T: EditPredictionProvider,
2518 {
2519 self.edit_prediction_provider =
2520 provider.map(|provider| RegisteredInlineCompletionProvider {
2521 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2522 if this.focus_handle.is_focused(window) {
2523 this.update_visible_inline_completion(window, cx);
2524 }
2525 }),
2526 provider: Arc::new(provider),
2527 });
2528 self.update_edit_prediction_settings(cx);
2529 self.refresh_inline_completion(false, false, window, cx);
2530 }
2531
2532 pub fn placeholder_text(&self) -> Option<&str> {
2533 self.placeholder_text.as_deref()
2534 }
2535
2536 pub fn set_placeholder_text(
2537 &mut self,
2538 placeholder_text: impl Into<Arc<str>>,
2539 cx: &mut Context<Self>,
2540 ) {
2541 let placeholder_text = Some(placeholder_text.into());
2542 if self.placeholder_text != placeholder_text {
2543 self.placeholder_text = placeholder_text;
2544 cx.notify();
2545 }
2546 }
2547
2548 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2549 self.cursor_shape = cursor_shape;
2550
2551 // Disrupt blink for immediate user feedback that the cursor shape has changed
2552 self.blink_manager.update(cx, BlinkManager::show_cursor);
2553
2554 cx.notify();
2555 }
2556
2557 pub fn set_current_line_highlight(
2558 &mut self,
2559 current_line_highlight: Option<CurrentLineHighlight>,
2560 ) {
2561 self.current_line_highlight = current_line_highlight;
2562 }
2563
2564 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2565 self.collapse_matches = collapse_matches;
2566 }
2567
2568 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2569 let buffers = self.buffer.read(cx).all_buffers();
2570 let Some(project) = self.project.as_ref() else {
2571 return;
2572 };
2573 project.update(cx, |project, cx| {
2574 for buffer in buffers {
2575 self.registered_buffers
2576 .entry(buffer.read(cx).remote_id())
2577 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2578 }
2579 })
2580 }
2581
2582 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2583 if self.collapse_matches {
2584 return range.start..range.start;
2585 }
2586 range.clone()
2587 }
2588
2589 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2590 if self.display_map.read(cx).clip_at_line_ends != clip {
2591 self.display_map
2592 .update(cx, |map, _| map.clip_at_line_ends = clip);
2593 }
2594 }
2595
2596 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2597 self.input_enabled = input_enabled;
2598 }
2599
2600 pub fn set_inline_completions_hidden_for_vim_mode(
2601 &mut self,
2602 hidden: bool,
2603 window: &mut Window,
2604 cx: &mut Context<Self>,
2605 ) {
2606 if hidden != self.inline_completions_hidden_for_vim_mode {
2607 self.inline_completions_hidden_for_vim_mode = hidden;
2608 if hidden {
2609 self.update_visible_inline_completion(window, cx);
2610 } else {
2611 self.refresh_inline_completion(true, false, window, cx);
2612 }
2613 }
2614 }
2615
2616 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2617 self.menu_inline_completions_policy = value;
2618 }
2619
2620 pub fn set_autoindent(&mut self, autoindent: bool) {
2621 if autoindent {
2622 self.autoindent_mode = Some(AutoindentMode::EachLine);
2623 } else {
2624 self.autoindent_mode = None;
2625 }
2626 }
2627
2628 pub fn read_only(&self, cx: &App) -> bool {
2629 self.read_only || self.buffer.read(cx).read_only()
2630 }
2631
2632 pub fn set_read_only(&mut self, read_only: bool) {
2633 self.read_only = read_only;
2634 }
2635
2636 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2637 self.use_autoclose = autoclose;
2638 }
2639
2640 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2641 self.use_auto_surround = auto_surround;
2642 }
2643
2644 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2645 self.auto_replace_emoji_shortcode = auto_replace;
2646 }
2647
2648 pub fn toggle_edit_predictions(
2649 &mut self,
2650 _: &ToggleEditPrediction,
2651 window: &mut Window,
2652 cx: &mut Context<Self>,
2653 ) {
2654 if self.show_inline_completions_override.is_some() {
2655 self.set_show_edit_predictions(None, window, cx);
2656 } else {
2657 let show_edit_predictions = !self.edit_predictions_enabled();
2658 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2659 }
2660 }
2661
2662 pub fn set_show_edit_predictions(
2663 &mut self,
2664 show_edit_predictions: Option<bool>,
2665 window: &mut Window,
2666 cx: &mut Context<Self>,
2667 ) {
2668 self.show_inline_completions_override = show_edit_predictions;
2669 self.update_edit_prediction_settings(cx);
2670
2671 if let Some(false) = show_edit_predictions {
2672 self.discard_inline_completion(false, cx);
2673 } else {
2674 self.refresh_inline_completion(false, true, window, cx);
2675 }
2676 }
2677
2678 fn inline_completions_disabled_in_scope(
2679 &self,
2680 buffer: &Entity<Buffer>,
2681 buffer_position: language::Anchor,
2682 cx: &App,
2683 ) -> bool {
2684 let snapshot = buffer.read(cx).snapshot();
2685 let settings = snapshot.settings_at(buffer_position, cx);
2686
2687 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2688 return false;
2689 };
2690
2691 scope.override_name().map_or(false, |scope_name| {
2692 settings
2693 .edit_predictions_disabled_in
2694 .iter()
2695 .any(|s| s == scope_name)
2696 })
2697 }
2698
2699 pub fn set_use_modal_editing(&mut self, to: bool) {
2700 self.use_modal_editing = to;
2701 }
2702
2703 pub fn use_modal_editing(&self) -> bool {
2704 self.use_modal_editing
2705 }
2706
2707 fn selections_did_change(
2708 &mut self,
2709 local: bool,
2710 old_cursor_position: &Anchor,
2711 should_update_completions: bool,
2712 window: &mut Window,
2713 cx: &mut Context<Self>,
2714 ) {
2715 window.invalidate_character_coordinates();
2716
2717 // Copy selections to primary selection buffer
2718 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2719 if local {
2720 let selections = self.selections.all::<usize>(cx);
2721 let buffer_handle = self.buffer.read(cx).read(cx);
2722
2723 let mut text = String::new();
2724 for (index, selection) in selections.iter().enumerate() {
2725 let text_for_selection = buffer_handle
2726 .text_for_range(selection.start..selection.end)
2727 .collect::<String>();
2728
2729 text.push_str(&text_for_selection);
2730 if index != selections.len() - 1 {
2731 text.push('\n');
2732 }
2733 }
2734
2735 if !text.is_empty() {
2736 cx.write_to_primary(ClipboardItem::new_string(text));
2737 }
2738 }
2739
2740 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2741 self.buffer.update(cx, |buffer, cx| {
2742 buffer.set_active_selections(
2743 &self.selections.disjoint_anchors(),
2744 self.selections.line_mode,
2745 self.cursor_shape,
2746 cx,
2747 )
2748 });
2749 }
2750 let display_map = self
2751 .display_map
2752 .update(cx, |display_map, cx| display_map.snapshot(cx));
2753 let buffer = &display_map.buffer_snapshot;
2754 if self.selections.count() == 1 {
2755 self.add_selections_state = None;
2756 }
2757 self.select_next_state = None;
2758 self.select_prev_state = None;
2759 self.select_syntax_node_history.try_clear();
2760 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2761 self.snippet_stack
2762 .invalidate(&self.selections.disjoint_anchors(), buffer);
2763 self.take_rename(false, window, cx);
2764
2765 let newest_selection = self.selections.newest_anchor();
2766 let new_cursor_position = newest_selection.head();
2767 let selection_start = newest_selection.start;
2768
2769 self.push_to_nav_history(
2770 *old_cursor_position,
2771 Some(new_cursor_position.to_point(buffer)),
2772 false,
2773 cx,
2774 );
2775
2776 if local {
2777 if let Some(buffer_id) = new_cursor_position.buffer_id {
2778 if !self.registered_buffers.contains_key(&buffer_id) {
2779 if let Some(project) = self.project.as_ref() {
2780 project.update(cx, |project, cx| {
2781 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2782 return;
2783 };
2784 self.registered_buffers.insert(
2785 buffer_id,
2786 project.register_buffer_with_language_servers(&buffer, cx),
2787 );
2788 })
2789 }
2790 }
2791 }
2792
2793 let mut context_menu = self.context_menu.borrow_mut();
2794 let completion_menu = match context_menu.as_ref() {
2795 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2796 Some(CodeContextMenu::CodeActions(_)) => {
2797 *context_menu = None;
2798 None
2799 }
2800 None => None,
2801 };
2802 let completion_position = completion_menu.map(|menu| menu.initial_position);
2803 drop(context_menu);
2804
2805 if should_update_completions {
2806 if let Some(completion_position) = completion_position {
2807 let start_offset = selection_start.to_offset(buffer);
2808 let position_matches = start_offset == completion_position.to_offset(buffer);
2809 let continue_showing = if position_matches {
2810 if self.snippet_stack.is_empty() {
2811 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2812 } else {
2813 // Snippet choices can be shown even when the cursor is in whitespace.
2814 // Dismissing the menu when actions like backspace
2815 true
2816 }
2817 } else {
2818 false
2819 };
2820
2821 if continue_showing {
2822 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2823 } else {
2824 self.hide_context_menu(window, cx);
2825 }
2826 }
2827 }
2828
2829 hide_hover(self, cx);
2830
2831 if old_cursor_position.to_display_point(&display_map).row()
2832 != new_cursor_position.to_display_point(&display_map).row()
2833 {
2834 self.available_code_actions.take();
2835 }
2836 self.refresh_code_actions(window, cx);
2837 self.refresh_document_highlights(cx);
2838 self.refresh_selected_text_highlights(false, window, cx);
2839 refresh_matching_bracket_highlights(self, window, cx);
2840 self.update_visible_inline_completion(window, cx);
2841 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2842 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2843 self.inline_blame_popover.take();
2844 if self.git_blame_inline_enabled {
2845 self.start_inline_blame_timer(window, cx);
2846 }
2847 }
2848
2849 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2850 cx.emit(EditorEvent::SelectionsChanged { local });
2851
2852 let selections = &self.selections.disjoint;
2853 if selections.len() == 1 {
2854 cx.emit(SearchEvent::ActiveMatchChanged)
2855 }
2856 if local {
2857 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2858 let inmemory_selections = selections
2859 .iter()
2860 .map(|s| {
2861 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2862 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2863 })
2864 .collect();
2865 self.update_restoration_data(cx, |data| {
2866 data.selections = inmemory_selections;
2867 });
2868
2869 if WorkspaceSettings::get(None, cx).restore_on_startup
2870 != RestoreOnStartupBehavior::None
2871 {
2872 if let Some(workspace_id) =
2873 self.workspace.as_ref().and_then(|workspace| workspace.1)
2874 {
2875 let snapshot = self.buffer().read(cx).snapshot(cx);
2876 let selections = selections.clone();
2877 let background_executor = cx.background_executor().clone();
2878 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2879 self.serialize_selections = cx.background_spawn(async move {
2880 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2881 let db_selections = selections
2882 .iter()
2883 .map(|selection| {
2884 (
2885 selection.start.to_offset(&snapshot),
2886 selection.end.to_offset(&snapshot),
2887 )
2888 })
2889 .collect();
2890
2891 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2892 .await
2893 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2894 .log_err();
2895 });
2896 }
2897 }
2898 }
2899 }
2900
2901 cx.notify();
2902 }
2903
2904 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2905 use text::ToOffset as _;
2906 use text::ToPoint as _;
2907
2908 if self.mode.is_minimap()
2909 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2910 {
2911 return;
2912 }
2913
2914 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2915 return;
2916 };
2917
2918 let snapshot = singleton.read(cx).snapshot();
2919 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2920 let display_snapshot = display_map.snapshot(cx);
2921
2922 display_snapshot
2923 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2924 .map(|fold| {
2925 fold.range.start.text_anchor.to_point(&snapshot)
2926 ..fold.range.end.text_anchor.to_point(&snapshot)
2927 })
2928 .collect()
2929 });
2930 self.update_restoration_data(cx, |data| {
2931 data.folds = inmemory_folds;
2932 });
2933
2934 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2935 return;
2936 };
2937 let background_executor = cx.background_executor().clone();
2938 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2939 let db_folds = self.display_map.update(cx, |display_map, cx| {
2940 display_map
2941 .snapshot(cx)
2942 .folds_in_range(0..snapshot.len())
2943 .map(|fold| {
2944 (
2945 fold.range.start.text_anchor.to_offset(&snapshot),
2946 fold.range.end.text_anchor.to_offset(&snapshot),
2947 )
2948 })
2949 .collect()
2950 });
2951 self.serialize_folds = cx.background_spawn(async move {
2952 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2953 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2954 .await
2955 .with_context(|| {
2956 format!(
2957 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2958 )
2959 })
2960 .log_err();
2961 });
2962 }
2963
2964 pub fn sync_selections(
2965 &mut self,
2966 other: Entity<Editor>,
2967 cx: &mut Context<Self>,
2968 ) -> gpui::Subscription {
2969 let other_selections = other.read(cx).selections.disjoint.to_vec();
2970 self.selections.change_with(cx, |selections| {
2971 selections.select_anchors(other_selections);
2972 });
2973
2974 let other_subscription =
2975 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2976 EditorEvent::SelectionsChanged { local: true } => {
2977 let other_selections = other.read(cx).selections.disjoint.to_vec();
2978 if other_selections.is_empty() {
2979 return;
2980 }
2981 this.selections.change_with(cx, |selections| {
2982 selections.select_anchors(other_selections);
2983 });
2984 }
2985 _ => {}
2986 });
2987
2988 let this_subscription =
2989 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2990 EditorEvent::SelectionsChanged { local: true } => {
2991 let these_selections = this.selections.disjoint.to_vec();
2992 if these_selections.is_empty() {
2993 return;
2994 }
2995 other.update(cx, |other_editor, cx| {
2996 other_editor.selections.change_with(cx, |selections| {
2997 selections.select_anchors(these_selections);
2998 })
2999 });
3000 }
3001 _ => {}
3002 });
3003
3004 Subscription::join(other_subscription, this_subscription)
3005 }
3006
3007 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3008 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3009 /// effects of selection change occur at the end of the transaction.
3010 pub fn change_selections<R>(
3011 &mut self,
3012 autoscroll: Option<Autoscroll>,
3013 window: &mut Window,
3014 cx: &mut Context<Self>,
3015 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3016 ) -> R {
3017 self.change_selections_inner(true, autoscroll, window, cx, change)
3018 }
3019
3020 pub(crate) fn change_selections_without_updating_completions<R>(
3021 &mut self,
3022 autoscroll: Option<Autoscroll>,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3026 ) -> R {
3027 self.change_selections_inner(false, autoscroll, window, cx, change)
3028 }
3029
3030 fn change_selections_inner<R>(
3031 &mut self,
3032 should_update_completions: bool,
3033 autoscroll: Option<Autoscroll>,
3034 window: &mut Window,
3035 cx: &mut Context<Self>,
3036 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3037 ) -> R {
3038 if let Some(state) = &mut self.deferred_selection_effects_state {
3039 state.autoscroll = autoscroll.or(state.autoscroll);
3040 state.should_update_completions = should_update_completions;
3041 let (changed, result) = self.selections.change_with(cx, change);
3042 state.changed |= changed;
3043 return result;
3044 }
3045 let mut state = DeferredSelectionEffectsState {
3046 changed: false,
3047 should_update_completions,
3048 autoscroll,
3049 old_cursor_position: self.selections.newest_anchor().head(),
3050 history_entry: SelectionHistoryEntry {
3051 selections: self.selections.disjoint_anchors(),
3052 select_next_state: self.select_next_state.clone(),
3053 select_prev_state: self.select_prev_state.clone(),
3054 add_selections_state: self.add_selections_state.clone(),
3055 },
3056 };
3057 let (changed, result) = self.selections.change_with(cx, change);
3058 state.changed = state.changed || changed;
3059 if self.defer_selection_effects {
3060 self.deferred_selection_effects_state = Some(state);
3061 } else {
3062 self.apply_selection_effects(state, window, cx);
3063 }
3064 result
3065 }
3066
3067 /// Defers the effects of selection change, so that the effects of multiple calls to
3068 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3069 /// to selection history and the state of popovers based on selection position aren't
3070 /// erroneously updated.
3071 pub fn with_selection_effects_deferred<R>(
3072 &mut self,
3073 window: &mut Window,
3074 cx: &mut Context<Self>,
3075 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3076 ) -> R {
3077 let already_deferred = self.defer_selection_effects;
3078 self.defer_selection_effects = true;
3079 let result = update(self, window, cx);
3080 if !already_deferred {
3081 self.defer_selection_effects = false;
3082 if let Some(state) = self.deferred_selection_effects_state.take() {
3083 self.apply_selection_effects(state, window, cx);
3084 }
3085 }
3086 result
3087 }
3088
3089 fn apply_selection_effects(
3090 &mut self,
3091 state: DeferredSelectionEffectsState,
3092 window: &mut Window,
3093 cx: &mut Context<Self>,
3094 ) {
3095 if state.changed {
3096 self.selection_history.push(state.history_entry);
3097
3098 if let Some(autoscroll) = state.autoscroll {
3099 self.request_autoscroll(autoscroll, cx);
3100 }
3101
3102 let old_cursor_position = &state.old_cursor_position;
3103
3104 self.selections_did_change(
3105 true,
3106 &old_cursor_position,
3107 state.should_update_completions,
3108 window,
3109 cx,
3110 );
3111
3112 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3113 self.show_signature_help(&ShowSignatureHelp, window, cx);
3114 }
3115 }
3116 }
3117
3118 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3119 where
3120 I: IntoIterator<Item = (Range<S>, T)>,
3121 S: ToOffset,
3122 T: Into<Arc<str>>,
3123 {
3124 if self.read_only(cx) {
3125 return;
3126 }
3127
3128 self.buffer
3129 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3130 }
3131
3132 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3133 where
3134 I: IntoIterator<Item = (Range<S>, T)>,
3135 S: ToOffset,
3136 T: Into<Arc<str>>,
3137 {
3138 if self.read_only(cx) {
3139 return;
3140 }
3141
3142 self.buffer.update(cx, |buffer, cx| {
3143 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3144 });
3145 }
3146
3147 pub fn edit_with_block_indent<I, S, T>(
3148 &mut self,
3149 edits: I,
3150 original_indent_columns: Vec<Option<u32>>,
3151 cx: &mut Context<Self>,
3152 ) where
3153 I: IntoIterator<Item = (Range<S>, T)>,
3154 S: ToOffset,
3155 T: Into<Arc<str>>,
3156 {
3157 if self.read_only(cx) {
3158 return;
3159 }
3160
3161 self.buffer.update(cx, |buffer, cx| {
3162 buffer.edit(
3163 edits,
3164 Some(AutoindentMode::Block {
3165 original_indent_columns,
3166 }),
3167 cx,
3168 )
3169 });
3170 }
3171
3172 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3173 self.hide_context_menu(window, cx);
3174
3175 match phase {
3176 SelectPhase::Begin {
3177 position,
3178 add,
3179 click_count,
3180 } => self.begin_selection(position, add, click_count, window, cx),
3181 SelectPhase::BeginColumnar {
3182 position,
3183 goal_column,
3184 reset,
3185 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3186 SelectPhase::Extend {
3187 position,
3188 click_count,
3189 } => self.extend_selection(position, click_count, window, cx),
3190 SelectPhase::Update {
3191 position,
3192 goal_column,
3193 scroll_delta,
3194 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3195 SelectPhase::End => self.end_selection(window, cx),
3196 }
3197 }
3198
3199 fn extend_selection(
3200 &mut self,
3201 position: DisplayPoint,
3202 click_count: usize,
3203 window: &mut Window,
3204 cx: &mut Context<Self>,
3205 ) {
3206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3207 let tail = self.selections.newest::<usize>(cx).tail();
3208 self.begin_selection(position, false, click_count, window, cx);
3209
3210 let position = position.to_offset(&display_map, Bias::Left);
3211 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3212
3213 let mut pending_selection = self
3214 .selections
3215 .pending_anchor()
3216 .expect("extend_selection not called with pending selection");
3217 if position >= tail {
3218 pending_selection.start = tail_anchor;
3219 } else {
3220 pending_selection.end = tail_anchor;
3221 pending_selection.reversed = true;
3222 }
3223
3224 let mut pending_mode = self.selections.pending_mode().unwrap();
3225 match &mut pending_mode {
3226 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3227 _ => {}
3228 }
3229
3230 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3231
3232 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3233 s.set_pending(pending_selection, pending_mode)
3234 });
3235 }
3236
3237 fn begin_selection(
3238 &mut self,
3239 position: DisplayPoint,
3240 add: bool,
3241 click_count: usize,
3242 window: &mut Window,
3243 cx: &mut Context<Self>,
3244 ) {
3245 if !self.focus_handle.is_focused(window) {
3246 self.last_focused_descendant = None;
3247 window.focus(&self.focus_handle);
3248 }
3249
3250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3251 let buffer = &display_map.buffer_snapshot;
3252 let position = display_map.clip_point(position, Bias::Left);
3253
3254 let start;
3255 let end;
3256 let mode;
3257 let mut auto_scroll;
3258 match click_count {
3259 1 => {
3260 start = buffer.anchor_before(position.to_point(&display_map));
3261 end = start;
3262 mode = SelectMode::Character;
3263 auto_scroll = true;
3264 }
3265 2 => {
3266 let range = movement::surrounding_word(&display_map, position);
3267 start = buffer.anchor_before(range.start.to_point(&display_map));
3268 end = buffer.anchor_before(range.end.to_point(&display_map));
3269 mode = SelectMode::Word(start..end);
3270 auto_scroll = true;
3271 }
3272 3 => {
3273 let position = display_map
3274 .clip_point(position, Bias::Left)
3275 .to_point(&display_map);
3276 let line_start = display_map.prev_line_boundary(position).0;
3277 let next_line_start = buffer.clip_point(
3278 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3279 Bias::Left,
3280 );
3281 start = buffer.anchor_before(line_start);
3282 end = buffer.anchor_before(next_line_start);
3283 mode = SelectMode::Line(start..end);
3284 auto_scroll = true;
3285 }
3286 _ => {
3287 start = buffer.anchor_before(0);
3288 end = buffer.anchor_before(buffer.len());
3289 mode = SelectMode::All;
3290 auto_scroll = false;
3291 }
3292 }
3293 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3294
3295 let point_to_delete: Option<usize> = {
3296 let selected_points: Vec<Selection<Point>> =
3297 self.selections.disjoint_in_range(start..end, cx);
3298
3299 if !add || click_count > 1 {
3300 None
3301 } else if !selected_points.is_empty() {
3302 Some(selected_points[0].id)
3303 } else {
3304 let clicked_point_already_selected =
3305 self.selections.disjoint.iter().find(|selection| {
3306 selection.start.to_point(buffer) == start.to_point(buffer)
3307 || selection.end.to_point(buffer) == end.to_point(buffer)
3308 });
3309
3310 clicked_point_already_selected.map(|selection| selection.id)
3311 }
3312 };
3313
3314 let selections_count = self.selections.count();
3315
3316 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3317 if let Some(point_to_delete) = point_to_delete {
3318 s.delete(point_to_delete);
3319
3320 if selections_count == 1 {
3321 s.set_pending_anchor_range(start..end, mode);
3322 }
3323 } else {
3324 if !add {
3325 s.clear_disjoint();
3326 }
3327
3328 s.set_pending_anchor_range(start..end, mode);
3329 }
3330 });
3331 }
3332
3333 fn begin_columnar_selection(
3334 &mut self,
3335 position: DisplayPoint,
3336 goal_column: u32,
3337 reset: bool,
3338 window: &mut Window,
3339 cx: &mut Context<Self>,
3340 ) {
3341 if !self.focus_handle.is_focused(window) {
3342 self.last_focused_descendant = None;
3343 window.focus(&self.focus_handle);
3344 }
3345
3346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3347
3348 if reset {
3349 let pointer_position = display_map
3350 .buffer_snapshot
3351 .anchor_before(position.to_point(&display_map));
3352
3353 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3354 s.clear_disjoint();
3355 s.set_pending_anchor_range(
3356 pointer_position..pointer_position,
3357 SelectMode::Character,
3358 );
3359 });
3360 if position.column() != goal_column {
3361 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3362 } else {
3363 self.columnar_display_point = None;
3364 }
3365 }
3366
3367 let tail = self.selections.newest::<Point>(cx).tail();
3368 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3369
3370 if !reset {
3371 self.columnar_display_point = None;
3372 self.select_columns(
3373 tail.to_display_point(&display_map),
3374 position,
3375 goal_column,
3376 &display_map,
3377 window,
3378 cx,
3379 );
3380 }
3381 }
3382
3383 fn update_selection(
3384 &mut self,
3385 position: DisplayPoint,
3386 goal_column: u32,
3387 scroll_delta: gpui::Point<f32>,
3388 window: &mut Window,
3389 cx: &mut Context<Self>,
3390 ) {
3391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3392
3393 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3394 let tail = self
3395 .columnar_display_point
3396 .unwrap_or_else(|| tail.to_display_point(&display_map));
3397 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3398 } else if let Some(mut pending) = self.selections.pending_anchor() {
3399 let buffer = self.buffer.read(cx).snapshot(cx);
3400 let head;
3401 let tail;
3402 let mode = self.selections.pending_mode().unwrap();
3403 match &mode {
3404 SelectMode::Character => {
3405 head = position.to_point(&display_map);
3406 tail = pending.tail().to_point(&buffer);
3407 }
3408 SelectMode::Word(original_range) => {
3409 let original_display_range = original_range.start.to_display_point(&display_map)
3410 ..original_range.end.to_display_point(&display_map);
3411 let original_buffer_range = original_display_range.start.to_point(&display_map)
3412 ..original_display_range.end.to_point(&display_map);
3413 if movement::is_inside_word(&display_map, position)
3414 || original_display_range.contains(&position)
3415 {
3416 let word_range = movement::surrounding_word(&display_map, position);
3417 if word_range.start < original_display_range.start {
3418 head = word_range.start.to_point(&display_map);
3419 } else {
3420 head = word_range.end.to_point(&display_map);
3421 }
3422 } else {
3423 head = position.to_point(&display_map);
3424 }
3425
3426 if head <= original_buffer_range.start {
3427 tail = original_buffer_range.end;
3428 } else {
3429 tail = original_buffer_range.start;
3430 }
3431 }
3432 SelectMode::Line(original_range) => {
3433 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3434
3435 let position = display_map
3436 .clip_point(position, Bias::Left)
3437 .to_point(&display_map);
3438 let line_start = display_map.prev_line_boundary(position).0;
3439 let next_line_start = buffer.clip_point(
3440 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3441 Bias::Left,
3442 );
3443
3444 if line_start < original_range.start {
3445 head = line_start
3446 } else {
3447 head = next_line_start
3448 }
3449
3450 if head <= original_range.start {
3451 tail = original_range.end;
3452 } else {
3453 tail = original_range.start;
3454 }
3455 }
3456 SelectMode::All => {
3457 return;
3458 }
3459 };
3460
3461 if head < tail {
3462 pending.start = buffer.anchor_before(head);
3463 pending.end = buffer.anchor_before(tail);
3464 pending.reversed = true;
3465 } else {
3466 pending.start = buffer.anchor_before(tail);
3467 pending.end = buffer.anchor_before(head);
3468 pending.reversed = false;
3469 }
3470
3471 self.change_selections(None, window, cx, |s| {
3472 s.set_pending(pending, mode);
3473 });
3474 } else {
3475 log::error!("update_selection dispatched with no pending selection");
3476 return;
3477 }
3478
3479 self.apply_scroll_delta(scroll_delta, window, cx);
3480 cx.notify();
3481 }
3482
3483 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3484 self.columnar_selection_tail.take();
3485 if self.selections.pending_anchor().is_some() {
3486 let selections = self.selections.all::<usize>(cx);
3487 self.change_selections(None, window, cx, |s| {
3488 s.select(selections);
3489 s.clear_pending();
3490 });
3491 }
3492 }
3493
3494 fn select_columns(
3495 &mut self,
3496 tail: DisplayPoint,
3497 head: DisplayPoint,
3498 goal_column: u32,
3499 display_map: &DisplaySnapshot,
3500 window: &mut Window,
3501 cx: &mut Context<Self>,
3502 ) {
3503 let start_row = cmp::min(tail.row(), head.row());
3504 let end_row = cmp::max(tail.row(), head.row());
3505 let start_column = cmp::min(tail.column(), goal_column);
3506 let end_column = cmp::max(tail.column(), goal_column);
3507 let reversed = start_column < tail.column();
3508
3509 let selection_ranges = (start_row.0..=end_row.0)
3510 .map(DisplayRow)
3511 .filter_map(|row| {
3512 if !display_map.is_block_line(row) {
3513 let start = display_map
3514 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3515 .to_point(display_map);
3516 let end = display_map
3517 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3518 .to_point(display_map);
3519 if reversed {
3520 Some(end..start)
3521 } else {
3522 Some(start..end)
3523 }
3524 } else {
3525 None
3526 }
3527 })
3528 .collect::<Vec<_>>();
3529
3530 let mut non_empty_ranges = selection_ranges
3531 .iter()
3532 .filter(|selection_range| selection_range.start != selection_range.end)
3533 .peekable();
3534
3535 let ranges = if non_empty_ranges.peek().is_some() {
3536 non_empty_ranges.cloned().collect()
3537 } else {
3538 selection_ranges
3539 };
3540
3541 self.change_selections(None, window, cx, |s| {
3542 s.select_ranges(ranges);
3543 });
3544 cx.notify();
3545 }
3546
3547 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3548 self.selections
3549 .all_adjusted(cx)
3550 .iter()
3551 .any(|selection| !selection.is_empty())
3552 }
3553
3554 pub fn has_pending_nonempty_selection(&self) -> bool {
3555 let pending_nonempty_selection = match self.selections.pending_anchor() {
3556 Some(Selection { start, end, .. }) => start != end,
3557 None => false,
3558 };
3559
3560 pending_nonempty_selection
3561 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3562 }
3563
3564 pub fn has_pending_selection(&self) -> bool {
3565 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3566 }
3567
3568 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3569 self.selection_mark_mode = false;
3570 self.selection_drag_state = SelectionDragState::None;
3571
3572 if self.clear_expanded_diff_hunks(cx) {
3573 cx.notify();
3574 return;
3575 }
3576 if self.dismiss_menus_and_popups(true, window, cx) {
3577 return;
3578 }
3579
3580 if self.mode.is_full()
3581 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3582 {
3583 return;
3584 }
3585
3586 cx.propagate();
3587 }
3588
3589 pub fn dismiss_menus_and_popups(
3590 &mut self,
3591 is_user_requested: bool,
3592 window: &mut Window,
3593 cx: &mut Context<Self>,
3594 ) -> bool {
3595 if self.take_rename(false, window, cx).is_some() {
3596 return true;
3597 }
3598
3599 if hide_hover(self, cx) {
3600 return true;
3601 }
3602
3603 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3604 return true;
3605 }
3606
3607 if self.hide_context_menu(window, cx).is_some() {
3608 return true;
3609 }
3610
3611 if self.mouse_context_menu.take().is_some() {
3612 return true;
3613 }
3614
3615 if is_user_requested && self.discard_inline_completion(true, cx) {
3616 return true;
3617 }
3618
3619 if self.snippet_stack.pop().is_some() {
3620 return true;
3621 }
3622
3623 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3624 self.dismiss_diagnostics(cx);
3625 return true;
3626 }
3627
3628 false
3629 }
3630
3631 fn linked_editing_ranges_for(
3632 &self,
3633 selection: Range<text::Anchor>,
3634 cx: &App,
3635 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3636 if self.linked_edit_ranges.is_empty() {
3637 return None;
3638 }
3639 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3640 selection.end.buffer_id.and_then(|end_buffer_id| {
3641 if selection.start.buffer_id != Some(end_buffer_id) {
3642 return None;
3643 }
3644 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3645 let snapshot = buffer.read(cx).snapshot();
3646 self.linked_edit_ranges
3647 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3648 .map(|ranges| (ranges, snapshot, buffer))
3649 })?;
3650 use text::ToOffset as TO;
3651 // find offset from the start of current range to current cursor position
3652 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3653
3654 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3655 let start_difference = start_offset - start_byte_offset;
3656 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3657 let end_difference = end_offset - start_byte_offset;
3658 // Current range has associated linked ranges.
3659 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3660 for range in linked_ranges.iter() {
3661 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3662 let end_offset = start_offset + end_difference;
3663 let start_offset = start_offset + start_difference;
3664 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3665 continue;
3666 }
3667 if self.selections.disjoint_anchor_ranges().any(|s| {
3668 if s.start.buffer_id != selection.start.buffer_id
3669 || s.end.buffer_id != selection.end.buffer_id
3670 {
3671 return false;
3672 }
3673 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3674 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3675 }) {
3676 continue;
3677 }
3678 let start = buffer_snapshot.anchor_after(start_offset);
3679 let end = buffer_snapshot.anchor_after(end_offset);
3680 linked_edits
3681 .entry(buffer.clone())
3682 .or_default()
3683 .push(start..end);
3684 }
3685 Some(linked_edits)
3686 }
3687
3688 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3689 let text: Arc<str> = text.into();
3690
3691 if self.read_only(cx) {
3692 return;
3693 }
3694
3695 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3696
3697 let selections = self.selections.all_adjusted(cx);
3698 let mut bracket_inserted = false;
3699 let mut edits = Vec::new();
3700 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3701 let mut new_selections = Vec::with_capacity(selections.len());
3702 let mut new_autoclose_regions = Vec::new();
3703 let snapshot = self.buffer.read(cx).read(cx);
3704 let mut clear_linked_edit_ranges = false;
3705
3706 for (selection, autoclose_region) in
3707 self.selections_with_autoclose_regions(selections, &snapshot)
3708 {
3709 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3710 // Determine if the inserted text matches the opening or closing
3711 // bracket of any of this language's bracket pairs.
3712 let mut bracket_pair = None;
3713 let mut is_bracket_pair_start = false;
3714 let mut is_bracket_pair_end = false;
3715 if !text.is_empty() {
3716 let mut bracket_pair_matching_end = None;
3717 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3718 // and they are removing the character that triggered IME popup.
3719 for (pair, enabled) in scope.brackets() {
3720 if !pair.close && !pair.surround {
3721 continue;
3722 }
3723
3724 if enabled && pair.start.ends_with(text.as_ref()) {
3725 let prefix_len = pair.start.len() - text.len();
3726 let preceding_text_matches_prefix = prefix_len == 0
3727 || (selection.start.column >= (prefix_len as u32)
3728 && snapshot.contains_str_at(
3729 Point::new(
3730 selection.start.row,
3731 selection.start.column - (prefix_len as u32),
3732 ),
3733 &pair.start[..prefix_len],
3734 ));
3735 if preceding_text_matches_prefix {
3736 bracket_pair = Some(pair.clone());
3737 is_bracket_pair_start = true;
3738 break;
3739 }
3740 }
3741 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3742 {
3743 // take first bracket pair matching end, but don't break in case a later bracket
3744 // pair matches start
3745 bracket_pair_matching_end = Some(pair.clone());
3746 }
3747 }
3748 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3749 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3750 is_bracket_pair_end = true;
3751 }
3752 }
3753
3754 if let Some(bracket_pair) = bracket_pair {
3755 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3756 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3757 let auto_surround =
3758 self.use_auto_surround && snapshot_settings.use_auto_surround;
3759 if selection.is_empty() {
3760 if is_bracket_pair_start {
3761 // If the inserted text is a suffix of an opening bracket and the
3762 // selection is preceded by the rest of the opening bracket, then
3763 // insert the closing bracket.
3764 let following_text_allows_autoclose = snapshot
3765 .chars_at(selection.start)
3766 .next()
3767 .map_or(true, |c| scope.should_autoclose_before(c));
3768
3769 let preceding_text_allows_autoclose = selection.start.column == 0
3770 || snapshot.reversed_chars_at(selection.start).next().map_or(
3771 true,
3772 |c| {
3773 bracket_pair.start != bracket_pair.end
3774 || !snapshot
3775 .char_classifier_at(selection.start)
3776 .is_word(c)
3777 },
3778 );
3779
3780 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3781 && bracket_pair.start.len() == 1
3782 {
3783 let target = bracket_pair.start.chars().next().unwrap();
3784 let current_line_count = snapshot
3785 .reversed_chars_at(selection.start)
3786 .take_while(|&c| c != '\n')
3787 .filter(|&c| c == target)
3788 .count();
3789 current_line_count % 2 == 1
3790 } else {
3791 false
3792 };
3793
3794 if autoclose
3795 && bracket_pair.close
3796 && following_text_allows_autoclose
3797 && preceding_text_allows_autoclose
3798 && !is_closing_quote
3799 {
3800 let anchor = snapshot.anchor_before(selection.end);
3801 new_selections.push((selection.map(|_| anchor), text.len()));
3802 new_autoclose_regions.push((
3803 anchor,
3804 text.len(),
3805 selection.id,
3806 bracket_pair.clone(),
3807 ));
3808 edits.push((
3809 selection.range(),
3810 format!("{}{}", text, bracket_pair.end).into(),
3811 ));
3812 bracket_inserted = true;
3813 continue;
3814 }
3815 }
3816
3817 if let Some(region) = autoclose_region {
3818 // If the selection is followed by an auto-inserted closing bracket,
3819 // then don't insert that closing bracket again; just move the selection
3820 // past the closing bracket.
3821 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3822 && text.as_ref() == region.pair.end.as_str();
3823 if should_skip {
3824 let anchor = snapshot.anchor_after(selection.end);
3825 new_selections
3826 .push((selection.map(|_| anchor), region.pair.end.len()));
3827 continue;
3828 }
3829 }
3830
3831 let always_treat_brackets_as_autoclosed = snapshot
3832 .language_settings_at(selection.start, cx)
3833 .always_treat_brackets_as_autoclosed;
3834 if always_treat_brackets_as_autoclosed
3835 && is_bracket_pair_end
3836 && snapshot.contains_str_at(selection.end, text.as_ref())
3837 {
3838 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3839 // and the inserted text is a closing bracket and the selection is followed
3840 // by the closing bracket then move the selection past the closing bracket.
3841 let anchor = snapshot.anchor_after(selection.end);
3842 new_selections.push((selection.map(|_| anchor), text.len()));
3843 continue;
3844 }
3845 }
3846 // If an opening bracket is 1 character long and is typed while
3847 // text is selected, then surround that text with the bracket pair.
3848 else if auto_surround
3849 && bracket_pair.surround
3850 && is_bracket_pair_start
3851 && bracket_pair.start.chars().count() == 1
3852 {
3853 edits.push((selection.start..selection.start, text.clone()));
3854 edits.push((
3855 selection.end..selection.end,
3856 bracket_pair.end.as_str().into(),
3857 ));
3858 bracket_inserted = true;
3859 new_selections.push((
3860 Selection {
3861 id: selection.id,
3862 start: snapshot.anchor_after(selection.start),
3863 end: snapshot.anchor_before(selection.end),
3864 reversed: selection.reversed,
3865 goal: selection.goal,
3866 },
3867 0,
3868 ));
3869 continue;
3870 }
3871 }
3872 }
3873
3874 if self.auto_replace_emoji_shortcode
3875 && selection.is_empty()
3876 && text.as_ref().ends_with(':')
3877 {
3878 if let Some(possible_emoji_short_code) =
3879 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3880 {
3881 if !possible_emoji_short_code.is_empty() {
3882 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3883 let emoji_shortcode_start = Point::new(
3884 selection.start.row,
3885 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3886 );
3887
3888 // Remove shortcode from buffer
3889 edits.push((
3890 emoji_shortcode_start..selection.start,
3891 "".to_string().into(),
3892 ));
3893 new_selections.push((
3894 Selection {
3895 id: selection.id,
3896 start: snapshot.anchor_after(emoji_shortcode_start),
3897 end: snapshot.anchor_before(selection.start),
3898 reversed: selection.reversed,
3899 goal: selection.goal,
3900 },
3901 0,
3902 ));
3903
3904 // Insert emoji
3905 let selection_start_anchor = snapshot.anchor_after(selection.start);
3906 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3907 edits.push((selection.start..selection.end, emoji.to_string().into()));
3908
3909 continue;
3910 }
3911 }
3912 }
3913 }
3914
3915 // If not handling any auto-close operation, then just replace the selected
3916 // text with the given input and move the selection to the end of the
3917 // newly inserted text.
3918 let anchor = snapshot.anchor_after(selection.end);
3919 if !self.linked_edit_ranges.is_empty() {
3920 let start_anchor = snapshot.anchor_before(selection.start);
3921
3922 let is_word_char = text.chars().next().map_or(true, |char| {
3923 let classifier = snapshot
3924 .char_classifier_at(start_anchor.to_offset(&snapshot))
3925 .ignore_punctuation(true);
3926 classifier.is_word(char)
3927 });
3928
3929 if is_word_char {
3930 if let Some(ranges) = self
3931 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3932 {
3933 for (buffer, edits) in ranges {
3934 linked_edits
3935 .entry(buffer.clone())
3936 .or_default()
3937 .extend(edits.into_iter().map(|range| (range, text.clone())));
3938 }
3939 }
3940 } else {
3941 clear_linked_edit_ranges = true;
3942 }
3943 }
3944
3945 new_selections.push((selection.map(|_| anchor), 0));
3946 edits.push((selection.start..selection.end, text.clone()));
3947 }
3948
3949 drop(snapshot);
3950
3951 self.transact(window, cx, |this, window, cx| {
3952 if clear_linked_edit_ranges {
3953 this.linked_edit_ranges.clear();
3954 }
3955 let initial_buffer_versions =
3956 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3957
3958 this.buffer.update(cx, |buffer, cx| {
3959 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3960 });
3961 for (buffer, edits) in linked_edits {
3962 buffer.update(cx, |buffer, cx| {
3963 let snapshot = buffer.snapshot();
3964 let edits = edits
3965 .into_iter()
3966 .map(|(range, text)| {
3967 use text::ToPoint as TP;
3968 let end_point = TP::to_point(&range.end, &snapshot);
3969 let start_point = TP::to_point(&range.start, &snapshot);
3970 (start_point..end_point, text)
3971 })
3972 .sorted_by_key(|(range, _)| range.start);
3973 buffer.edit(edits, None, cx);
3974 })
3975 }
3976 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3977 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3978 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3979 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3980 .zip(new_selection_deltas)
3981 .map(|(selection, delta)| Selection {
3982 id: selection.id,
3983 start: selection.start + delta,
3984 end: selection.end + delta,
3985 reversed: selection.reversed,
3986 goal: SelectionGoal::None,
3987 })
3988 .collect::<Vec<_>>();
3989
3990 let mut i = 0;
3991 for (position, delta, selection_id, pair) in new_autoclose_regions {
3992 let position = position.to_offset(&map.buffer_snapshot) + delta;
3993 let start = map.buffer_snapshot.anchor_before(position);
3994 let end = map.buffer_snapshot.anchor_after(position);
3995 while let Some(existing_state) = this.autoclose_regions.get(i) {
3996 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3997 Ordering::Less => i += 1,
3998 Ordering::Greater => break,
3999 Ordering::Equal => {
4000 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4001 Ordering::Less => i += 1,
4002 Ordering::Equal => break,
4003 Ordering::Greater => break,
4004 }
4005 }
4006 }
4007 }
4008 this.autoclose_regions.insert(
4009 i,
4010 AutocloseRegion {
4011 selection_id,
4012 range: start..end,
4013 pair,
4014 },
4015 );
4016 }
4017
4018 let had_active_inline_completion = this.has_active_inline_completion();
4019 this.change_selections_without_updating_completions(
4020 Some(Autoscroll::fit()),
4021 window,
4022 cx,
4023 |s| s.select(new_selections),
4024 );
4025
4026 if !bracket_inserted {
4027 if let Some(on_type_format_task) =
4028 this.trigger_on_type_formatting(text.to_string(), window, cx)
4029 {
4030 on_type_format_task.detach_and_log_err(cx);
4031 }
4032 }
4033
4034 let editor_settings = EditorSettings::get_global(cx);
4035 if bracket_inserted
4036 && (editor_settings.auto_signature_help
4037 || editor_settings.show_signature_help_after_edits)
4038 {
4039 this.show_signature_help(&ShowSignatureHelp, window, cx);
4040 }
4041
4042 let trigger_in_words =
4043 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4044 if this.hard_wrap.is_some() {
4045 let latest: Range<Point> = this.selections.newest(cx).range();
4046 if latest.is_empty()
4047 && this
4048 .buffer()
4049 .read(cx)
4050 .snapshot(cx)
4051 .line_len(MultiBufferRow(latest.start.row))
4052 == latest.start.column
4053 {
4054 this.rewrap_impl(
4055 RewrapOptions {
4056 override_language_settings: true,
4057 preserve_existing_whitespace: true,
4058 },
4059 cx,
4060 )
4061 }
4062 }
4063 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4064 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4065 this.refresh_inline_completion(true, false, window, cx);
4066 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4067 });
4068 }
4069
4070 fn find_possible_emoji_shortcode_at_position(
4071 snapshot: &MultiBufferSnapshot,
4072 position: Point,
4073 ) -> Option<String> {
4074 let mut chars = Vec::new();
4075 let mut found_colon = false;
4076 for char in snapshot.reversed_chars_at(position).take(100) {
4077 // Found a possible emoji shortcode in the middle of the buffer
4078 if found_colon {
4079 if char.is_whitespace() {
4080 chars.reverse();
4081 return Some(chars.iter().collect());
4082 }
4083 // If the previous character is not a whitespace, we are in the middle of a word
4084 // and we only want to complete the shortcode if the word is made up of other emojis
4085 let mut containing_word = String::new();
4086 for ch in snapshot
4087 .reversed_chars_at(position)
4088 .skip(chars.len() + 1)
4089 .take(100)
4090 {
4091 if ch.is_whitespace() {
4092 break;
4093 }
4094 containing_word.push(ch);
4095 }
4096 let containing_word = containing_word.chars().rev().collect::<String>();
4097 if util::word_consists_of_emojis(containing_word.as_str()) {
4098 chars.reverse();
4099 return Some(chars.iter().collect());
4100 }
4101 }
4102
4103 if char.is_whitespace() || !char.is_ascii() {
4104 return None;
4105 }
4106 if char == ':' {
4107 found_colon = true;
4108 } else {
4109 chars.push(char);
4110 }
4111 }
4112 // Found a possible emoji shortcode at the beginning of the buffer
4113 chars.reverse();
4114 Some(chars.iter().collect())
4115 }
4116
4117 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4118 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4119 self.transact(window, cx, |this, window, cx| {
4120 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4121 let selections = this.selections.all::<usize>(cx);
4122 let multi_buffer = this.buffer.read(cx);
4123 let buffer = multi_buffer.snapshot(cx);
4124 selections
4125 .iter()
4126 .map(|selection| {
4127 let start_point = selection.start.to_point(&buffer);
4128 let mut existing_indent =
4129 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4130 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4131 let start = selection.start;
4132 let end = selection.end;
4133 let selection_is_empty = start == end;
4134 let language_scope = buffer.language_scope_at(start);
4135 let (
4136 comment_delimiter,
4137 doc_delimiter,
4138 insert_extra_newline,
4139 indent_on_newline,
4140 indent_on_extra_newline,
4141 ) = if let Some(language) = &language_scope {
4142 let mut insert_extra_newline =
4143 insert_extra_newline_brackets(&buffer, start..end, language)
4144 || insert_extra_newline_tree_sitter(&buffer, start..end);
4145
4146 // Comment extension on newline is allowed only for cursor selections
4147 let comment_delimiter = maybe!({
4148 if !selection_is_empty {
4149 return None;
4150 }
4151
4152 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4153 return None;
4154 }
4155
4156 let delimiters = language.line_comment_prefixes();
4157 let max_len_of_delimiter =
4158 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4159 let (snapshot, range) =
4160 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4161
4162 let num_of_whitespaces = snapshot
4163 .chars_for_range(range.clone())
4164 .take_while(|c| c.is_whitespace())
4165 .count();
4166 let comment_candidate = snapshot
4167 .chars_for_range(range)
4168 .skip(num_of_whitespaces)
4169 .take(max_len_of_delimiter)
4170 .collect::<String>();
4171 let (delimiter, trimmed_len) = delimiters
4172 .iter()
4173 .filter_map(|delimiter| {
4174 let prefix = delimiter.trim_end();
4175 if comment_candidate.starts_with(prefix) {
4176 Some((delimiter, prefix.len()))
4177 } else {
4178 None
4179 }
4180 })
4181 .max_by_key(|(_, len)| *len)?;
4182
4183 let cursor_is_placed_after_comment_marker =
4184 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4185 if cursor_is_placed_after_comment_marker {
4186 Some(delimiter.clone())
4187 } else {
4188 None
4189 }
4190 });
4191
4192 let mut indent_on_newline = IndentSize::spaces(0);
4193 let mut indent_on_extra_newline = IndentSize::spaces(0);
4194
4195 let doc_delimiter = maybe!({
4196 if !selection_is_empty {
4197 return None;
4198 }
4199
4200 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4201 return None;
4202 }
4203
4204 let DocumentationConfig {
4205 start: start_tag,
4206 end: end_tag,
4207 prefix: delimiter,
4208 tab_size: len,
4209 } = language.documentation()?;
4210
4211 let is_within_block_comment = buffer
4212 .language_scope_at(start_point)
4213 .is_some_and(|scope| scope.override_name() == Some("comment"));
4214 if !is_within_block_comment {
4215 return None;
4216 }
4217
4218 let (snapshot, range) =
4219 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4220
4221 let num_of_whitespaces = snapshot
4222 .chars_for_range(range.clone())
4223 .take_while(|c| c.is_whitespace())
4224 .count();
4225
4226 // 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.
4227 let column = start_point.column;
4228 let cursor_is_after_start_tag = {
4229 let start_tag_len = start_tag.len();
4230 let start_tag_line = snapshot
4231 .chars_for_range(range.clone())
4232 .skip(num_of_whitespaces)
4233 .take(start_tag_len)
4234 .collect::<String>();
4235 if start_tag_line.starts_with(start_tag.as_ref()) {
4236 num_of_whitespaces + start_tag_len <= column as usize
4237 } else {
4238 false
4239 }
4240 };
4241
4242 let cursor_is_after_delimiter = {
4243 let delimiter_trim = delimiter.trim_end();
4244 let delimiter_line = snapshot
4245 .chars_for_range(range.clone())
4246 .skip(num_of_whitespaces)
4247 .take(delimiter_trim.len())
4248 .collect::<String>();
4249 if delimiter_line.starts_with(delimiter_trim) {
4250 num_of_whitespaces + delimiter_trim.len() <= column as usize
4251 } else {
4252 false
4253 }
4254 };
4255
4256 let cursor_is_before_end_tag_if_exists = {
4257 let mut char_position = 0u32;
4258 let mut end_tag_offset = None;
4259
4260 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4261 if let Some(byte_pos) = chunk.find(&**end_tag) {
4262 let chars_before_match =
4263 chunk[..byte_pos].chars().count() as u32;
4264 end_tag_offset =
4265 Some(char_position + chars_before_match);
4266 break 'outer;
4267 }
4268 char_position += chunk.chars().count() as u32;
4269 }
4270
4271 if let Some(end_tag_offset) = end_tag_offset {
4272 let cursor_is_before_end_tag = column <= end_tag_offset;
4273 if cursor_is_after_start_tag {
4274 if cursor_is_before_end_tag {
4275 insert_extra_newline = true;
4276 }
4277 let cursor_is_at_start_of_end_tag =
4278 column == end_tag_offset;
4279 if cursor_is_at_start_of_end_tag {
4280 indent_on_extra_newline.len = (*len).into();
4281 }
4282 }
4283 cursor_is_before_end_tag
4284 } else {
4285 true
4286 }
4287 };
4288
4289 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4290 && cursor_is_before_end_tag_if_exists
4291 {
4292 if cursor_is_after_start_tag {
4293 indent_on_newline.len = (*len).into();
4294 }
4295 Some(delimiter.clone())
4296 } else {
4297 None
4298 }
4299 });
4300
4301 (
4302 comment_delimiter,
4303 doc_delimiter,
4304 insert_extra_newline,
4305 indent_on_newline,
4306 indent_on_extra_newline,
4307 )
4308 } else {
4309 (
4310 None,
4311 None,
4312 false,
4313 IndentSize::default(),
4314 IndentSize::default(),
4315 )
4316 };
4317
4318 let prevent_auto_indent = doc_delimiter.is_some();
4319 let delimiter = comment_delimiter.or(doc_delimiter);
4320
4321 let capacity_for_delimiter =
4322 delimiter.as_deref().map(str::len).unwrap_or_default();
4323 let mut new_text = String::with_capacity(
4324 1 + capacity_for_delimiter
4325 + existing_indent.len as usize
4326 + indent_on_newline.len as usize
4327 + indent_on_extra_newline.len as usize,
4328 );
4329 new_text.push('\n');
4330 new_text.extend(existing_indent.chars());
4331 new_text.extend(indent_on_newline.chars());
4332
4333 if let Some(delimiter) = &delimiter {
4334 new_text.push_str(delimiter);
4335 }
4336
4337 if insert_extra_newline {
4338 new_text.push('\n');
4339 new_text.extend(existing_indent.chars());
4340 new_text.extend(indent_on_extra_newline.chars());
4341 }
4342
4343 let anchor = buffer.anchor_after(end);
4344 let new_selection = selection.map(|_| anchor);
4345 (
4346 ((start..end, new_text), prevent_auto_indent),
4347 (insert_extra_newline, new_selection),
4348 )
4349 })
4350 .unzip()
4351 };
4352
4353 let mut auto_indent_edits = Vec::new();
4354 let mut edits = Vec::new();
4355 for (edit, prevent_auto_indent) in edits_with_flags {
4356 if prevent_auto_indent {
4357 edits.push(edit);
4358 } else {
4359 auto_indent_edits.push(edit);
4360 }
4361 }
4362 if !edits.is_empty() {
4363 this.edit(edits, cx);
4364 }
4365 if !auto_indent_edits.is_empty() {
4366 this.edit_with_autoindent(auto_indent_edits, cx);
4367 }
4368
4369 let buffer = this.buffer.read(cx).snapshot(cx);
4370 let new_selections = selection_info
4371 .into_iter()
4372 .map(|(extra_newline_inserted, new_selection)| {
4373 let mut cursor = new_selection.end.to_point(&buffer);
4374 if extra_newline_inserted {
4375 cursor.row -= 1;
4376 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4377 }
4378 new_selection.map(|_| cursor)
4379 })
4380 .collect();
4381
4382 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4383 s.select(new_selections)
4384 });
4385 this.refresh_inline_completion(true, false, window, cx);
4386 });
4387 }
4388
4389 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4391
4392 let buffer = self.buffer.read(cx);
4393 let snapshot = buffer.snapshot(cx);
4394
4395 let mut edits = Vec::new();
4396 let mut rows = Vec::new();
4397
4398 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4399 let cursor = selection.head();
4400 let row = cursor.row;
4401
4402 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4403
4404 let newline = "\n".to_string();
4405 edits.push((start_of_line..start_of_line, newline));
4406
4407 rows.push(row + rows_inserted as u32);
4408 }
4409
4410 self.transact(window, cx, |editor, window, cx| {
4411 editor.edit(edits, cx);
4412
4413 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4414 let mut index = 0;
4415 s.move_cursors_with(|map, _, _| {
4416 let row = rows[index];
4417 index += 1;
4418
4419 let point = Point::new(row, 0);
4420 let boundary = map.next_line_boundary(point).1;
4421 let clipped = map.clip_point(boundary, Bias::Left);
4422
4423 (clipped, SelectionGoal::None)
4424 });
4425 });
4426
4427 let mut indent_edits = Vec::new();
4428 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4429 for row in rows {
4430 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4431 for (row, indent) in indents {
4432 if indent.len == 0 {
4433 continue;
4434 }
4435
4436 let text = match indent.kind {
4437 IndentKind::Space => " ".repeat(indent.len as usize),
4438 IndentKind::Tab => "\t".repeat(indent.len as usize),
4439 };
4440 let point = Point::new(row.0, 0);
4441 indent_edits.push((point..point, text));
4442 }
4443 }
4444 editor.edit(indent_edits, cx);
4445 });
4446 }
4447
4448 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4449 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4450
4451 let buffer = self.buffer.read(cx);
4452 let snapshot = buffer.snapshot(cx);
4453
4454 let mut edits = Vec::new();
4455 let mut rows = Vec::new();
4456 let mut rows_inserted = 0;
4457
4458 for selection in self.selections.all_adjusted(cx) {
4459 let cursor = selection.head();
4460 let row = cursor.row;
4461
4462 let point = Point::new(row + 1, 0);
4463 let start_of_line = snapshot.clip_point(point, Bias::Left);
4464
4465 let newline = "\n".to_string();
4466 edits.push((start_of_line..start_of_line, newline));
4467
4468 rows_inserted += 1;
4469 rows.push(row + rows_inserted);
4470 }
4471
4472 self.transact(window, cx, |editor, window, cx| {
4473 editor.edit(edits, cx);
4474
4475 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4476 let mut index = 0;
4477 s.move_cursors_with(|map, _, _| {
4478 let row = rows[index];
4479 index += 1;
4480
4481 let point = Point::new(row, 0);
4482 let boundary = map.next_line_boundary(point).1;
4483 let clipped = map.clip_point(boundary, Bias::Left);
4484
4485 (clipped, SelectionGoal::None)
4486 });
4487 });
4488
4489 let mut indent_edits = Vec::new();
4490 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4491 for row in rows {
4492 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4493 for (row, indent) in indents {
4494 if indent.len == 0 {
4495 continue;
4496 }
4497
4498 let text = match indent.kind {
4499 IndentKind::Space => " ".repeat(indent.len as usize),
4500 IndentKind::Tab => "\t".repeat(indent.len as usize),
4501 };
4502 let point = Point::new(row.0, 0);
4503 indent_edits.push((point..point, text));
4504 }
4505 }
4506 editor.edit(indent_edits, cx);
4507 });
4508 }
4509
4510 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4511 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4512 original_indent_columns: Vec::new(),
4513 });
4514 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4515 }
4516
4517 fn insert_with_autoindent_mode(
4518 &mut self,
4519 text: &str,
4520 autoindent_mode: Option<AutoindentMode>,
4521 window: &mut Window,
4522 cx: &mut Context<Self>,
4523 ) {
4524 if self.read_only(cx) {
4525 return;
4526 }
4527
4528 let text: Arc<str> = text.into();
4529 self.transact(window, cx, |this, window, cx| {
4530 let old_selections = this.selections.all_adjusted(cx);
4531 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4532 let anchors = {
4533 let snapshot = buffer.read(cx);
4534 old_selections
4535 .iter()
4536 .map(|s| {
4537 let anchor = snapshot.anchor_after(s.head());
4538 s.map(|_| anchor)
4539 })
4540 .collect::<Vec<_>>()
4541 };
4542 buffer.edit(
4543 old_selections
4544 .iter()
4545 .map(|s| (s.start..s.end, text.clone())),
4546 autoindent_mode,
4547 cx,
4548 );
4549 anchors
4550 });
4551
4552 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4553 s.select_anchors(selection_anchors);
4554 });
4555
4556 cx.notify();
4557 });
4558 }
4559
4560 fn trigger_completion_on_input(
4561 &mut self,
4562 text: &str,
4563 trigger_in_words: bool,
4564 window: &mut Window,
4565 cx: &mut Context<Self>,
4566 ) {
4567 let completions_source = self
4568 .context_menu
4569 .borrow()
4570 .as_ref()
4571 .and_then(|menu| match menu {
4572 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4573 CodeContextMenu::CodeActions(_) => None,
4574 });
4575
4576 match completions_source {
4577 Some(CompletionsMenuSource::Words) => {
4578 self.show_word_completions(&ShowWordCompletions, window, cx)
4579 }
4580 Some(CompletionsMenuSource::Normal)
4581 | Some(CompletionsMenuSource::SnippetChoices)
4582 | None
4583 if self.is_completion_trigger(
4584 text,
4585 trigger_in_words,
4586 completions_source.is_some(),
4587 cx,
4588 ) =>
4589 {
4590 self.show_completions(
4591 &ShowCompletions {
4592 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4593 },
4594 window,
4595 cx,
4596 )
4597 }
4598 _ => {
4599 self.hide_context_menu(window, cx);
4600 }
4601 }
4602 }
4603
4604 fn is_completion_trigger(
4605 &self,
4606 text: &str,
4607 trigger_in_words: bool,
4608 menu_is_open: bool,
4609 cx: &mut Context<Self>,
4610 ) -> bool {
4611 let position = self.selections.newest_anchor().head();
4612 let multibuffer = self.buffer.read(cx);
4613 let Some(buffer) = position
4614 .buffer_id
4615 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4616 else {
4617 return false;
4618 };
4619
4620 if let Some(completion_provider) = &self.completion_provider {
4621 completion_provider.is_completion_trigger(
4622 &buffer,
4623 position.text_anchor,
4624 text,
4625 trigger_in_words,
4626 menu_is_open,
4627 cx,
4628 )
4629 } else {
4630 false
4631 }
4632 }
4633
4634 /// If any empty selections is touching the start of its innermost containing autoclose
4635 /// region, expand it to select the brackets.
4636 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4637 let selections = self.selections.all::<usize>(cx);
4638 let buffer = self.buffer.read(cx).read(cx);
4639 let new_selections = self
4640 .selections_with_autoclose_regions(selections, &buffer)
4641 .map(|(mut selection, region)| {
4642 if !selection.is_empty() {
4643 return selection;
4644 }
4645
4646 if let Some(region) = region {
4647 let mut range = region.range.to_offset(&buffer);
4648 if selection.start == range.start && range.start >= region.pair.start.len() {
4649 range.start -= region.pair.start.len();
4650 if buffer.contains_str_at(range.start, ®ion.pair.start)
4651 && buffer.contains_str_at(range.end, ®ion.pair.end)
4652 {
4653 range.end += region.pair.end.len();
4654 selection.start = range.start;
4655 selection.end = range.end;
4656
4657 return selection;
4658 }
4659 }
4660 }
4661
4662 let always_treat_brackets_as_autoclosed = buffer
4663 .language_settings_at(selection.start, cx)
4664 .always_treat_brackets_as_autoclosed;
4665
4666 if !always_treat_brackets_as_autoclosed {
4667 return selection;
4668 }
4669
4670 if let Some(scope) = buffer.language_scope_at(selection.start) {
4671 for (pair, enabled) in scope.brackets() {
4672 if !enabled || !pair.close {
4673 continue;
4674 }
4675
4676 if buffer.contains_str_at(selection.start, &pair.end) {
4677 let pair_start_len = pair.start.len();
4678 if buffer.contains_str_at(
4679 selection.start.saturating_sub(pair_start_len),
4680 &pair.start,
4681 ) {
4682 selection.start -= pair_start_len;
4683 selection.end += pair.end.len();
4684
4685 return selection;
4686 }
4687 }
4688 }
4689 }
4690
4691 selection
4692 })
4693 .collect();
4694
4695 drop(buffer);
4696 self.change_selections(None, window, cx, |selections| {
4697 selections.select(new_selections)
4698 });
4699 }
4700
4701 /// Iterate the given selections, and for each one, find the smallest surrounding
4702 /// autoclose region. This uses the ordering of the selections and the autoclose
4703 /// regions to avoid repeated comparisons.
4704 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4705 &'a self,
4706 selections: impl IntoIterator<Item = Selection<D>>,
4707 buffer: &'a MultiBufferSnapshot,
4708 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4709 let mut i = 0;
4710 let mut regions = self.autoclose_regions.as_slice();
4711 selections.into_iter().map(move |selection| {
4712 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4713
4714 let mut enclosing = None;
4715 while let Some(pair_state) = regions.get(i) {
4716 if pair_state.range.end.to_offset(buffer) < range.start {
4717 regions = ®ions[i + 1..];
4718 i = 0;
4719 } else if pair_state.range.start.to_offset(buffer) > range.end {
4720 break;
4721 } else {
4722 if pair_state.selection_id == selection.id {
4723 enclosing = Some(pair_state);
4724 }
4725 i += 1;
4726 }
4727 }
4728
4729 (selection, enclosing)
4730 })
4731 }
4732
4733 /// Remove any autoclose regions that no longer contain their selection.
4734 fn invalidate_autoclose_regions(
4735 &mut self,
4736 mut selections: &[Selection<Anchor>],
4737 buffer: &MultiBufferSnapshot,
4738 ) {
4739 self.autoclose_regions.retain(|state| {
4740 let mut i = 0;
4741 while let Some(selection) = selections.get(i) {
4742 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4743 selections = &selections[1..];
4744 continue;
4745 }
4746 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4747 break;
4748 }
4749 if selection.id == state.selection_id {
4750 return true;
4751 } else {
4752 i += 1;
4753 }
4754 }
4755 false
4756 });
4757 }
4758
4759 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4760 let offset = position.to_offset(buffer);
4761 let (word_range, kind) = buffer.surrounding_word(offset, true);
4762 if offset > word_range.start && kind == Some(CharKind::Word) {
4763 Some(
4764 buffer
4765 .text_for_range(word_range.start..offset)
4766 .collect::<String>(),
4767 )
4768 } else {
4769 None
4770 }
4771 }
4772
4773 pub fn toggle_inline_values(
4774 &mut self,
4775 _: &ToggleInlineValues,
4776 _: &mut Window,
4777 cx: &mut Context<Self>,
4778 ) {
4779 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4780
4781 self.refresh_inline_values(cx);
4782 }
4783
4784 pub fn toggle_inlay_hints(
4785 &mut self,
4786 _: &ToggleInlayHints,
4787 _: &mut Window,
4788 cx: &mut Context<Self>,
4789 ) {
4790 self.refresh_inlay_hints(
4791 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4792 cx,
4793 );
4794 }
4795
4796 pub fn inlay_hints_enabled(&self) -> bool {
4797 self.inlay_hint_cache.enabled
4798 }
4799
4800 pub fn inline_values_enabled(&self) -> bool {
4801 self.inline_value_cache.enabled
4802 }
4803
4804 #[cfg(any(test, feature = "test-support"))]
4805 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4806 self.display_map
4807 .read(cx)
4808 .current_inlays()
4809 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4810 .cloned()
4811 .collect()
4812 }
4813
4814 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4815 if self.semantics_provider.is_none() || !self.mode.is_full() {
4816 return;
4817 }
4818
4819 let reason_description = reason.description();
4820 let ignore_debounce = matches!(
4821 reason,
4822 InlayHintRefreshReason::SettingsChange(_)
4823 | InlayHintRefreshReason::Toggle(_)
4824 | InlayHintRefreshReason::ExcerptsRemoved(_)
4825 | InlayHintRefreshReason::ModifiersChanged(_)
4826 );
4827 let (invalidate_cache, required_languages) = match reason {
4828 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4829 match self.inlay_hint_cache.modifiers_override(enabled) {
4830 Some(enabled) => {
4831 if enabled {
4832 (InvalidationStrategy::RefreshRequested, None)
4833 } else {
4834 self.splice_inlays(
4835 &self
4836 .visible_inlay_hints(cx)
4837 .iter()
4838 .map(|inlay| inlay.id)
4839 .collect::<Vec<InlayId>>(),
4840 Vec::new(),
4841 cx,
4842 );
4843 return;
4844 }
4845 }
4846 None => return,
4847 }
4848 }
4849 InlayHintRefreshReason::Toggle(enabled) => {
4850 if self.inlay_hint_cache.toggle(enabled) {
4851 if enabled {
4852 (InvalidationStrategy::RefreshRequested, None)
4853 } else {
4854 self.splice_inlays(
4855 &self
4856 .visible_inlay_hints(cx)
4857 .iter()
4858 .map(|inlay| inlay.id)
4859 .collect::<Vec<InlayId>>(),
4860 Vec::new(),
4861 cx,
4862 );
4863 return;
4864 }
4865 } else {
4866 return;
4867 }
4868 }
4869 InlayHintRefreshReason::SettingsChange(new_settings) => {
4870 match self.inlay_hint_cache.update_settings(
4871 &self.buffer,
4872 new_settings,
4873 self.visible_inlay_hints(cx),
4874 cx,
4875 ) {
4876 ControlFlow::Break(Some(InlaySplice {
4877 to_remove,
4878 to_insert,
4879 })) => {
4880 self.splice_inlays(&to_remove, to_insert, cx);
4881 return;
4882 }
4883 ControlFlow::Break(None) => return,
4884 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4885 }
4886 }
4887 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4888 if let Some(InlaySplice {
4889 to_remove,
4890 to_insert,
4891 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4892 {
4893 self.splice_inlays(&to_remove, to_insert, cx);
4894 }
4895 self.display_map.update(cx, |display_map, _| {
4896 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4897 });
4898 return;
4899 }
4900 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4901 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4902 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4903 }
4904 InlayHintRefreshReason::RefreshRequested => {
4905 (InvalidationStrategy::RefreshRequested, None)
4906 }
4907 };
4908
4909 if let Some(InlaySplice {
4910 to_remove,
4911 to_insert,
4912 }) = self.inlay_hint_cache.spawn_hint_refresh(
4913 reason_description,
4914 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4915 invalidate_cache,
4916 ignore_debounce,
4917 cx,
4918 ) {
4919 self.splice_inlays(&to_remove, to_insert, cx);
4920 }
4921 }
4922
4923 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4924 self.display_map
4925 .read(cx)
4926 .current_inlays()
4927 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4928 .cloned()
4929 .collect()
4930 }
4931
4932 pub fn excerpts_for_inlay_hints_query(
4933 &self,
4934 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4935 cx: &mut Context<Editor>,
4936 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4937 let Some(project) = self.project.as_ref() else {
4938 return HashMap::default();
4939 };
4940 let project = project.read(cx);
4941 let multi_buffer = self.buffer().read(cx);
4942 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4943 let multi_buffer_visible_start = self
4944 .scroll_manager
4945 .anchor()
4946 .anchor
4947 .to_point(&multi_buffer_snapshot);
4948 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4949 multi_buffer_visible_start
4950 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4951 Bias::Left,
4952 );
4953 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4954 multi_buffer_snapshot
4955 .range_to_buffer_ranges(multi_buffer_visible_range)
4956 .into_iter()
4957 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4958 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4959 let buffer_file = project::File::from_dyn(buffer.file())?;
4960 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4961 let worktree_entry = buffer_worktree
4962 .read(cx)
4963 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4964 if worktree_entry.is_ignored {
4965 return None;
4966 }
4967
4968 let language = buffer.language()?;
4969 if let Some(restrict_to_languages) = restrict_to_languages {
4970 if !restrict_to_languages.contains(language) {
4971 return None;
4972 }
4973 }
4974 Some((
4975 excerpt_id,
4976 (
4977 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4978 buffer.version().clone(),
4979 excerpt_visible_range,
4980 ),
4981 ))
4982 })
4983 .collect()
4984 }
4985
4986 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4987 TextLayoutDetails {
4988 text_system: window.text_system().clone(),
4989 editor_style: self.style.clone().unwrap(),
4990 rem_size: window.rem_size(),
4991 scroll_anchor: self.scroll_manager.anchor(),
4992 visible_rows: self.visible_line_count(),
4993 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4994 }
4995 }
4996
4997 pub fn splice_inlays(
4998 &self,
4999 to_remove: &[InlayId],
5000 to_insert: Vec<Inlay>,
5001 cx: &mut Context<Self>,
5002 ) {
5003 self.display_map.update(cx, |display_map, cx| {
5004 display_map.splice_inlays(to_remove, to_insert, cx)
5005 });
5006 cx.notify();
5007 }
5008
5009 fn trigger_on_type_formatting(
5010 &self,
5011 input: String,
5012 window: &mut Window,
5013 cx: &mut Context<Self>,
5014 ) -> Option<Task<Result<()>>> {
5015 if input.len() != 1 {
5016 return None;
5017 }
5018
5019 let project = self.project.as_ref()?;
5020 let position = self.selections.newest_anchor().head();
5021 let (buffer, buffer_position) = self
5022 .buffer
5023 .read(cx)
5024 .text_anchor_for_position(position, cx)?;
5025
5026 let settings = language_settings::language_settings(
5027 buffer
5028 .read(cx)
5029 .language_at(buffer_position)
5030 .map(|l| l.name()),
5031 buffer.read(cx).file(),
5032 cx,
5033 );
5034 if !settings.use_on_type_format {
5035 return None;
5036 }
5037
5038 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5039 // hence we do LSP request & edit on host side only — add formats to host's history.
5040 let push_to_lsp_host_history = true;
5041 // If this is not the host, append its history with new edits.
5042 let push_to_client_history = project.read(cx).is_via_collab();
5043
5044 let on_type_formatting = project.update(cx, |project, cx| {
5045 project.on_type_format(
5046 buffer.clone(),
5047 buffer_position,
5048 input,
5049 push_to_lsp_host_history,
5050 cx,
5051 )
5052 });
5053 Some(cx.spawn_in(window, async move |editor, cx| {
5054 if let Some(transaction) = on_type_formatting.await? {
5055 if push_to_client_history {
5056 buffer
5057 .update(cx, |buffer, _| {
5058 buffer.push_transaction(transaction, Instant::now());
5059 buffer.finalize_last_transaction();
5060 })
5061 .ok();
5062 }
5063 editor.update(cx, |editor, cx| {
5064 editor.refresh_document_highlights(cx);
5065 })?;
5066 }
5067 Ok(())
5068 }))
5069 }
5070
5071 pub fn show_word_completions(
5072 &mut self,
5073 _: &ShowWordCompletions,
5074 window: &mut Window,
5075 cx: &mut Context<Self>,
5076 ) {
5077 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5078 }
5079
5080 pub fn show_completions(
5081 &mut self,
5082 options: &ShowCompletions,
5083 window: &mut Window,
5084 cx: &mut Context<Self>,
5085 ) {
5086 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5087 }
5088
5089 fn open_or_update_completions_menu(
5090 &mut self,
5091 requested_source: Option<CompletionsMenuSource>,
5092 trigger: Option<&str>,
5093 window: &mut Window,
5094 cx: &mut Context<Self>,
5095 ) {
5096 if self.pending_rename.is_some() {
5097 return;
5098 }
5099
5100 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5101
5102 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5103 // inserted and selected. To handle that case, the start of the selection is used so that
5104 // the menu starts with all choices.
5105 let position = self
5106 .selections
5107 .newest_anchor()
5108 .start
5109 .bias_right(&multibuffer_snapshot);
5110 if position.diff_base_anchor.is_some() {
5111 return;
5112 }
5113 let (buffer, buffer_position) =
5114 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5115 output
5116 } else {
5117 return;
5118 };
5119 let buffer_snapshot = buffer.read(cx).snapshot();
5120
5121 let query: Option<Arc<String>> =
5122 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5123
5124 drop(multibuffer_snapshot);
5125
5126 let provider = match requested_source {
5127 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5128 Some(CompletionsMenuSource::Words) => None,
5129 Some(CompletionsMenuSource::SnippetChoices) => {
5130 log::error!("bug: SnippetChoices requested_source is not handled");
5131 None
5132 }
5133 };
5134
5135 let sort_completions = provider
5136 .as_ref()
5137 .map_or(false, |provider| provider.sort_completions());
5138
5139 let filter_completions = provider
5140 .as_ref()
5141 .map_or(true, |provider| provider.filter_completions());
5142
5143 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5144 if filter_completions {
5145 menu.filter(query.clone(), provider.clone(), window, cx);
5146 }
5147 // When `is_incomplete` is false, no need to re-query completions when the current query
5148 // is a suffix of the initial query.
5149 if !menu.is_incomplete {
5150 // If the new query is a suffix of the old query (typing more characters) and
5151 // the previous result was complete, the existing completions can be filtered.
5152 //
5153 // Note that this is always true for snippet completions.
5154 let query_matches = match (&menu.initial_query, &query) {
5155 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5156 (None, _) => true,
5157 _ => false,
5158 };
5159 if query_matches {
5160 let position_matches = if menu.initial_position == position {
5161 true
5162 } else {
5163 let snapshot = self.buffer.read(cx).read(cx);
5164 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5165 };
5166 if position_matches {
5167 return;
5168 }
5169 }
5170 }
5171 };
5172
5173 let trigger_kind = match trigger {
5174 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5175 CompletionTriggerKind::TRIGGER_CHARACTER
5176 }
5177 _ => CompletionTriggerKind::INVOKED,
5178 };
5179 let completion_context = CompletionContext {
5180 trigger_character: trigger.and_then(|trigger| {
5181 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5182 Some(String::from(trigger))
5183 } else {
5184 None
5185 }
5186 }),
5187 trigger_kind,
5188 };
5189
5190 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5191 buffer_snapshot.surrounding_word(buffer_position)
5192 {
5193 let word_to_exclude = buffer_snapshot
5194 .text_for_range(word_range.clone())
5195 .collect::<String>();
5196 (
5197 buffer_snapshot.anchor_before(word_range.start)
5198 ..buffer_snapshot.anchor_after(buffer_position),
5199 Some(word_to_exclude),
5200 )
5201 } else {
5202 (buffer_position..buffer_position, None)
5203 };
5204
5205 let language = buffer_snapshot
5206 .language_at(buffer_position)
5207 .map(|language| language.name());
5208
5209 let completion_settings =
5210 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5211
5212 let show_completion_documentation = buffer_snapshot
5213 .settings_at(buffer_position, cx)
5214 .show_completion_documentation;
5215
5216 // The document can be large, so stay in reasonable bounds when searching for words,
5217 // otherwise completion pop-up might be slow to appear.
5218 const WORD_LOOKUP_ROWS: u32 = 5_000;
5219 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5220 let min_word_search = buffer_snapshot.clip_point(
5221 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5222 Bias::Left,
5223 );
5224 let max_word_search = buffer_snapshot.clip_point(
5225 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5226 Bias::Right,
5227 );
5228 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5229 ..buffer_snapshot.point_to_offset(max_word_search);
5230
5231 let skip_digits = query
5232 .as_ref()
5233 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5234
5235 let (mut words, provider_responses) = match &provider {
5236 Some(provider) => {
5237 let provider_responses = provider.completions(
5238 position.excerpt_id,
5239 &buffer,
5240 buffer_position,
5241 completion_context,
5242 window,
5243 cx,
5244 );
5245
5246 let words = match completion_settings.words {
5247 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5248 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5249 .background_spawn(async move {
5250 buffer_snapshot.words_in_range(WordsQuery {
5251 fuzzy_contents: None,
5252 range: word_search_range,
5253 skip_digits,
5254 })
5255 }),
5256 };
5257
5258 (words, provider_responses)
5259 }
5260 None => (
5261 cx.background_spawn(async move {
5262 buffer_snapshot.words_in_range(WordsQuery {
5263 fuzzy_contents: None,
5264 range: word_search_range,
5265 skip_digits,
5266 })
5267 }),
5268 Task::ready(Ok(Vec::new())),
5269 ),
5270 };
5271
5272 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5273
5274 let id = post_inc(&mut self.next_completion_id);
5275 let task = cx.spawn_in(window, async move |editor, cx| {
5276 let Ok(()) = editor.update(cx, |this, _| {
5277 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5278 }) else {
5279 return;
5280 };
5281
5282 // TODO: Ideally completions from different sources would be selectively re-queried, so
5283 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5284 let mut completions = Vec::new();
5285 let mut is_incomplete = false;
5286 if let Some(provider_responses) = provider_responses.await.log_err() {
5287 if !provider_responses.is_empty() {
5288 for response in provider_responses {
5289 completions.extend(response.completions);
5290 is_incomplete = is_incomplete || response.is_incomplete;
5291 }
5292 if completion_settings.words == WordsCompletionMode::Fallback {
5293 words = Task::ready(BTreeMap::default());
5294 }
5295 }
5296 }
5297
5298 let mut words = words.await;
5299 if let Some(word_to_exclude) = &word_to_exclude {
5300 words.remove(word_to_exclude);
5301 }
5302 for lsp_completion in &completions {
5303 words.remove(&lsp_completion.new_text);
5304 }
5305 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5306 replace_range: word_replace_range.clone(),
5307 new_text: word.clone(),
5308 label: CodeLabel::plain(word, None),
5309 icon_path: None,
5310 documentation: None,
5311 source: CompletionSource::BufferWord {
5312 word_range,
5313 resolved: false,
5314 },
5315 insert_text_mode: Some(InsertTextMode::AS_IS),
5316 confirm: None,
5317 }));
5318
5319 let menu = if completions.is_empty() {
5320 None
5321 } else {
5322 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5323 let languages = editor
5324 .workspace
5325 .as_ref()
5326 .and_then(|(workspace, _)| workspace.upgrade())
5327 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5328 let menu = CompletionsMenu::new(
5329 id,
5330 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5331 sort_completions,
5332 show_completion_documentation,
5333 position,
5334 query.clone(),
5335 is_incomplete,
5336 buffer.clone(),
5337 completions.into(),
5338 snippet_sort_order,
5339 languages,
5340 language,
5341 cx,
5342 );
5343
5344 let query = if filter_completions { query } else { None };
5345 let matches_task = if let Some(query) = query {
5346 menu.do_async_filtering(query, cx)
5347 } else {
5348 Task::ready(menu.unfiltered_matches())
5349 };
5350 (menu, matches_task)
5351 }) else {
5352 return;
5353 };
5354
5355 let matches = matches_task.await;
5356
5357 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5358 // Newer menu already set, so exit.
5359 match editor.context_menu.borrow().as_ref() {
5360 Some(CodeContextMenu::Completions(prev_menu)) => {
5361 if prev_menu.id > id {
5362 return;
5363 }
5364 }
5365 _ => {}
5366 };
5367
5368 // Only valid to take prev_menu because it the new menu is immediately set
5369 // below, or the menu is hidden.
5370 match editor.context_menu.borrow_mut().take() {
5371 Some(CodeContextMenu::Completions(prev_menu)) => {
5372 let position_matches =
5373 if prev_menu.initial_position == menu.initial_position {
5374 true
5375 } else {
5376 let snapshot = editor.buffer.read(cx).read(cx);
5377 prev_menu.initial_position.to_offset(&snapshot)
5378 == menu.initial_position.to_offset(&snapshot)
5379 };
5380 if position_matches {
5381 // Preserve markdown cache before `set_filter_results` because it will
5382 // try to populate the documentation cache.
5383 menu.preserve_markdown_cache(prev_menu);
5384 }
5385 }
5386 _ => {}
5387 };
5388
5389 menu.set_filter_results(matches, provider, window, cx);
5390 }) else {
5391 return;
5392 };
5393
5394 menu.visible().then_some(menu)
5395 };
5396
5397 editor
5398 .update_in(cx, |editor, window, cx| {
5399 if editor.focus_handle.is_focused(window) {
5400 if let Some(menu) = menu {
5401 *editor.context_menu.borrow_mut() =
5402 Some(CodeContextMenu::Completions(menu));
5403
5404 crate::hover_popover::hide_hover(editor, cx);
5405 if editor.show_edit_predictions_in_menu() {
5406 editor.update_visible_inline_completion(window, cx);
5407 } else {
5408 editor.discard_inline_completion(false, cx);
5409 }
5410
5411 cx.notify();
5412 return;
5413 }
5414 }
5415
5416 if editor.completion_tasks.len() <= 1 {
5417 // If there are no more completion tasks and the last menu was empty, we should hide it.
5418 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5419 // If it was already hidden and we don't show inline completions in the menu, we should
5420 // also show the inline-completion when available.
5421 if was_hidden && editor.show_edit_predictions_in_menu() {
5422 editor.update_visible_inline_completion(window, cx);
5423 }
5424 }
5425 })
5426 .ok();
5427 });
5428
5429 self.completion_tasks.push((id, task));
5430 }
5431
5432 #[cfg(feature = "test-support")]
5433 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5434 let menu = self.context_menu.borrow();
5435 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5436 let completions = menu.completions.borrow();
5437 Some(completions.to_vec())
5438 } else {
5439 None
5440 }
5441 }
5442
5443 pub fn with_completions_menu_matching_id<R>(
5444 &self,
5445 id: CompletionId,
5446 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5447 ) -> R {
5448 let mut context_menu = self.context_menu.borrow_mut();
5449 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5450 return f(None);
5451 };
5452 if completions_menu.id != id {
5453 return f(None);
5454 }
5455 f(Some(completions_menu))
5456 }
5457
5458 pub fn confirm_completion(
5459 &mut self,
5460 action: &ConfirmCompletion,
5461 window: &mut Window,
5462 cx: &mut Context<Self>,
5463 ) -> Option<Task<Result<()>>> {
5464 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5465 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5466 }
5467
5468 pub fn confirm_completion_insert(
5469 &mut self,
5470 _: &ConfirmCompletionInsert,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) -> Option<Task<Result<()>>> {
5474 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5475 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5476 }
5477
5478 pub fn confirm_completion_replace(
5479 &mut self,
5480 _: &ConfirmCompletionReplace,
5481 window: &mut Window,
5482 cx: &mut Context<Self>,
5483 ) -> Option<Task<Result<()>>> {
5484 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5485 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5486 }
5487
5488 pub fn compose_completion(
5489 &mut self,
5490 action: &ComposeCompletion,
5491 window: &mut Window,
5492 cx: &mut Context<Self>,
5493 ) -> Option<Task<Result<()>>> {
5494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5495 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5496 }
5497
5498 fn do_completion(
5499 &mut self,
5500 item_ix: Option<usize>,
5501 intent: CompletionIntent,
5502 window: &mut Window,
5503 cx: &mut Context<Editor>,
5504 ) -> Option<Task<Result<()>>> {
5505 use language::ToOffset as _;
5506
5507 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5508 else {
5509 return None;
5510 };
5511
5512 let candidate_id = {
5513 let entries = completions_menu.entries.borrow();
5514 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5515 if self.show_edit_predictions_in_menu() {
5516 self.discard_inline_completion(true, cx);
5517 }
5518 mat.candidate_id
5519 };
5520
5521 let completion = completions_menu
5522 .completions
5523 .borrow()
5524 .get(candidate_id)?
5525 .clone();
5526 cx.stop_propagation();
5527
5528 let buffer_handle = completions_menu.buffer.clone();
5529
5530 let CompletionEdit {
5531 new_text,
5532 snippet,
5533 replace_range,
5534 } = process_completion_for_edit(
5535 &completion,
5536 intent,
5537 &buffer_handle,
5538 &completions_menu.initial_position.text_anchor,
5539 cx,
5540 );
5541
5542 let buffer = buffer_handle.read(cx);
5543 let snapshot = self.buffer.read(cx).snapshot(cx);
5544 let newest_anchor = self.selections.newest_anchor();
5545 let replace_range_multibuffer = {
5546 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5547 let multibuffer_anchor = snapshot
5548 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5549 .unwrap()
5550 ..snapshot
5551 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5552 .unwrap();
5553 multibuffer_anchor.start.to_offset(&snapshot)
5554 ..multibuffer_anchor.end.to_offset(&snapshot)
5555 };
5556 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5557 return None;
5558 }
5559
5560 let old_text = buffer
5561 .text_for_range(replace_range.clone())
5562 .collect::<String>();
5563 let lookbehind = newest_anchor
5564 .start
5565 .text_anchor
5566 .to_offset(buffer)
5567 .saturating_sub(replace_range.start);
5568 let lookahead = replace_range
5569 .end
5570 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5571 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5572 let suffix = &old_text[lookbehind.min(old_text.len())..];
5573
5574 let selections = self.selections.all::<usize>(cx);
5575 let mut ranges = Vec::new();
5576 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5577
5578 for selection in &selections {
5579 let range = if selection.id == newest_anchor.id {
5580 replace_range_multibuffer.clone()
5581 } else {
5582 let mut range = selection.range();
5583
5584 // if prefix is present, don't duplicate it
5585 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5586 range.start = range.start.saturating_sub(lookbehind);
5587
5588 // if suffix is also present, mimic the newest cursor and replace it
5589 if selection.id != newest_anchor.id
5590 && snapshot.contains_str_at(range.end, suffix)
5591 {
5592 range.end += lookahead;
5593 }
5594 }
5595 range
5596 };
5597
5598 ranges.push(range.clone());
5599
5600 if !self.linked_edit_ranges.is_empty() {
5601 let start_anchor = snapshot.anchor_before(range.start);
5602 let end_anchor = snapshot.anchor_after(range.end);
5603 if let Some(ranges) = self
5604 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5605 {
5606 for (buffer, edits) in ranges {
5607 linked_edits
5608 .entry(buffer.clone())
5609 .or_default()
5610 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5611 }
5612 }
5613 }
5614 }
5615
5616 let common_prefix_len = old_text
5617 .chars()
5618 .zip(new_text.chars())
5619 .take_while(|(a, b)| a == b)
5620 .map(|(a, _)| a.len_utf8())
5621 .sum::<usize>();
5622
5623 cx.emit(EditorEvent::InputHandled {
5624 utf16_range_to_replace: None,
5625 text: new_text[common_prefix_len..].into(),
5626 });
5627
5628 self.transact(window, cx, |this, window, cx| {
5629 if let Some(mut snippet) = snippet {
5630 snippet.text = new_text.to_string();
5631 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5632 } else {
5633 this.buffer.update(cx, |buffer, cx| {
5634 let auto_indent = match completion.insert_text_mode {
5635 Some(InsertTextMode::AS_IS) => None,
5636 _ => this.autoindent_mode.clone(),
5637 };
5638 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5639 buffer.edit(edits, auto_indent, cx);
5640 });
5641 }
5642 for (buffer, edits) in linked_edits {
5643 buffer.update(cx, |buffer, cx| {
5644 let snapshot = buffer.snapshot();
5645 let edits = edits
5646 .into_iter()
5647 .map(|(range, text)| {
5648 use text::ToPoint as TP;
5649 let end_point = TP::to_point(&range.end, &snapshot);
5650 let start_point = TP::to_point(&range.start, &snapshot);
5651 (start_point..end_point, text)
5652 })
5653 .sorted_by_key(|(range, _)| range.start);
5654 buffer.edit(edits, None, cx);
5655 })
5656 }
5657
5658 this.refresh_inline_completion(true, false, window, cx);
5659 });
5660
5661 let show_new_completions_on_confirm = completion
5662 .confirm
5663 .as_ref()
5664 .map_or(false, |confirm| confirm(intent, window, cx));
5665 if show_new_completions_on_confirm {
5666 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5667 }
5668
5669 let provider = self.completion_provider.as_ref()?;
5670 drop(completion);
5671 let apply_edits = provider.apply_additional_edits_for_completion(
5672 buffer_handle,
5673 completions_menu.completions.clone(),
5674 candidate_id,
5675 true,
5676 cx,
5677 );
5678
5679 let editor_settings = EditorSettings::get_global(cx);
5680 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5681 // After the code completion is finished, users often want to know what signatures are needed.
5682 // so we should automatically call signature_help
5683 self.show_signature_help(&ShowSignatureHelp, window, cx);
5684 }
5685
5686 Some(cx.foreground_executor().spawn(async move {
5687 apply_edits.await?;
5688 Ok(())
5689 }))
5690 }
5691
5692 pub fn toggle_code_actions(
5693 &mut self,
5694 action: &ToggleCodeActions,
5695 window: &mut Window,
5696 cx: &mut Context<Self>,
5697 ) {
5698 let quick_launch = action.quick_launch;
5699 let mut context_menu = self.context_menu.borrow_mut();
5700 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5701 if code_actions.deployed_from == action.deployed_from {
5702 // Toggle if we're selecting the same one
5703 *context_menu = None;
5704 cx.notify();
5705 return;
5706 } else {
5707 // Otherwise, clear it and start a new one
5708 *context_menu = None;
5709 cx.notify();
5710 }
5711 }
5712 drop(context_menu);
5713 let snapshot = self.snapshot(window, cx);
5714 let deployed_from = action.deployed_from.clone();
5715 let action = action.clone();
5716 self.completion_tasks.clear();
5717 self.discard_inline_completion(false, cx);
5718
5719 let multibuffer_point = match &action.deployed_from {
5720 Some(CodeActionSource::Indicator(row)) => {
5721 DisplayPoint::new(*row, 0).to_point(&snapshot)
5722 }
5723 _ => self.selections.newest::<Point>(cx).head(),
5724 };
5725 let Some((buffer, buffer_row)) = snapshot
5726 .buffer_snapshot
5727 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5728 .and_then(|(buffer_snapshot, range)| {
5729 self.buffer()
5730 .read(cx)
5731 .buffer(buffer_snapshot.remote_id())
5732 .map(|buffer| (buffer, range.start.row))
5733 })
5734 else {
5735 return;
5736 };
5737 let buffer_id = buffer.read(cx).remote_id();
5738 let tasks = self
5739 .tasks
5740 .get(&(buffer_id, buffer_row))
5741 .map(|t| Arc::new(t.to_owned()));
5742
5743 if !self.focus_handle.is_focused(window) {
5744 return;
5745 }
5746 let project = self.project.clone();
5747
5748 let code_actions_task = match deployed_from {
5749 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5750 _ => self.code_actions(buffer_row, window, cx),
5751 };
5752
5753 let runnable_task = match deployed_from {
5754 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5755 _ => {
5756 let mut task_context_task = Task::ready(None);
5757 if let Some(tasks) = &tasks {
5758 if let Some(project) = project {
5759 task_context_task =
5760 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5761 }
5762 }
5763
5764 cx.spawn_in(window, {
5765 let buffer = buffer.clone();
5766 async move |editor, cx| {
5767 let task_context = task_context_task.await;
5768
5769 let resolved_tasks =
5770 tasks
5771 .zip(task_context.clone())
5772 .map(|(tasks, task_context)| ResolvedTasks {
5773 templates: tasks.resolve(&task_context).collect(),
5774 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5775 multibuffer_point.row,
5776 tasks.column,
5777 )),
5778 });
5779 let debug_scenarios = editor.update(cx, |editor, cx| {
5780 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5781 })?;
5782 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5783 }
5784 })
5785 }
5786 };
5787
5788 cx.spawn_in(window, async move |editor, cx| {
5789 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5790 let code_actions = code_actions_task.await;
5791 let spawn_straight_away = quick_launch
5792 && resolved_tasks
5793 .as_ref()
5794 .map_or(false, |tasks| tasks.templates.len() == 1)
5795 && code_actions
5796 .as_ref()
5797 .map_or(true, |actions| actions.is_empty())
5798 && debug_scenarios.is_empty();
5799
5800 editor.update_in(cx, |editor, window, cx| {
5801 crate::hover_popover::hide_hover(editor, cx);
5802 *editor.context_menu.borrow_mut() =
5803 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5804 buffer,
5805 actions: CodeActionContents::new(
5806 resolved_tasks,
5807 code_actions,
5808 debug_scenarios,
5809 task_context.unwrap_or_default(),
5810 ),
5811 selected_item: Default::default(),
5812 scroll_handle: UniformListScrollHandle::default(),
5813 deployed_from,
5814 }));
5815 if spawn_straight_away {
5816 if let Some(task) = editor.confirm_code_action(
5817 &ConfirmCodeAction { item_ix: Some(0) },
5818 window,
5819 cx,
5820 ) {
5821 cx.notify();
5822 return task;
5823 }
5824 }
5825
5826 Task::ready(Ok(()))
5827 })
5828 })
5829 .detach_and_log_err(cx);
5830 }
5831
5832 fn debug_scenarios(
5833 &mut self,
5834 resolved_tasks: &Option<ResolvedTasks>,
5835 buffer: &Entity<Buffer>,
5836 cx: &mut App,
5837 ) -> Vec<task::DebugScenario> {
5838 if cx.has_flag::<DebuggerFeatureFlag>() {
5839 maybe!({
5840 let project = self.project.as_ref()?;
5841 let dap_store = project.read(cx).dap_store();
5842 let mut scenarios = vec![];
5843 let resolved_tasks = resolved_tasks.as_ref()?;
5844 let buffer = buffer.read(cx);
5845 let language = buffer.language()?;
5846 let file = buffer.file();
5847 let debug_adapter = language_settings(language.name().into(), file, cx)
5848 .debuggers
5849 .first()
5850 .map(SharedString::from)
5851 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5852
5853 dap_store.update(cx, |dap_store, cx| {
5854 for (_, task) in &resolved_tasks.templates {
5855 if let Some(scenario) = dap_store.debug_scenario_for_build_task(
5856 task.original_task().clone(),
5857 debug_adapter.clone().into(),
5858 task.display_label().to_owned().into(),
5859 cx,
5860 ) {
5861 scenarios.push(scenario);
5862 }
5863 }
5864 });
5865 Some(scenarios)
5866 })
5867 .unwrap_or_default()
5868 } else {
5869 vec![]
5870 }
5871 }
5872
5873 fn code_actions(
5874 &mut self,
5875 buffer_row: u32,
5876 window: &mut Window,
5877 cx: &mut Context<Self>,
5878 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5879 let mut task = self.code_actions_task.take();
5880 cx.spawn_in(window, async move |editor, cx| {
5881 while let Some(prev_task) = task {
5882 prev_task.await.log_err();
5883 task = editor
5884 .update(cx, |this, _| this.code_actions_task.take())
5885 .ok()?;
5886 }
5887
5888 editor
5889 .update(cx, |editor, cx| {
5890 editor
5891 .available_code_actions
5892 .clone()
5893 .and_then(|(location, code_actions)| {
5894 let snapshot = location.buffer.read(cx).snapshot();
5895 let point_range = location.range.to_point(&snapshot);
5896 let point_range = point_range.start.row..=point_range.end.row;
5897 if point_range.contains(&buffer_row) {
5898 Some(code_actions)
5899 } else {
5900 None
5901 }
5902 })
5903 })
5904 .ok()
5905 .flatten()
5906 })
5907 }
5908
5909 pub fn confirm_code_action(
5910 &mut self,
5911 action: &ConfirmCodeAction,
5912 window: &mut Window,
5913 cx: &mut Context<Self>,
5914 ) -> Option<Task<Result<()>>> {
5915 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5916
5917 let actions_menu =
5918 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5919 menu
5920 } else {
5921 return None;
5922 };
5923
5924 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5925 let action = actions_menu.actions.get(action_ix)?;
5926 let title = action.label();
5927 let buffer = actions_menu.buffer;
5928 let workspace = self.workspace()?;
5929
5930 match action {
5931 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5932 workspace.update(cx, |workspace, cx| {
5933 workspace.schedule_resolved_task(
5934 task_source_kind,
5935 resolved_task,
5936 false,
5937 window,
5938 cx,
5939 );
5940
5941 Some(Task::ready(Ok(())))
5942 })
5943 }
5944 CodeActionsItem::CodeAction {
5945 excerpt_id,
5946 action,
5947 provider,
5948 } => {
5949 let apply_code_action =
5950 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5951 let workspace = workspace.downgrade();
5952 Some(cx.spawn_in(window, async move |editor, cx| {
5953 let project_transaction = apply_code_action.await?;
5954 Self::open_project_transaction(
5955 &editor,
5956 workspace,
5957 project_transaction,
5958 title,
5959 cx,
5960 )
5961 .await
5962 }))
5963 }
5964 CodeActionsItem::DebugScenario(scenario) => {
5965 let context = actions_menu.actions.context.clone();
5966
5967 workspace.update(cx, |workspace, cx| {
5968 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5969 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5970 });
5971 Some(Task::ready(Ok(())))
5972 }
5973 }
5974 }
5975
5976 pub async fn open_project_transaction(
5977 this: &WeakEntity<Editor>,
5978 workspace: WeakEntity<Workspace>,
5979 transaction: ProjectTransaction,
5980 title: String,
5981 cx: &mut AsyncWindowContext,
5982 ) -> Result<()> {
5983 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5984 cx.update(|_, cx| {
5985 entries.sort_unstable_by_key(|(buffer, _)| {
5986 buffer.read(cx).file().map(|f| f.path().clone())
5987 });
5988 })?;
5989
5990 // If the project transaction's edits are all contained within this editor, then
5991 // avoid opening a new editor to display them.
5992
5993 if let Some((buffer, transaction)) = entries.first() {
5994 if entries.len() == 1 {
5995 let excerpt = this.update(cx, |editor, cx| {
5996 editor
5997 .buffer()
5998 .read(cx)
5999 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6000 })?;
6001 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6002 if excerpted_buffer == *buffer {
6003 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6004 let excerpt_range = excerpt_range.to_offset(buffer);
6005 buffer
6006 .edited_ranges_for_transaction::<usize>(transaction)
6007 .all(|range| {
6008 excerpt_range.start <= range.start
6009 && excerpt_range.end >= range.end
6010 })
6011 })?;
6012
6013 if all_edits_within_excerpt {
6014 return Ok(());
6015 }
6016 }
6017 }
6018 }
6019 } else {
6020 return Ok(());
6021 }
6022
6023 let mut ranges_to_highlight = Vec::new();
6024 let excerpt_buffer = cx.new(|cx| {
6025 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6026 for (buffer_handle, transaction) in &entries {
6027 let edited_ranges = buffer_handle
6028 .read(cx)
6029 .edited_ranges_for_transaction::<Point>(transaction)
6030 .collect::<Vec<_>>();
6031 let (ranges, _) = multibuffer.set_excerpts_for_path(
6032 PathKey::for_buffer(buffer_handle, cx),
6033 buffer_handle.clone(),
6034 edited_ranges,
6035 DEFAULT_MULTIBUFFER_CONTEXT,
6036 cx,
6037 );
6038
6039 ranges_to_highlight.extend(ranges);
6040 }
6041 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6042 multibuffer
6043 })?;
6044
6045 workspace.update_in(cx, |workspace, window, cx| {
6046 let project = workspace.project().clone();
6047 let editor =
6048 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6049 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6050 editor.update(cx, |editor, cx| {
6051 editor.highlight_background::<Self>(
6052 &ranges_to_highlight,
6053 |theme| theme.editor_highlighted_line_background,
6054 cx,
6055 );
6056 });
6057 })?;
6058
6059 Ok(())
6060 }
6061
6062 pub fn clear_code_action_providers(&mut self) {
6063 self.code_action_providers.clear();
6064 self.available_code_actions.take();
6065 }
6066
6067 pub fn add_code_action_provider(
6068 &mut self,
6069 provider: Rc<dyn CodeActionProvider>,
6070 window: &mut Window,
6071 cx: &mut Context<Self>,
6072 ) {
6073 if self
6074 .code_action_providers
6075 .iter()
6076 .any(|existing_provider| existing_provider.id() == provider.id())
6077 {
6078 return;
6079 }
6080
6081 self.code_action_providers.push(provider);
6082 self.refresh_code_actions(window, cx);
6083 }
6084
6085 pub fn remove_code_action_provider(
6086 &mut self,
6087 id: Arc<str>,
6088 window: &mut Window,
6089 cx: &mut Context<Self>,
6090 ) {
6091 self.code_action_providers
6092 .retain(|provider| provider.id() != id);
6093 self.refresh_code_actions(window, cx);
6094 }
6095
6096 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6097 !self.code_action_providers.is_empty()
6098 && EditorSettings::get_global(cx).toolbar.code_actions
6099 }
6100
6101 pub fn has_available_code_actions(&self) -> bool {
6102 self.available_code_actions
6103 .as_ref()
6104 .is_some_and(|(_, actions)| !actions.is_empty())
6105 }
6106
6107 fn render_inline_code_actions(
6108 &self,
6109 icon_size: ui::IconSize,
6110 display_row: DisplayRow,
6111 is_active: bool,
6112 cx: &mut Context<Self>,
6113 ) -> AnyElement {
6114 let show_tooltip = !self.context_menu_visible();
6115 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6116 .icon_size(icon_size)
6117 .shape(ui::IconButtonShape::Square)
6118 .style(ButtonStyle::Transparent)
6119 .icon_color(ui::Color::Hidden)
6120 .toggle_state(is_active)
6121 .when(show_tooltip, |this| {
6122 this.tooltip({
6123 let focus_handle = self.focus_handle.clone();
6124 move |window, cx| {
6125 Tooltip::for_action_in(
6126 "Toggle Code Actions",
6127 &ToggleCodeActions {
6128 deployed_from: None,
6129 quick_launch: false,
6130 },
6131 &focus_handle,
6132 window,
6133 cx,
6134 )
6135 }
6136 })
6137 })
6138 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6139 window.focus(&editor.focus_handle(cx));
6140 editor.toggle_code_actions(
6141 &crate::actions::ToggleCodeActions {
6142 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6143 display_row,
6144 )),
6145 quick_launch: false,
6146 },
6147 window,
6148 cx,
6149 );
6150 }))
6151 .into_any_element()
6152 }
6153
6154 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6155 &self.context_menu
6156 }
6157
6158 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6159 let newest_selection = self.selections.newest_anchor().clone();
6160 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6161 let buffer = self.buffer.read(cx);
6162 if newest_selection.head().diff_base_anchor.is_some() {
6163 return None;
6164 }
6165 let (start_buffer, start) =
6166 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6167 let (end_buffer, end) =
6168 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6169 if start_buffer != end_buffer {
6170 return None;
6171 }
6172
6173 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6174 cx.background_executor()
6175 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6176 .await;
6177
6178 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6179 let providers = this.code_action_providers.clone();
6180 let tasks = this
6181 .code_action_providers
6182 .iter()
6183 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6184 .collect::<Vec<_>>();
6185 (providers, tasks)
6186 })?;
6187
6188 let mut actions = Vec::new();
6189 for (provider, provider_actions) in
6190 providers.into_iter().zip(future::join_all(tasks).await)
6191 {
6192 if let Some(provider_actions) = provider_actions.log_err() {
6193 actions.extend(provider_actions.into_iter().map(|action| {
6194 AvailableCodeAction {
6195 excerpt_id: newest_selection.start.excerpt_id,
6196 action,
6197 provider: provider.clone(),
6198 }
6199 }));
6200 }
6201 }
6202
6203 this.update(cx, |this, cx| {
6204 this.available_code_actions = if actions.is_empty() {
6205 None
6206 } else {
6207 Some((
6208 Location {
6209 buffer: start_buffer,
6210 range: start..end,
6211 },
6212 actions.into(),
6213 ))
6214 };
6215 cx.notify();
6216 })
6217 }));
6218 None
6219 }
6220
6221 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6222 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6223 self.show_git_blame_inline = false;
6224
6225 self.show_git_blame_inline_delay_task =
6226 Some(cx.spawn_in(window, async move |this, cx| {
6227 cx.background_executor().timer(delay).await;
6228
6229 this.update(cx, |this, cx| {
6230 this.show_git_blame_inline = true;
6231 cx.notify();
6232 })
6233 .log_err();
6234 }));
6235 }
6236 }
6237
6238 fn show_blame_popover(
6239 &mut self,
6240 blame_entry: &BlameEntry,
6241 position: gpui::Point<Pixels>,
6242 cx: &mut Context<Self>,
6243 ) {
6244 if let Some(state) = &mut self.inline_blame_popover {
6245 state.hide_task.take();
6246 cx.notify();
6247 } else {
6248 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6249 let show_task = cx.spawn(async move |editor, cx| {
6250 cx.background_executor()
6251 .timer(std::time::Duration::from_millis(delay))
6252 .await;
6253 editor
6254 .update(cx, |editor, cx| {
6255 if let Some(state) = &mut editor.inline_blame_popover {
6256 state.show_task = None;
6257 cx.notify();
6258 }
6259 })
6260 .ok();
6261 });
6262 let Some(blame) = self.blame.as_ref() else {
6263 return;
6264 };
6265 let blame = blame.read(cx);
6266 let details = blame.details_for_entry(&blame_entry);
6267 let markdown = cx.new(|cx| {
6268 Markdown::new(
6269 details
6270 .as_ref()
6271 .map(|message| message.message.clone())
6272 .unwrap_or_default(),
6273 None,
6274 None,
6275 cx,
6276 )
6277 });
6278 self.inline_blame_popover = Some(InlineBlamePopover {
6279 position,
6280 show_task: Some(show_task),
6281 hide_task: None,
6282 popover_bounds: None,
6283 popover_state: InlineBlamePopoverState {
6284 scroll_handle: ScrollHandle::new(),
6285 commit_message: details,
6286 markdown,
6287 },
6288 });
6289 }
6290 }
6291
6292 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6293 if let Some(state) = &mut self.inline_blame_popover {
6294 if state.show_task.is_some() {
6295 self.inline_blame_popover.take();
6296 cx.notify();
6297 } else {
6298 let hide_task = cx.spawn(async move |editor, cx| {
6299 cx.background_executor()
6300 .timer(std::time::Duration::from_millis(100))
6301 .await;
6302 editor
6303 .update(cx, |editor, cx| {
6304 editor.inline_blame_popover.take();
6305 cx.notify();
6306 })
6307 .ok();
6308 });
6309 state.hide_task = Some(hide_task);
6310 }
6311 }
6312 }
6313
6314 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6315 if self.pending_rename.is_some() {
6316 return None;
6317 }
6318
6319 let provider = self.semantics_provider.clone()?;
6320 let buffer = self.buffer.read(cx);
6321 let newest_selection = self.selections.newest_anchor().clone();
6322 let cursor_position = newest_selection.head();
6323 let (cursor_buffer, cursor_buffer_position) =
6324 buffer.text_anchor_for_position(cursor_position, cx)?;
6325 let (tail_buffer, tail_buffer_position) =
6326 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6327 if cursor_buffer != tail_buffer {
6328 return None;
6329 }
6330
6331 let snapshot = cursor_buffer.read(cx).snapshot();
6332 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6333 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6334 if start_word_range != end_word_range {
6335 self.document_highlights_task.take();
6336 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6337 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6338 return None;
6339 }
6340
6341 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6342 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6343 cx.background_executor()
6344 .timer(Duration::from_millis(debounce))
6345 .await;
6346
6347 let highlights = if let Some(highlights) = cx
6348 .update(|cx| {
6349 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6350 })
6351 .ok()
6352 .flatten()
6353 {
6354 highlights.await.log_err()
6355 } else {
6356 None
6357 };
6358
6359 if let Some(highlights) = highlights {
6360 this.update(cx, |this, cx| {
6361 if this.pending_rename.is_some() {
6362 return;
6363 }
6364
6365 let buffer_id = cursor_position.buffer_id;
6366 let buffer = this.buffer.read(cx);
6367 if !buffer
6368 .text_anchor_for_position(cursor_position, cx)
6369 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6370 {
6371 return;
6372 }
6373
6374 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6375 let mut write_ranges = Vec::new();
6376 let mut read_ranges = Vec::new();
6377 for highlight in highlights {
6378 for (excerpt_id, excerpt_range) in
6379 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6380 {
6381 let start = highlight
6382 .range
6383 .start
6384 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6385 let end = highlight
6386 .range
6387 .end
6388 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6389 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6390 continue;
6391 }
6392
6393 let range = Anchor {
6394 buffer_id,
6395 excerpt_id,
6396 text_anchor: start,
6397 diff_base_anchor: None,
6398 }..Anchor {
6399 buffer_id,
6400 excerpt_id,
6401 text_anchor: end,
6402 diff_base_anchor: None,
6403 };
6404 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6405 write_ranges.push(range);
6406 } else {
6407 read_ranges.push(range);
6408 }
6409 }
6410 }
6411
6412 this.highlight_background::<DocumentHighlightRead>(
6413 &read_ranges,
6414 |theme| theme.editor_document_highlight_read_background,
6415 cx,
6416 );
6417 this.highlight_background::<DocumentHighlightWrite>(
6418 &write_ranges,
6419 |theme| theme.editor_document_highlight_write_background,
6420 cx,
6421 );
6422 cx.notify();
6423 })
6424 .log_err();
6425 }
6426 }));
6427 None
6428 }
6429
6430 fn prepare_highlight_query_from_selection(
6431 &mut self,
6432 cx: &mut Context<Editor>,
6433 ) -> Option<(String, Range<Anchor>)> {
6434 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6435 return None;
6436 }
6437 if !EditorSettings::get_global(cx).selection_highlight {
6438 return None;
6439 }
6440 if self.selections.count() != 1 || self.selections.line_mode {
6441 return None;
6442 }
6443 let selection = self.selections.newest::<Point>(cx);
6444 if selection.is_empty() || selection.start.row != selection.end.row {
6445 return None;
6446 }
6447 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6448 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6449 let query = multi_buffer_snapshot
6450 .text_for_range(selection_anchor_range.clone())
6451 .collect::<String>();
6452 if query.trim().is_empty() {
6453 return None;
6454 }
6455 Some((query, selection_anchor_range))
6456 }
6457
6458 fn update_selection_occurrence_highlights(
6459 &mut self,
6460 query_text: String,
6461 query_range: Range<Anchor>,
6462 multi_buffer_range_to_query: Range<Point>,
6463 use_debounce: bool,
6464 window: &mut Window,
6465 cx: &mut Context<Editor>,
6466 ) -> Task<()> {
6467 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6468 cx.spawn_in(window, async move |editor, cx| {
6469 if use_debounce {
6470 cx.background_executor()
6471 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6472 .await;
6473 }
6474 let match_task = cx.background_spawn(async move {
6475 let buffer_ranges = multi_buffer_snapshot
6476 .range_to_buffer_ranges(multi_buffer_range_to_query)
6477 .into_iter()
6478 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6479 let mut match_ranges = Vec::new();
6480 let Ok(regex) = project::search::SearchQuery::text(
6481 query_text.clone(),
6482 false,
6483 false,
6484 false,
6485 Default::default(),
6486 Default::default(),
6487 false,
6488 None,
6489 ) else {
6490 return Vec::default();
6491 };
6492 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6493 match_ranges.extend(
6494 regex
6495 .search(&buffer_snapshot, Some(search_range.clone()))
6496 .await
6497 .into_iter()
6498 .filter_map(|match_range| {
6499 let match_start = buffer_snapshot
6500 .anchor_after(search_range.start + match_range.start);
6501 let match_end = buffer_snapshot
6502 .anchor_before(search_range.start + match_range.end);
6503 let match_anchor_range = Anchor::range_in_buffer(
6504 excerpt_id,
6505 buffer_snapshot.remote_id(),
6506 match_start..match_end,
6507 );
6508 (match_anchor_range != query_range).then_some(match_anchor_range)
6509 }),
6510 );
6511 }
6512 match_ranges
6513 });
6514 let match_ranges = match_task.await;
6515 editor
6516 .update_in(cx, |editor, _, cx| {
6517 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6518 if !match_ranges.is_empty() {
6519 editor.highlight_background::<SelectedTextHighlight>(
6520 &match_ranges,
6521 |theme| theme.editor_document_highlight_bracket_background,
6522 cx,
6523 )
6524 }
6525 })
6526 .log_err();
6527 })
6528 }
6529
6530 fn refresh_selected_text_highlights(
6531 &mut self,
6532 on_buffer_edit: bool,
6533 window: &mut Window,
6534 cx: &mut Context<Editor>,
6535 ) {
6536 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6537 else {
6538 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6539 self.quick_selection_highlight_task.take();
6540 self.debounced_selection_highlight_task.take();
6541 return;
6542 };
6543 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6544 if on_buffer_edit
6545 || self
6546 .quick_selection_highlight_task
6547 .as_ref()
6548 .map_or(true, |(prev_anchor_range, _)| {
6549 prev_anchor_range != &query_range
6550 })
6551 {
6552 let multi_buffer_visible_start = self
6553 .scroll_manager
6554 .anchor()
6555 .anchor
6556 .to_point(&multi_buffer_snapshot);
6557 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6558 multi_buffer_visible_start
6559 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6560 Bias::Left,
6561 );
6562 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6563 self.quick_selection_highlight_task = Some((
6564 query_range.clone(),
6565 self.update_selection_occurrence_highlights(
6566 query_text.clone(),
6567 query_range.clone(),
6568 multi_buffer_visible_range,
6569 false,
6570 window,
6571 cx,
6572 ),
6573 ));
6574 }
6575 if on_buffer_edit
6576 || self
6577 .debounced_selection_highlight_task
6578 .as_ref()
6579 .map_or(true, |(prev_anchor_range, _)| {
6580 prev_anchor_range != &query_range
6581 })
6582 {
6583 let multi_buffer_start = multi_buffer_snapshot
6584 .anchor_before(0)
6585 .to_point(&multi_buffer_snapshot);
6586 let multi_buffer_end = multi_buffer_snapshot
6587 .anchor_after(multi_buffer_snapshot.len())
6588 .to_point(&multi_buffer_snapshot);
6589 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6590 self.debounced_selection_highlight_task = Some((
6591 query_range.clone(),
6592 self.update_selection_occurrence_highlights(
6593 query_text,
6594 query_range,
6595 multi_buffer_full_range,
6596 true,
6597 window,
6598 cx,
6599 ),
6600 ));
6601 }
6602 }
6603
6604 pub fn refresh_inline_completion(
6605 &mut self,
6606 debounce: bool,
6607 user_requested: bool,
6608 window: &mut Window,
6609 cx: &mut Context<Self>,
6610 ) -> Option<()> {
6611 let provider = self.edit_prediction_provider()?;
6612 let cursor = self.selections.newest_anchor().head();
6613 let (buffer, cursor_buffer_position) =
6614 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6615
6616 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6617 self.discard_inline_completion(false, cx);
6618 return None;
6619 }
6620
6621 if !user_requested
6622 && (!self.should_show_edit_predictions()
6623 || !self.is_focused(window)
6624 || buffer.read(cx).is_empty())
6625 {
6626 self.discard_inline_completion(false, cx);
6627 return None;
6628 }
6629
6630 self.update_visible_inline_completion(window, cx);
6631 provider.refresh(
6632 self.project.clone(),
6633 buffer,
6634 cursor_buffer_position,
6635 debounce,
6636 cx,
6637 );
6638 Some(())
6639 }
6640
6641 fn show_edit_predictions_in_menu(&self) -> bool {
6642 match self.edit_prediction_settings {
6643 EditPredictionSettings::Disabled => false,
6644 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6645 }
6646 }
6647
6648 pub fn edit_predictions_enabled(&self) -> bool {
6649 match self.edit_prediction_settings {
6650 EditPredictionSettings::Disabled => false,
6651 EditPredictionSettings::Enabled { .. } => true,
6652 }
6653 }
6654
6655 fn edit_prediction_requires_modifier(&self) -> bool {
6656 match self.edit_prediction_settings {
6657 EditPredictionSettings::Disabled => false,
6658 EditPredictionSettings::Enabled {
6659 preview_requires_modifier,
6660 ..
6661 } => preview_requires_modifier,
6662 }
6663 }
6664
6665 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6666 if self.edit_prediction_provider.is_none() {
6667 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6668 } else {
6669 let selection = self.selections.newest_anchor();
6670 let cursor = selection.head();
6671
6672 if let Some((buffer, cursor_buffer_position)) =
6673 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6674 {
6675 self.edit_prediction_settings =
6676 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6677 }
6678 }
6679 }
6680
6681 fn edit_prediction_settings_at_position(
6682 &self,
6683 buffer: &Entity<Buffer>,
6684 buffer_position: language::Anchor,
6685 cx: &App,
6686 ) -> EditPredictionSettings {
6687 if !self.mode.is_full()
6688 || !self.show_inline_completions_override.unwrap_or(true)
6689 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6690 {
6691 return EditPredictionSettings::Disabled;
6692 }
6693
6694 let buffer = buffer.read(cx);
6695
6696 let file = buffer.file();
6697
6698 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6699 return EditPredictionSettings::Disabled;
6700 };
6701
6702 let by_provider = matches!(
6703 self.menu_inline_completions_policy,
6704 MenuInlineCompletionsPolicy::ByProvider
6705 );
6706
6707 let show_in_menu = by_provider
6708 && self
6709 .edit_prediction_provider
6710 .as_ref()
6711 .map_or(false, |provider| {
6712 provider.provider.show_completions_in_menu()
6713 });
6714
6715 let preview_requires_modifier =
6716 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6717
6718 EditPredictionSettings::Enabled {
6719 show_in_menu,
6720 preview_requires_modifier,
6721 }
6722 }
6723
6724 fn should_show_edit_predictions(&self) -> bool {
6725 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6726 }
6727
6728 pub fn edit_prediction_preview_is_active(&self) -> bool {
6729 matches!(
6730 self.edit_prediction_preview,
6731 EditPredictionPreview::Active { .. }
6732 )
6733 }
6734
6735 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6736 let cursor = self.selections.newest_anchor().head();
6737 if let Some((buffer, cursor_position)) =
6738 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6739 {
6740 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6741 } else {
6742 false
6743 }
6744 }
6745
6746 pub fn supports_minimap(&self, cx: &App) -> bool {
6747 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6748 }
6749
6750 fn edit_predictions_enabled_in_buffer(
6751 &self,
6752 buffer: &Entity<Buffer>,
6753 buffer_position: language::Anchor,
6754 cx: &App,
6755 ) -> bool {
6756 maybe!({
6757 if self.read_only(cx) {
6758 return Some(false);
6759 }
6760 let provider = self.edit_prediction_provider()?;
6761 if !provider.is_enabled(&buffer, buffer_position, cx) {
6762 return Some(false);
6763 }
6764 let buffer = buffer.read(cx);
6765 let Some(file) = buffer.file() else {
6766 return Some(true);
6767 };
6768 let settings = all_language_settings(Some(file), cx);
6769 Some(settings.edit_predictions_enabled_for_file(file, cx))
6770 })
6771 .unwrap_or(false)
6772 }
6773
6774 fn cycle_inline_completion(
6775 &mut self,
6776 direction: Direction,
6777 window: &mut Window,
6778 cx: &mut Context<Self>,
6779 ) -> Option<()> {
6780 let provider = self.edit_prediction_provider()?;
6781 let cursor = self.selections.newest_anchor().head();
6782 let (buffer, cursor_buffer_position) =
6783 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6784 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6785 return None;
6786 }
6787
6788 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6789 self.update_visible_inline_completion(window, cx);
6790
6791 Some(())
6792 }
6793
6794 pub fn show_inline_completion(
6795 &mut self,
6796 _: &ShowEditPrediction,
6797 window: &mut Window,
6798 cx: &mut Context<Self>,
6799 ) {
6800 if !self.has_active_inline_completion() {
6801 self.refresh_inline_completion(false, true, window, cx);
6802 return;
6803 }
6804
6805 self.update_visible_inline_completion(window, cx);
6806 }
6807
6808 pub fn display_cursor_names(
6809 &mut self,
6810 _: &DisplayCursorNames,
6811 window: &mut Window,
6812 cx: &mut Context<Self>,
6813 ) {
6814 self.show_cursor_names(window, cx);
6815 }
6816
6817 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6818 self.show_cursor_names = true;
6819 cx.notify();
6820 cx.spawn_in(window, async move |this, cx| {
6821 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6822 this.update(cx, |this, cx| {
6823 this.show_cursor_names = false;
6824 cx.notify()
6825 })
6826 .ok()
6827 })
6828 .detach();
6829 }
6830
6831 pub fn next_edit_prediction(
6832 &mut self,
6833 _: &NextEditPrediction,
6834 window: &mut Window,
6835 cx: &mut Context<Self>,
6836 ) {
6837 if self.has_active_inline_completion() {
6838 self.cycle_inline_completion(Direction::Next, window, cx);
6839 } else {
6840 let is_copilot_disabled = self
6841 .refresh_inline_completion(false, true, window, cx)
6842 .is_none();
6843 if is_copilot_disabled {
6844 cx.propagate();
6845 }
6846 }
6847 }
6848
6849 pub fn previous_edit_prediction(
6850 &mut self,
6851 _: &PreviousEditPrediction,
6852 window: &mut Window,
6853 cx: &mut Context<Self>,
6854 ) {
6855 if self.has_active_inline_completion() {
6856 self.cycle_inline_completion(Direction::Prev, window, cx);
6857 } else {
6858 let is_copilot_disabled = self
6859 .refresh_inline_completion(false, true, window, cx)
6860 .is_none();
6861 if is_copilot_disabled {
6862 cx.propagate();
6863 }
6864 }
6865 }
6866
6867 pub fn accept_edit_prediction(
6868 &mut self,
6869 _: &AcceptEditPrediction,
6870 window: &mut Window,
6871 cx: &mut Context<Self>,
6872 ) {
6873 if self.show_edit_predictions_in_menu() {
6874 self.hide_context_menu(window, cx);
6875 }
6876
6877 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6878 return;
6879 };
6880
6881 self.report_inline_completion_event(
6882 active_inline_completion.completion_id.clone(),
6883 true,
6884 cx,
6885 );
6886
6887 match &active_inline_completion.completion {
6888 InlineCompletion::Move { target, .. } => {
6889 let target = *target;
6890
6891 if let Some(position_map) = &self.last_position_map {
6892 if position_map
6893 .visible_row_range
6894 .contains(&target.to_display_point(&position_map.snapshot).row())
6895 || !self.edit_prediction_requires_modifier()
6896 {
6897 self.unfold_ranges(&[target..target], true, false, cx);
6898 // Note that this is also done in vim's handler of the Tab action.
6899 self.change_selections(
6900 Some(Autoscroll::newest()),
6901 window,
6902 cx,
6903 |selections| {
6904 selections.select_anchor_ranges([target..target]);
6905 },
6906 );
6907 self.clear_row_highlights::<EditPredictionPreview>();
6908
6909 self.edit_prediction_preview
6910 .set_previous_scroll_position(None);
6911 } else {
6912 self.edit_prediction_preview
6913 .set_previous_scroll_position(Some(
6914 position_map.snapshot.scroll_anchor,
6915 ));
6916
6917 self.highlight_rows::<EditPredictionPreview>(
6918 target..target,
6919 cx.theme().colors().editor_highlighted_line_background,
6920 RowHighlightOptions {
6921 autoscroll: true,
6922 ..Default::default()
6923 },
6924 cx,
6925 );
6926 self.request_autoscroll(Autoscroll::fit(), cx);
6927 }
6928 }
6929 }
6930 InlineCompletion::Edit { edits, .. } => {
6931 if let Some(provider) = self.edit_prediction_provider() {
6932 provider.accept(cx);
6933 }
6934
6935 // Store the transaction ID and selections before applying the edit
6936 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6937
6938 let snapshot = self.buffer.read(cx).snapshot(cx);
6939 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6940
6941 self.buffer.update(cx, |buffer, cx| {
6942 buffer.edit(edits.iter().cloned(), None, cx)
6943 });
6944
6945 self.change_selections(None, window, cx, |s| {
6946 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6947 });
6948
6949 let selections = self.selections.disjoint_anchors();
6950 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6951 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6952 if has_new_transaction {
6953 self.selection_history
6954 .insert_transaction(transaction_id_now, selections);
6955 }
6956 }
6957
6958 self.update_visible_inline_completion(window, cx);
6959 if self.active_inline_completion.is_none() {
6960 self.refresh_inline_completion(true, true, window, cx);
6961 }
6962
6963 cx.notify();
6964 }
6965 }
6966
6967 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6968 }
6969
6970 pub fn accept_partial_inline_completion(
6971 &mut self,
6972 _: &AcceptPartialEditPrediction,
6973 window: &mut Window,
6974 cx: &mut Context<Self>,
6975 ) {
6976 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6977 return;
6978 };
6979 if self.selections.count() != 1 {
6980 return;
6981 }
6982
6983 self.report_inline_completion_event(
6984 active_inline_completion.completion_id.clone(),
6985 true,
6986 cx,
6987 );
6988
6989 match &active_inline_completion.completion {
6990 InlineCompletion::Move { target, .. } => {
6991 let target = *target;
6992 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6993 selections.select_anchor_ranges([target..target]);
6994 });
6995 }
6996 InlineCompletion::Edit { edits, .. } => {
6997 // Find an insertion that starts at the cursor position.
6998 let snapshot = self.buffer.read(cx).snapshot(cx);
6999 let cursor_offset = self.selections.newest::<usize>(cx).head();
7000 let insertion = edits.iter().find_map(|(range, text)| {
7001 let range = range.to_offset(&snapshot);
7002 if range.is_empty() && range.start == cursor_offset {
7003 Some(text)
7004 } else {
7005 None
7006 }
7007 });
7008
7009 if let Some(text) = insertion {
7010 let mut partial_completion = text
7011 .chars()
7012 .by_ref()
7013 .take_while(|c| c.is_alphabetic())
7014 .collect::<String>();
7015 if partial_completion.is_empty() {
7016 partial_completion = text
7017 .chars()
7018 .by_ref()
7019 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7020 .collect::<String>();
7021 }
7022
7023 cx.emit(EditorEvent::InputHandled {
7024 utf16_range_to_replace: None,
7025 text: partial_completion.clone().into(),
7026 });
7027
7028 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7029
7030 self.refresh_inline_completion(true, true, window, cx);
7031 cx.notify();
7032 } else {
7033 self.accept_edit_prediction(&Default::default(), window, cx);
7034 }
7035 }
7036 }
7037 }
7038
7039 fn discard_inline_completion(
7040 &mut self,
7041 should_report_inline_completion_event: bool,
7042 cx: &mut Context<Self>,
7043 ) -> bool {
7044 if should_report_inline_completion_event {
7045 let completion_id = self
7046 .active_inline_completion
7047 .as_ref()
7048 .and_then(|active_completion| active_completion.completion_id.clone());
7049
7050 self.report_inline_completion_event(completion_id, false, cx);
7051 }
7052
7053 if let Some(provider) = self.edit_prediction_provider() {
7054 provider.discard(cx);
7055 }
7056
7057 self.take_active_inline_completion(cx)
7058 }
7059
7060 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7061 let Some(provider) = self.edit_prediction_provider() else {
7062 return;
7063 };
7064
7065 let Some((_, buffer, _)) = self
7066 .buffer
7067 .read(cx)
7068 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7069 else {
7070 return;
7071 };
7072
7073 let extension = buffer
7074 .read(cx)
7075 .file()
7076 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7077
7078 let event_type = match accepted {
7079 true => "Edit Prediction Accepted",
7080 false => "Edit Prediction Discarded",
7081 };
7082 telemetry::event!(
7083 event_type,
7084 provider = provider.name(),
7085 prediction_id = id,
7086 suggestion_accepted = accepted,
7087 file_extension = extension,
7088 );
7089 }
7090
7091 pub fn has_active_inline_completion(&self) -> bool {
7092 self.active_inline_completion.is_some()
7093 }
7094
7095 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7096 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7097 return false;
7098 };
7099
7100 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7101 self.clear_highlights::<InlineCompletionHighlight>(cx);
7102 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7103 true
7104 }
7105
7106 /// Returns true when we're displaying the edit prediction popover below the cursor
7107 /// like we are not previewing and the LSP autocomplete menu is visible
7108 /// or we are in `when_holding_modifier` mode.
7109 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7110 if self.edit_prediction_preview_is_active()
7111 || !self.show_edit_predictions_in_menu()
7112 || !self.edit_predictions_enabled()
7113 {
7114 return false;
7115 }
7116
7117 if self.has_visible_completions_menu() {
7118 return true;
7119 }
7120
7121 has_completion && self.edit_prediction_requires_modifier()
7122 }
7123
7124 fn handle_modifiers_changed(
7125 &mut self,
7126 modifiers: Modifiers,
7127 position_map: &PositionMap,
7128 window: &mut Window,
7129 cx: &mut Context<Self>,
7130 ) {
7131 if self.show_edit_predictions_in_menu() {
7132 self.update_edit_prediction_preview(&modifiers, window, cx);
7133 }
7134
7135 self.update_selection_mode(&modifiers, position_map, window, cx);
7136
7137 let mouse_position = window.mouse_position();
7138 if !position_map.text_hitbox.is_hovered(window) {
7139 return;
7140 }
7141
7142 self.update_hovered_link(
7143 position_map.point_for_position(mouse_position),
7144 &position_map.snapshot,
7145 modifiers,
7146 window,
7147 cx,
7148 )
7149 }
7150
7151 fn multi_cursor_modifier(
7152 cursor_event: bool,
7153 modifiers: &Modifiers,
7154 cx: &mut Context<Self>,
7155 ) -> bool {
7156 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7157 if cursor_event {
7158 match multi_cursor_setting {
7159 MultiCursorModifier::Alt => modifiers.alt,
7160 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7161 }
7162 } else {
7163 match multi_cursor_setting {
7164 MultiCursorModifier::Alt => modifiers.secondary(),
7165 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7166 }
7167 }
7168 }
7169
7170 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7171 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7172 }
7173
7174 fn update_selection_mode(
7175 &mut self,
7176 modifiers: &Modifiers,
7177 position_map: &PositionMap,
7178 window: &mut Window,
7179 cx: &mut Context<Self>,
7180 ) {
7181 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7182 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7183 || self.selections.pending.is_none()
7184 {
7185 return;
7186 }
7187
7188 let mouse_position = window.mouse_position();
7189 let point_for_position = position_map.point_for_position(mouse_position);
7190 let position = point_for_position.previous_valid;
7191
7192 self.select(
7193 SelectPhase::BeginColumnar {
7194 position,
7195 reset: false,
7196 goal_column: point_for_position.exact_unclipped.column(),
7197 },
7198 window,
7199 cx,
7200 );
7201 }
7202
7203 fn update_edit_prediction_preview(
7204 &mut self,
7205 modifiers: &Modifiers,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 let mut modifiers_held = false;
7210 if let Some(accept_keystroke) = self
7211 .accept_edit_prediction_keybind(false, window, cx)
7212 .keystroke()
7213 {
7214 modifiers_held = modifiers_held
7215 || (&accept_keystroke.modifiers == modifiers
7216 && accept_keystroke.modifiers.modified());
7217 };
7218 if let Some(accept_partial_keystroke) = self
7219 .accept_edit_prediction_keybind(true, window, cx)
7220 .keystroke()
7221 {
7222 modifiers_held = modifiers_held
7223 || (&accept_partial_keystroke.modifiers == modifiers
7224 && accept_partial_keystroke.modifiers.modified());
7225 }
7226
7227 if modifiers_held {
7228 if matches!(
7229 self.edit_prediction_preview,
7230 EditPredictionPreview::Inactive { .. }
7231 ) {
7232 self.edit_prediction_preview = EditPredictionPreview::Active {
7233 previous_scroll_position: None,
7234 since: Instant::now(),
7235 };
7236
7237 self.update_visible_inline_completion(window, cx);
7238 cx.notify();
7239 }
7240 } else if let EditPredictionPreview::Active {
7241 previous_scroll_position,
7242 since,
7243 } = self.edit_prediction_preview
7244 {
7245 if let (Some(previous_scroll_position), Some(position_map)) =
7246 (previous_scroll_position, self.last_position_map.as_ref())
7247 {
7248 self.set_scroll_position(
7249 previous_scroll_position
7250 .scroll_position(&position_map.snapshot.display_snapshot),
7251 window,
7252 cx,
7253 );
7254 }
7255
7256 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7257 released_too_fast: since.elapsed() < Duration::from_millis(200),
7258 };
7259 self.clear_row_highlights::<EditPredictionPreview>();
7260 self.update_visible_inline_completion(window, cx);
7261 cx.notify();
7262 }
7263 }
7264
7265 fn update_visible_inline_completion(
7266 &mut self,
7267 _window: &mut Window,
7268 cx: &mut Context<Self>,
7269 ) -> Option<()> {
7270 let selection = self.selections.newest_anchor();
7271 let cursor = selection.head();
7272 let multibuffer = self.buffer.read(cx).snapshot(cx);
7273 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7274 let excerpt_id = cursor.excerpt_id;
7275
7276 let show_in_menu = self.show_edit_predictions_in_menu();
7277 let completions_menu_has_precedence = !show_in_menu
7278 && (self.context_menu.borrow().is_some()
7279 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7280
7281 if completions_menu_has_precedence
7282 || !offset_selection.is_empty()
7283 || self
7284 .active_inline_completion
7285 .as_ref()
7286 .map_or(false, |completion| {
7287 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7288 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7289 !invalidation_range.contains(&offset_selection.head())
7290 })
7291 {
7292 self.discard_inline_completion(false, cx);
7293 return None;
7294 }
7295
7296 self.take_active_inline_completion(cx);
7297 let Some(provider) = self.edit_prediction_provider() else {
7298 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7299 return None;
7300 };
7301
7302 let (buffer, cursor_buffer_position) =
7303 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7304
7305 self.edit_prediction_settings =
7306 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7307
7308 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7309
7310 if self.edit_prediction_indent_conflict {
7311 let cursor_point = cursor.to_point(&multibuffer);
7312
7313 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7314
7315 if let Some((_, indent)) = indents.iter().next() {
7316 if indent.len == cursor_point.column {
7317 self.edit_prediction_indent_conflict = false;
7318 }
7319 }
7320 }
7321
7322 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7323 let edits = inline_completion
7324 .edits
7325 .into_iter()
7326 .flat_map(|(range, new_text)| {
7327 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7328 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7329 Some((start..end, new_text))
7330 })
7331 .collect::<Vec<_>>();
7332 if edits.is_empty() {
7333 return None;
7334 }
7335
7336 let first_edit_start = edits.first().unwrap().0.start;
7337 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7338 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7339
7340 let last_edit_end = edits.last().unwrap().0.end;
7341 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7342 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7343
7344 let cursor_row = cursor.to_point(&multibuffer).row;
7345
7346 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7347
7348 let mut inlay_ids = Vec::new();
7349 let invalidation_row_range;
7350 let move_invalidation_row_range = if cursor_row < edit_start_row {
7351 Some(cursor_row..edit_end_row)
7352 } else if cursor_row > edit_end_row {
7353 Some(edit_start_row..cursor_row)
7354 } else {
7355 None
7356 };
7357 let is_move =
7358 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7359 let completion = if is_move {
7360 invalidation_row_range =
7361 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7362 let target = first_edit_start;
7363 InlineCompletion::Move { target, snapshot }
7364 } else {
7365 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7366 && !self.inline_completions_hidden_for_vim_mode;
7367
7368 if show_completions_in_buffer {
7369 if edits
7370 .iter()
7371 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7372 {
7373 let mut inlays = Vec::new();
7374 for (range, new_text) in &edits {
7375 let inlay = Inlay::inline_completion(
7376 post_inc(&mut self.next_inlay_id),
7377 range.start,
7378 new_text.as_str(),
7379 );
7380 inlay_ids.push(inlay.id);
7381 inlays.push(inlay);
7382 }
7383
7384 self.splice_inlays(&[], inlays, cx);
7385 } else {
7386 let background_color = cx.theme().status().deleted_background;
7387 self.highlight_text::<InlineCompletionHighlight>(
7388 edits.iter().map(|(range, _)| range.clone()).collect(),
7389 HighlightStyle {
7390 background_color: Some(background_color),
7391 ..Default::default()
7392 },
7393 cx,
7394 );
7395 }
7396 }
7397
7398 invalidation_row_range = edit_start_row..edit_end_row;
7399
7400 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7401 if provider.show_tab_accept_marker() {
7402 EditDisplayMode::TabAccept
7403 } else {
7404 EditDisplayMode::Inline
7405 }
7406 } else {
7407 EditDisplayMode::DiffPopover
7408 };
7409
7410 InlineCompletion::Edit {
7411 edits,
7412 edit_preview: inline_completion.edit_preview,
7413 display_mode,
7414 snapshot,
7415 }
7416 };
7417
7418 let invalidation_range = multibuffer
7419 .anchor_before(Point::new(invalidation_row_range.start, 0))
7420 ..multibuffer.anchor_after(Point::new(
7421 invalidation_row_range.end,
7422 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7423 ));
7424
7425 self.stale_inline_completion_in_menu = None;
7426 self.active_inline_completion = Some(InlineCompletionState {
7427 inlay_ids,
7428 completion,
7429 completion_id: inline_completion.id,
7430 invalidation_range,
7431 });
7432
7433 cx.notify();
7434
7435 Some(())
7436 }
7437
7438 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7439 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7440 }
7441
7442 fn clear_tasks(&mut self) {
7443 self.tasks.clear()
7444 }
7445
7446 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7447 if self.tasks.insert(key, value).is_some() {
7448 // This case should hopefully be rare, but just in case...
7449 log::error!(
7450 "multiple different run targets found on a single line, only the last target will be rendered"
7451 )
7452 }
7453 }
7454
7455 /// Get all display points of breakpoints that will be rendered within editor
7456 ///
7457 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7458 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7459 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7460 fn active_breakpoints(
7461 &self,
7462 range: Range<DisplayRow>,
7463 window: &mut Window,
7464 cx: &mut Context<Self>,
7465 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7466 let mut breakpoint_display_points = HashMap::default();
7467
7468 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7469 return breakpoint_display_points;
7470 };
7471
7472 let snapshot = self.snapshot(window, cx);
7473
7474 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7475 let Some(project) = self.project.as_ref() else {
7476 return breakpoint_display_points;
7477 };
7478
7479 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7480 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7481
7482 for (buffer_snapshot, range, excerpt_id) in
7483 multi_buffer_snapshot.range_to_buffer_ranges(range)
7484 {
7485 let Some(buffer) = project
7486 .read(cx)
7487 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7488 else {
7489 continue;
7490 };
7491 let breakpoints = breakpoint_store.read(cx).breakpoints(
7492 &buffer,
7493 Some(
7494 buffer_snapshot.anchor_before(range.start)
7495 ..buffer_snapshot.anchor_after(range.end),
7496 ),
7497 buffer_snapshot,
7498 cx,
7499 );
7500 for (breakpoint, state) in breakpoints {
7501 let multi_buffer_anchor =
7502 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7503 let position = multi_buffer_anchor
7504 .to_point(&multi_buffer_snapshot)
7505 .to_display_point(&snapshot);
7506
7507 breakpoint_display_points.insert(
7508 position.row(),
7509 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7510 );
7511 }
7512 }
7513
7514 breakpoint_display_points
7515 }
7516
7517 fn breakpoint_context_menu(
7518 &self,
7519 anchor: Anchor,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) -> Entity<ui::ContextMenu> {
7523 let weak_editor = cx.weak_entity();
7524 let focus_handle = self.focus_handle(cx);
7525
7526 let row = self
7527 .buffer
7528 .read(cx)
7529 .snapshot(cx)
7530 .summary_for_anchor::<Point>(&anchor)
7531 .row;
7532
7533 let breakpoint = self
7534 .breakpoint_at_row(row, window, cx)
7535 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7536
7537 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7538 "Edit Log Breakpoint"
7539 } else {
7540 "Set Log Breakpoint"
7541 };
7542
7543 let condition_breakpoint_msg = if breakpoint
7544 .as_ref()
7545 .is_some_and(|bp| bp.1.condition.is_some())
7546 {
7547 "Edit Condition Breakpoint"
7548 } else {
7549 "Set Condition Breakpoint"
7550 };
7551
7552 let hit_condition_breakpoint_msg = if breakpoint
7553 .as_ref()
7554 .is_some_and(|bp| bp.1.hit_condition.is_some())
7555 {
7556 "Edit Hit Condition Breakpoint"
7557 } else {
7558 "Set Hit Condition Breakpoint"
7559 };
7560
7561 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7562 "Unset Breakpoint"
7563 } else {
7564 "Set Breakpoint"
7565 };
7566
7567 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7568
7569 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7570 BreakpointState::Enabled => Some("Disable"),
7571 BreakpointState::Disabled => Some("Enable"),
7572 });
7573
7574 let (anchor, breakpoint) =
7575 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7576
7577 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7578 menu.on_blur_subscription(Subscription::new(|| {}))
7579 .context(focus_handle)
7580 .when(run_to_cursor, |this| {
7581 let weak_editor = weak_editor.clone();
7582 this.entry("Run to cursor", None, move |window, cx| {
7583 weak_editor
7584 .update(cx, |editor, cx| {
7585 editor.change_selections(None, window, cx, |s| {
7586 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7587 });
7588 })
7589 .ok();
7590
7591 window.dispatch_action(Box::new(RunToCursor), cx);
7592 })
7593 .separator()
7594 })
7595 .when_some(toggle_state_msg, |this, msg| {
7596 this.entry(msg, None, {
7597 let weak_editor = weak_editor.clone();
7598 let breakpoint = breakpoint.clone();
7599 move |_window, cx| {
7600 weak_editor
7601 .update(cx, |this, cx| {
7602 this.edit_breakpoint_at_anchor(
7603 anchor,
7604 breakpoint.as_ref().clone(),
7605 BreakpointEditAction::InvertState,
7606 cx,
7607 );
7608 })
7609 .log_err();
7610 }
7611 })
7612 })
7613 .entry(set_breakpoint_msg, None, {
7614 let weak_editor = weak_editor.clone();
7615 let breakpoint = breakpoint.clone();
7616 move |_window, cx| {
7617 weak_editor
7618 .update(cx, |this, cx| {
7619 this.edit_breakpoint_at_anchor(
7620 anchor,
7621 breakpoint.as_ref().clone(),
7622 BreakpointEditAction::Toggle,
7623 cx,
7624 );
7625 })
7626 .log_err();
7627 }
7628 })
7629 .entry(log_breakpoint_msg, None, {
7630 let breakpoint = breakpoint.clone();
7631 let weak_editor = weak_editor.clone();
7632 move |window, cx| {
7633 weak_editor
7634 .update(cx, |this, cx| {
7635 this.add_edit_breakpoint_block(
7636 anchor,
7637 breakpoint.as_ref(),
7638 BreakpointPromptEditAction::Log,
7639 window,
7640 cx,
7641 );
7642 })
7643 .log_err();
7644 }
7645 })
7646 .entry(condition_breakpoint_msg, None, {
7647 let breakpoint = breakpoint.clone();
7648 let weak_editor = weak_editor.clone();
7649 move |window, cx| {
7650 weak_editor
7651 .update(cx, |this, cx| {
7652 this.add_edit_breakpoint_block(
7653 anchor,
7654 breakpoint.as_ref(),
7655 BreakpointPromptEditAction::Condition,
7656 window,
7657 cx,
7658 );
7659 })
7660 .log_err();
7661 }
7662 })
7663 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7664 weak_editor
7665 .update(cx, |this, cx| {
7666 this.add_edit_breakpoint_block(
7667 anchor,
7668 breakpoint.as_ref(),
7669 BreakpointPromptEditAction::HitCondition,
7670 window,
7671 cx,
7672 );
7673 })
7674 .log_err();
7675 })
7676 })
7677 }
7678
7679 fn render_breakpoint(
7680 &self,
7681 position: Anchor,
7682 row: DisplayRow,
7683 breakpoint: &Breakpoint,
7684 state: Option<BreakpointSessionState>,
7685 cx: &mut Context<Self>,
7686 ) -> IconButton {
7687 let is_rejected = state.is_some_and(|s| !s.verified);
7688 // Is it a breakpoint that shows up when hovering over gutter?
7689 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7690 (false, false),
7691 |PhantomBreakpointIndicator {
7692 is_active,
7693 display_row,
7694 collides_with_existing_breakpoint,
7695 }| {
7696 (
7697 is_active && display_row == row,
7698 collides_with_existing_breakpoint,
7699 )
7700 },
7701 );
7702
7703 let (color, icon) = {
7704 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7705 (false, false) => ui::IconName::DebugBreakpoint,
7706 (true, false) => ui::IconName::DebugLogBreakpoint,
7707 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7708 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7709 };
7710
7711 let color = if is_phantom {
7712 Color::Hint
7713 } else if is_rejected {
7714 Color::Disabled
7715 } else {
7716 Color::Debugger
7717 };
7718
7719 (color, icon)
7720 };
7721
7722 let breakpoint = Arc::from(breakpoint.clone());
7723
7724 let alt_as_text = gpui::Keystroke {
7725 modifiers: Modifiers::secondary_key(),
7726 ..Default::default()
7727 };
7728 let primary_action_text = if breakpoint.is_disabled() {
7729 "Enable breakpoint"
7730 } else if is_phantom && !collides_with_existing {
7731 "Set breakpoint"
7732 } else {
7733 "Unset breakpoint"
7734 };
7735 let focus_handle = self.focus_handle.clone();
7736
7737 let meta = if is_rejected {
7738 SharedString::from("No executable code is associated with this line.")
7739 } else if collides_with_existing && !breakpoint.is_disabled() {
7740 SharedString::from(format!(
7741 "{alt_as_text}-click to disable,\nright-click for more options."
7742 ))
7743 } else {
7744 SharedString::from("Right-click for more options.")
7745 };
7746 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7747 .icon_size(IconSize::XSmall)
7748 .size(ui::ButtonSize::None)
7749 .when(is_rejected, |this| {
7750 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7751 })
7752 .icon_color(color)
7753 .style(ButtonStyle::Transparent)
7754 .on_click(cx.listener({
7755 let breakpoint = breakpoint.clone();
7756
7757 move |editor, event: &ClickEvent, window, cx| {
7758 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7759 BreakpointEditAction::InvertState
7760 } else {
7761 BreakpointEditAction::Toggle
7762 };
7763
7764 window.focus(&editor.focus_handle(cx));
7765 editor.edit_breakpoint_at_anchor(
7766 position,
7767 breakpoint.as_ref().clone(),
7768 edit_action,
7769 cx,
7770 );
7771 }
7772 }))
7773 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7774 editor.set_breakpoint_context_menu(
7775 row,
7776 Some(position),
7777 event.down.position,
7778 window,
7779 cx,
7780 );
7781 }))
7782 .tooltip(move |window, cx| {
7783 Tooltip::with_meta_in(
7784 primary_action_text,
7785 Some(&ToggleBreakpoint),
7786 meta.clone(),
7787 &focus_handle,
7788 window,
7789 cx,
7790 )
7791 })
7792 }
7793
7794 fn build_tasks_context(
7795 project: &Entity<Project>,
7796 buffer: &Entity<Buffer>,
7797 buffer_row: u32,
7798 tasks: &Arc<RunnableTasks>,
7799 cx: &mut Context<Self>,
7800 ) -> Task<Option<task::TaskContext>> {
7801 let position = Point::new(buffer_row, tasks.column);
7802 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7803 let location = Location {
7804 buffer: buffer.clone(),
7805 range: range_start..range_start,
7806 };
7807 // Fill in the environmental variables from the tree-sitter captures
7808 let mut captured_task_variables = TaskVariables::default();
7809 for (capture_name, value) in tasks.extra_variables.clone() {
7810 captured_task_variables.insert(
7811 task::VariableName::Custom(capture_name.into()),
7812 value.clone(),
7813 );
7814 }
7815 project.update(cx, |project, cx| {
7816 project.task_store().update(cx, |task_store, cx| {
7817 task_store.task_context_for_location(captured_task_variables, location, cx)
7818 })
7819 })
7820 }
7821
7822 pub fn spawn_nearest_task(
7823 &mut self,
7824 action: &SpawnNearestTask,
7825 window: &mut Window,
7826 cx: &mut Context<Self>,
7827 ) {
7828 let Some((workspace, _)) = self.workspace.clone() else {
7829 return;
7830 };
7831 let Some(project) = self.project.clone() else {
7832 return;
7833 };
7834
7835 // Try to find a closest, enclosing node using tree-sitter that has a
7836 // task
7837 let Some((buffer, buffer_row, tasks)) = self
7838 .find_enclosing_node_task(cx)
7839 // Or find the task that's closest in row-distance.
7840 .or_else(|| self.find_closest_task(cx))
7841 else {
7842 return;
7843 };
7844
7845 let reveal_strategy = action.reveal;
7846 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7847 cx.spawn_in(window, async move |_, cx| {
7848 let context = task_context.await?;
7849 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7850
7851 let resolved = &mut resolved_task.resolved;
7852 resolved.reveal = reveal_strategy;
7853
7854 workspace
7855 .update_in(cx, |workspace, window, cx| {
7856 workspace.schedule_resolved_task(
7857 task_source_kind,
7858 resolved_task,
7859 false,
7860 window,
7861 cx,
7862 );
7863 })
7864 .ok()
7865 })
7866 .detach();
7867 }
7868
7869 fn find_closest_task(
7870 &mut self,
7871 cx: &mut Context<Self>,
7872 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7873 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7874
7875 let ((buffer_id, row), tasks) = self
7876 .tasks
7877 .iter()
7878 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7879
7880 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7881 let tasks = Arc::new(tasks.to_owned());
7882 Some((buffer, *row, tasks))
7883 }
7884
7885 fn find_enclosing_node_task(
7886 &mut self,
7887 cx: &mut Context<Self>,
7888 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7889 let snapshot = self.buffer.read(cx).snapshot(cx);
7890 let offset = self.selections.newest::<usize>(cx).head();
7891 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7892 let buffer_id = excerpt.buffer().remote_id();
7893
7894 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7895 let mut cursor = layer.node().walk();
7896
7897 while cursor.goto_first_child_for_byte(offset).is_some() {
7898 if cursor.node().end_byte() == offset {
7899 cursor.goto_next_sibling();
7900 }
7901 }
7902
7903 // Ascend to the smallest ancestor that contains the range and has a task.
7904 loop {
7905 let node = cursor.node();
7906 let node_range = node.byte_range();
7907 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7908
7909 // Check if this node contains our offset
7910 if node_range.start <= offset && node_range.end >= offset {
7911 // If it contains offset, check for task
7912 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7913 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7914 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7915 }
7916 }
7917
7918 if !cursor.goto_parent() {
7919 break;
7920 }
7921 }
7922 None
7923 }
7924
7925 fn render_run_indicator(
7926 &self,
7927 _style: &EditorStyle,
7928 is_active: bool,
7929 row: DisplayRow,
7930 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7931 cx: &mut Context<Self>,
7932 ) -> IconButton {
7933 let color = Color::Muted;
7934 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7935
7936 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7937 .shape(ui::IconButtonShape::Square)
7938 .icon_size(IconSize::XSmall)
7939 .icon_color(color)
7940 .toggle_state(is_active)
7941 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7942 let quick_launch = e.down.button == MouseButton::Left;
7943 window.focus(&editor.focus_handle(cx));
7944 editor.toggle_code_actions(
7945 &ToggleCodeActions {
7946 deployed_from: Some(CodeActionSource::RunMenu(row)),
7947 quick_launch,
7948 },
7949 window,
7950 cx,
7951 );
7952 }))
7953 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7954 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7955 }))
7956 }
7957
7958 pub fn context_menu_visible(&self) -> bool {
7959 !self.edit_prediction_preview_is_active()
7960 && self
7961 .context_menu
7962 .borrow()
7963 .as_ref()
7964 .map_or(false, |menu| menu.visible())
7965 }
7966
7967 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7968 self.context_menu
7969 .borrow()
7970 .as_ref()
7971 .map(|menu| menu.origin())
7972 }
7973
7974 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7975 self.context_menu_options = Some(options);
7976 }
7977
7978 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7979 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7980
7981 fn render_edit_prediction_popover(
7982 &mut self,
7983 text_bounds: &Bounds<Pixels>,
7984 content_origin: gpui::Point<Pixels>,
7985 right_margin: Pixels,
7986 editor_snapshot: &EditorSnapshot,
7987 visible_row_range: Range<DisplayRow>,
7988 scroll_top: f32,
7989 scroll_bottom: f32,
7990 line_layouts: &[LineWithInvisibles],
7991 line_height: Pixels,
7992 scroll_pixel_position: gpui::Point<Pixels>,
7993 newest_selection_head: Option<DisplayPoint>,
7994 editor_width: Pixels,
7995 style: &EditorStyle,
7996 window: &mut Window,
7997 cx: &mut App,
7998 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7999 if self.mode().is_minimap() {
8000 return None;
8001 }
8002 let active_inline_completion = self.active_inline_completion.as_ref()?;
8003
8004 if self.edit_prediction_visible_in_cursor_popover(true) {
8005 return None;
8006 }
8007
8008 match &active_inline_completion.completion {
8009 InlineCompletion::Move { target, .. } => {
8010 let target_display_point = target.to_display_point(editor_snapshot);
8011
8012 if self.edit_prediction_requires_modifier() {
8013 if !self.edit_prediction_preview_is_active() {
8014 return None;
8015 }
8016
8017 self.render_edit_prediction_modifier_jump_popover(
8018 text_bounds,
8019 content_origin,
8020 visible_row_range,
8021 line_layouts,
8022 line_height,
8023 scroll_pixel_position,
8024 newest_selection_head,
8025 target_display_point,
8026 window,
8027 cx,
8028 )
8029 } else {
8030 self.render_edit_prediction_eager_jump_popover(
8031 text_bounds,
8032 content_origin,
8033 editor_snapshot,
8034 visible_row_range,
8035 scroll_top,
8036 scroll_bottom,
8037 line_height,
8038 scroll_pixel_position,
8039 target_display_point,
8040 editor_width,
8041 window,
8042 cx,
8043 )
8044 }
8045 }
8046 InlineCompletion::Edit {
8047 display_mode: EditDisplayMode::Inline,
8048 ..
8049 } => None,
8050 InlineCompletion::Edit {
8051 display_mode: EditDisplayMode::TabAccept,
8052 edits,
8053 ..
8054 } => {
8055 let range = &edits.first()?.0;
8056 let target_display_point = range.end.to_display_point(editor_snapshot);
8057
8058 self.render_edit_prediction_end_of_line_popover(
8059 "Accept",
8060 editor_snapshot,
8061 visible_row_range,
8062 target_display_point,
8063 line_height,
8064 scroll_pixel_position,
8065 content_origin,
8066 editor_width,
8067 window,
8068 cx,
8069 )
8070 }
8071 InlineCompletion::Edit {
8072 edits,
8073 edit_preview,
8074 display_mode: EditDisplayMode::DiffPopover,
8075 snapshot,
8076 } => self.render_edit_prediction_diff_popover(
8077 text_bounds,
8078 content_origin,
8079 right_margin,
8080 editor_snapshot,
8081 visible_row_range,
8082 line_layouts,
8083 line_height,
8084 scroll_pixel_position,
8085 newest_selection_head,
8086 editor_width,
8087 style,
8088 edits,
8089 edit_preview,
8090 snapshot,
8091 window,
8092 cx,
8093 ),
8094 }
8095 }
8096
8097 fn render_edit_prediction_modifier_jump_popover(
8098 &mut self,
8099 text_bounds: &Bounds<Pixels>,
8100 content_origin: gpui::Point<Pixels>,
8101 visible_row_range: Range<DisplayRow>,
8102 line_layouts: &[LineWithInvisibles],
8103 line_height: Pixels,
8104 scroll_pixel_position: gpui::Point<Pixels>,
8105 newest_selection_head: Option<DisplayPoint>,
8106 target_display_point: DisplayPoint,
8107 window: &mut Window,
8108 cx: &mut App,
8109 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8110 let scrolled_content_origin =
8111 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8112
8113 const SCROLL_PADDING_Y: Pixels = px(12.);
8114
8115 if target_display_point.row() < visible_row_range.start {
8116 return self.render_edit_prediction_scroll_popover(
8117 |_| SCROLL_PADDING_Y,
8118 IconName::ArrowUp,
8119 visible_row_range,
8120 line_layouts,
8121 newest_selection_head,
8122 scrolled_content_origin,
8123 window,
8124 cx,
8125 );
8126 } else if target_display_point.row() >= visible_row_range.end {
8127 return self.render_edit_prediction_scroll_popover(
8128 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8129 IconName::ArrowDown,
8130 visible_row_range,
8131 line_layouts,
8132 newest_selection_head,
8133 scrolled_content_origin,
8134 window,
8135 cx,
8136 );
8137 }
8138
8139 const POLE_WIDTH: Pixels = px(2.);
8140
8141 let line_layout =
8142 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8143 let target_column = target_display_point.column() as usize;
8144
8145 let target_x = line_layout.x_for_index(target_column);
8146 let target_y =
8147 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8148
8149 let flag_on_right = target_x < text_bounds.size.width / 2.;
8150
8151 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8152 border_color.l += 0.001;
8153
8154 let mut element = v_flex()
8155 .items_end()
8156 .when(flag_on_right, |el| el.items_start())
8157 .child(if flag_on_right {
8158 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8159 .rounded_bl(px(0.))
8160 .rounded_tl(px(0.))
8161 .border_l_2()
8162 .border_color(border_color)
8163 } else {
8164 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8165 .rounded_br(px(0.))
8166 .rounded_tr(px(0.))
8167 .border_r_2()
8168 .border_color(border_color)
8169 })
8170 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8171 .into_any();
8172
8173 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8174
8175 let mut origin = scrolled_content_origin + point(target_x, target_y)
8176 - point(
8177 if flag_on_right {
8178 POLE_WIDTH
8179 } else {
8180 size.width - POLE_WIDTH
8181 },
8182 size.height - line_height,
8183 );
8184
8185 origin.x = origin.x.max(content_origin.x);
8186
8187 element.prepaint_at(origin, window, cx);
8188
8189 Some((element, origin))
8190 }
8191
8192 fn render_edit_prediction_scroll_popover(
8193 &mut self,
8194 to_y: impl Fn(Size<Pixels>) -> Pixels,
8195 scroll_icon: IconName,
8196 visible_row_range: Range<DisplayRow>,
8197 line_layouts: &[LineWithInvisibles],
8198 newest_selection_head: Option<DisplayPoint>,
8199 scrolled_content_origin: gpui::Point<Pixels>,
8200 window: &mut Window,
8201 cx: &mut App,
8202 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8203 let mut element = self
8204 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8205 .into_any();
8206
8207 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8208
8209 let cursor = newest_selection_head?;
8210 let cursor_row_layout =
8211 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8212 let cursor_column = cursor.column() as usize;
8213
8214 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8215
8216 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8217
8218 element.prepaint_at(origin, window, cx);
8219 Some((element, origin))
8220 }
8221
8222 fn render_edit_prediction_eager_jump_popover(
8223 &mut self,
8224 text_bounds: &Bounds<Pixels>,
8225 content_origin: gpui::Point<Pixels>,
8226 editor_snapshot: &EditorSnapshot,
8227 visible_row_range: Range<DisplayRow>,
8228 scroll_top: f32,
8229 scroll_bottom: f32,
8230 line_height: Pixels,
8231 scroll_pixel_position: gpui::Point<Pixels>,
8232 target_display_point: DisplayPoint,
8233 editor_width: Pixels,
8234 window: &mut Window,
8235 cx: &mut App,
8236 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8237 if target_display_point.row().as_f32() < scroll_top {
8238 let mut element = self
8239 .render_edit_prediction_line_popover(
8240 "Jump to Edit",
8241 Some(IconName::ArrowUp),
8242 window,
8243 cx,
8244 )?
8245 .into_any();
8246
8247 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8248 let offset = point(
8249 (text_bounds.size.width - size.width) / 2.,
8250 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8251 );
8252
8253 let origin = text_bounds.origin + offset;
8254 element.prepaint_at(origin, window, cx);
8255 Some((element, origin))
8256 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8257 let mut element = self
8258 .render_edit_prediction_line_popover(
8259 "Jump to Edit",
8260 Some(IconName::ArrowDown),
8261 window,
8262 cx,
8263 )?
8264 .into_any();
8265
8266 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8267 let offset = point(
8268 (text_bounds.size.width - size.width) / 2.,
8269 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8270 );
8271
8272 let origin = text_bounds.origin + offset;
8273 element.prepaint_at(origin, window, cx);
8274 Some((element, origin))
8275 } else {
8276 self.render_edit_prediction_end_of_line_popover(
8277 "Jump to Edit",
8278 editor_snapshot,
8279 visible_row_range,
8280 target_display_point,
8281 line_height,
8282 scroll_pixel_position,
8283 content_origin,
8284 editor_width,
8285 window,
8286 cx,
8287 )
8288 }
8289 }
8290
8291 fn render_edit_prediction_end_of_line_popover(
8292 self: &mut Editor,
8293 label: &'static str,
8294 editor_snapshot: &EditorSnapshot,
8295 visible_row_range: Range<DisplayRow>,
8296 target_display_point: DisplayPoint,
8297 line_height: Pixels,
8298 scroll_pixel_position: gpui::Point<Pixels>,
8299 content_origin: gpui::Point<Pixels>,
8300 editor_width: Pixels,
8301 window: &mut Window,
8302 cx: &mut App,
8303 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8304 let target_line_end = DisplayPoint::new(
8305 target_display_point.row(),
8306 editor_snapshot.line_len(target_display_point.row()),
8307 );
8308
8309 let mut element = self
8310 .render_edit_prediction_line_popover(label, None, window, cx)?
8311 .into_any();
8312
8313 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8314
8315 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8316
8317 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8318 let mut origin = start_point
8319 + line_origin
8320 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8321 origin.x = origin.x.max(content_origin.x);
8322
8323 let max_x = content_origin.x + editor_width - size.width;
8324
8325 if origin.x > max_x {
8326 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8327
8328 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8329 origin.y += offset;
8330 IconName::ArrowUp
8331 } else {
8332 origin.y -= offset;
8333 IconName::ArrowDown
8334 };
8335
8336 element = self
8337 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8338 .into_any();
8339
8340 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8341
8342 origin.x = content_origin.x + editor_width - size.width - px(2.);
8343 }
8344
8345 element.prepaint_at(origin, window, cx);
8346 Some((element, origin))
8347 }
8348
8349 fn render_edit_prediction_diff_popover(
8350 self: &Editor,
8351 text_bounds: &Bounds<Pixels>,
8352 content_origin: gpui::Point<Pixels>,
8353 right_margin: Pixels,
8354 editor_snapshot: &EditorSnapshot,
8355 visible_row_range: Range<DisplayRow>,
8356 line_layouts: &[LineWithInvisibles],
8357 line_height: Pixels,
8358 scroll_pixel_position: gpui::Point<Pixels>,
8359 newest_selection_head: Option<DisplayPoint>,
8360 editor_width: Pixels,
8361 style: &EditorStyle,
8362 edits: &Vec<(Range<Anchor>, String)>,
8363 edit_preview: &Option<language::EditPreview>,
8364 snapshot: &language::BufferSnapshot,
8365 window: &mut Window,
8366 cx: &mut App,
8367 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8368 let edit_start = edits
8369 .first()
8370 .unwrap()
8371 .0
8372 .start
8373 .to_display_point(editor_snapshot);
8374 let edit_end = edits
8375 .last()
8376 .unwrap()
8377 .0
8378 .end
8379 .to_display_point(editor_snapshot);
8380
8381 let is_visible = visible_row_range.contains(&edit_start.row())
8382 || visible_row_range.contains(&edit_end.row());
8383 if !is_visible {
8384 return None;
8385 }
8386
8387 let highlighted_edits =
8388 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8389
8390 let styled_text = highlighted_edits.to_styled_text(&style.text);
8391 let line_count = highlighted_edits.text.lines().count();
8392
8393 const BORDER_WIDTH: Pixels = px(1.);
8394
8395 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8396 let has_keybind = keybind.is_some();
8397
8398 let mut element = h_flex()
8399 .items_start()
8400 .child(
8401 h_flex()
8402 .bg(cx.theme().colors().editor_background)
8403 .border(BORDER_WIDTH)
8404 .shadow_sm()
8405 .border_color(cx.theme().colors().border)
8406 .rounded_l_lg()
8407 .when(line_count > 1, |el| el.rounded_br_lg())
8408 .pr_1()
8409 .child(styled_text),
8410 )
8411 .child(
8412 h_flex()
8413 .h(line_height + BORDER_WIDTH * 2.)
8414 .px_1p5()
8415 .gap_1()
8416 // Workaround: For some reason, there's a gap if we don't do this
8417 .ml(-BORDER_WIDTH)
8418 .shadow(vec![gpui::BoxShadow {
8419 color: gpui::black().opacity(0.05),
8420 offset: point(px(1.), px(1.)),
8421 blur_radius: px(2.),
8422 spread_radius: px(0.),
8423 }])
8424 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8425 .border(BORDER_WIDTH)
8426 .border_color(cx.theme().colors().border)
8427 .rounded_r_lg()
8428 .id("edit_prediction_diff_popover_keybind")
8429 .when(!has_keybind, |el| {
8430 let status_colors = cx.theme().status();
8431
8432 el.bg(status_colors.error_background)
8433 .border_color(status_colors.error.opacity(0.6))
8434 .child(Icon::new(IconName::Info).color(Color::Error))
8435 .cursor_default()
8436 .hoverable_tooltip(move |_window, cx| {
8437 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8438 })
8439 })
8440 .children(keybind),
8441 )
8442 .into_any();
8443
8444 let longest_row =
8445 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8446 let longest_line_width = if visible_row_range.contains(&longest_row) {
8447 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8448 } else {
8449 layout_line(
8450 longest_row,
8451 editor_snapshot,
8452 style,
8453 editor_width,
8454 |_| false,
8455 window,
8456 cx,
8457 )
8458 .width
8459 };
8460
8461 let viewport_bounds =
8462 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8463 right: -right_margin,
8464 ..Default::default()
8465 });
8466
8467 let x_after_longest =
8468 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8469 - scroll_pixel_position.x;
8470
8471 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8472
8473 // Fully visible if it can be displayed within the window (allow overlapping other
8474 // panes). However, this is only allowed if the popover starts within text_bounds.
8475 let can_position_to_the_right = x_after_longest < text_bounds.right()
8476 && x_after_longest + element_bounds.width < viewport_bounds.right();
8477
8478 let mut origin = if can_position_to_the_right {
8479 point(
8480 x_after_longest,
8481 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8482 - scroll_pixel_position.y,
8483 )
8484 } else {
8485 let cursor_row = newest_selection_head.map(|head| head.row());
8486 let above_edit = edit_start
8487 .row()
8488 .0
8489 .checked_sub(line_count as u32)
8490 .map(DisplayRow);
8491 let below_edit = Some(edit_end.row() + 1);
8492 let above_cursor =
8493 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8494 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8495
8496 // Place the edit popover adjacent to the edit if there is a location
8497 // available that is onscreen and does not obscure the cursor. Otherwise,
8498 // place it adjacent to the cursor.
8499 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8500 .into_iter()
8501 .flatten()
8502 .find(|&start_row| {
8503 let end_row = start_row + line_count as u32;
8504 visible_row_range.contains(&start_row)
8505 && visible_row_range.contains(&end_row)
8506 && cursor_row.map_or(true, |cursor_row| {
8507 !((start_row..end_row).contains(&cursor_row))
8508 })
8509 })?;
8510
8511 content_origin
8512 + point(
8513 -scroll_pixel_position.x,
8514 row_target.as_f32() * line_height - scroll_pixel_position.y,
8515 )
8516 };
8517
8518 origin.x -= BORDER_WIDTH;
8519
8520 window.defer_draw(element, origin, 1);
8521
8522 // Do not return an element, since it will already be drawn due to defer_draw.
8523 None
8524 }
8525
8526 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8527 px(30.)
8528 }
8529
8530 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8531 if self.read_only(cx) {
8532 cx.theme().players().read_only()
8533 } else {
8534 self.style.as_ref().unwrap().local_player
8535 }
8536 }
8537
8538 fn render_edit_prediction_accept_keybind(
8539 &self,
8540 window: &mut Window,
8541 cx: &App,
8542 ) -> Option<AnyElement> {
8543 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8544 let accept_keystroke = accept_binding.keystroke()?;
8545
8546 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8547
8548 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8549 Color::Accent
8550 } else {
8551 Color::Muted
8552 };
8553
8554 h_flex()
8555 .px_0p5()
8556 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8557 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8558 .text_size(TextSize::XSmall.rems(cx))
8559 .child(h_flex().children(ui::render_modifiers(
8560 &accept_keystroke.modifiers,
8561 PlatformStyle::platform(),
8562 Some(modifiers_color),
8563 Some(IconSize::XSmall.rems().into()),
8564 true,
8565 )))
8566 .when(is_platform_style_mac, |parent| {
8567 parent.child(accept_keystroke.key.clone())
8568 })
8569 .when(!is_platform_style_mac, |parent| {
8570 parent.child(
8571 Key::new(
8572 util::capitalize(&accept_keystroke.key),
8573 Some(Color::Default),
8574 )
8575 .size(Some(IconSize::XSmall.rems().into())),
8576 )
8577 })
8578 .into_any()
8579 .into()
8580 }
8581
8582 fn render_edit_prediction_line_popover(
8583 &self,
8584 label: impl Into<SharedString>,
8585 icon: Option<IconName>,
8586 window: &mut Window,
8587 cx: &App,
8588 ) -> Option<Stateful<Div>> {
8589 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8590
8591 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8592 let has_keybind = keybind.is_some();
8593
8594 let result = h_flex()
8595 .id("ep-line-popover")
8596 .py_0p5()
8597 .pl_1()
8598 .pr(padding_right)
8599 .gap_1()
8600 .rounded_md()
8601 .border_1()
8602 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8603 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8604 .shadow_sm()
8605 .when(!has_keybind, |el| {
8606 let status_colors = cx.theme().status();
8607
8608 el.bg(status_colors.error_background)
8609 .border_color(status_colors.error.opacity(0.6))
8610 .pl_2()
8611 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8612 .cursor_default()
8613 .hoverable_tooltip(move |_window, cx| {
8614 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8615 })
8616 })
8617 .children(keybind)
8618 .child(
8619 Label::new(label)
8620 .size(LabelSize::Small)
8621 .when(!has_keybind, |el| {
8622 el.color(cx.theme().status().error.into()).strikethrough()
8623 }),
8624 )
8625 .when(!has_keybind, |el| {
8626 el.child(
8627 h_flex().ml_1().child(
8628 Icon::new(IconName::Info)
8629 .size(IconSize::Small)
8630 .color(cx.theme().status().error.into()),
8631 ),
8632 )
8633 })
8634 .when_some(icon, |element, icon| {
8635 element.child(
8636 div()
8637 .mt(px(1.5))
8638 .child(Icon::new(icon).size(IconSize::Small)),
8639 )
8640 });
8641
8642 Some(result)
8643 }
8644
8645 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8646 let accent_color = cx.theme().colors().text_accent;
8647 let editor_bg_color = cx.theme().colors().editor_background;
8648 editor_bg_color.blend(accent_color.opacity(0.1))
8649 }
8650
8651 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8652 let accent_color = cx.theme().colors().text_accent;
8653 let editor_bg_color = cx.theme().colors().editor_background;
8654 editor_bg_color.blend(accent_color.opacity(0.6))
8655 }
8656
8657 fn render_edit_prediction_cursor_popover(
8658 &self,
8659 min_width: Pixels,
8660 max_width: Pixels,
8661 cursor_point: Point,
8662 style: &EditorStyle,
8663 accept_keystroke: Option<&gpui::Keystroke>,
8664 _window: &Window,
8665 cx: &mut Context<Editor>,
8666 ) -> Option<AnyElement> {
8667 let provider = self.edit_prediction_provider.as_ref()?;
8668
8669 if provider.provider.needs_terms_acceptance(cx) {
8670 return Some(
8671 h_flex()
8672 .min_w(min_width)
8673 .flex_1()
8674 .px_2()
8675 .py_1()
8676 .gap_3()
8677 .elevation_2(cx)
8678 .hover(|style| style.bg(cx.theme().colors().element_hover))
8679 .id("accept-terms")
8680 .cursor_pointer()
8681 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8682 .on_click(cx.listener(|this, _event, window, cx| {
8683 cx.stop_propagation();
8684 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8685 window.dispatch_action(
8686 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8687 cx,
8688 );
8689 }))
8690 .child(
8691 h_flex()
8692 .flex_1()
8693 .gap_2()
8694 .child(Icon::new(IconName::ZedPredict))
8695 .child(Label::new("Accept Terms of Service"))
8696 .child(div().w_full())
8697 .child(
8698 Icon::new(IconName::ArrowUpRight)
8699 .color(Color::Muted)
8700 .size(IconSize::Small),
8701 )
8702 .into_any_element(),
8703 )
8704 .into_any(),
8705 );
8706 }
8707
8708 let is_refreshing = provider.provider.is_refreshing(cx);
8709
8710 fn pending_completion_container() -> Div {
8711 h_flex()
8712 .h_full()
8713 .flex_1()
8714 .gap_2()
8715 .child(Icon::new(IconName::ZedPredict))
8716 }
8717
8718 let completion = match &self.active_inline_completion {
8719 Some(prediction) => {
8720 if !self.has_visible_completions_menu() {
8721 const RADIUS: Pixels = px(6.);
8722 const BORDER_WIDTH: Pixels = px(1.);
8723
8724 return Some(
8725 h_flex()
8726 .elevation_2(cx)
8727 .border(BORDER_WIDTH)
8728 .border_color(cx.theme().colors().border)
8729 .when(accept_keystroke.is_none(), |el| {
8730 el.border_color(cx.theme().status().error)
8731 })
8732 .rounded(RADIUS)
8733 .rounded_tl(px(0.))
8734 .overflow_hidden()
8735 .child(div().px_1p5().child(match &prediction.completion {
8736 InlineCompletion::Move { target, snapshot } => {
8737 use text::ToPoint as _;
8738 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8739 {
8740 Icon::new(IconName::ZedPredictDown)
8741 } else {
8742 Icon::new(IconName::ZedPredictUp)
8743 }
8744 }
8745 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8746 }))
8747 .child(
8748 h_flex()
8749 .gap_1()
8750 .py_1()
8751 .px_2()
8752 .rounded_r(RADIUS - BORDER_WIDTH)
8753 .border_l_1()
8754 .border_color(cx.theme().colors().border)
8755 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8756 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8757 el.child(
8758 Label::new("Hold")
8759 .size(LabelSize::Small)
8760 .when(accept_keystroke.is_none(), |el| {
8761 el.strikethrough()
8762 })
8763 .line_height_style(LineHeightStyle::UiLabel),
8764 )
8765 })
8766 .id("edit_prediction_cursor_popover_keybind")
8767 .when(accept_keystroke.is_none(), |el| {
8768 let status_colors = cx.theme().status();
8769
8770 el.bg(status_colors.error_background)
8771 .border_color(status_colors.error.opacity(0.6))
8772 .child(Icon::new(IconName::Info).color(Color::Error))
8773 .cursor_default()
8774 .hoverable_tooltip(move |_window, cx| {
8775 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8776 .into()
8777 })
8778 })
8779 .when_some(
8780 accept_keystroke.as_ref(),
8781 |el, accept_keystroke| {
8782 el.child(h_flex().children(ui::render_modifiers(
8783 &accept_keystroke.modifiers,
8784 PlatformStyle::platform(),
8785 Some(Color::Default),
8786 Some(IconSize::XSmall.rems().into()),
8787 false,
8788 )))
8789 },
8790 ),
8791 )
8792 .into_any(),
8793 );
8794 }
8795
8796 self.render_edit_prediction_cursor_popover_preview(
8797 prediction,
8798 cursor_point,
8799 style,
8800 cx,
8801 )?
8802 }
8803
8804 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8805 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8806 stale_completion,
8807 cursor_point,
8808 style,
8809 cx,
8810 )?,
8811
8812 None => {
8813 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8814 }
8815 },
8816
8817 None => pending_completion_container().child(Label::new("No Prediction")),
8818 };
8819
8820 let completion = if is_refreshing {
8821 completion
8822 .with_animation(
8823 "loading-completion",
8824 Animation::new(Duration::from_secs(2))
8825 .repeat()
8826 .with_easing(pulsating_between(0.4, 0.8)),
8827 |label, delta| label.opacity(delta),
8828 )
8829 .into_any_element()
8830 } else {
8831 completion.into_any_element()
8832 };
8833
8834 let has_completion = self.active_inline_completion.is_some();
8835
8836 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8837 Some(
8838 h_flex()
8839 .min_w(min_width)
8840 .max_w(max_width)
8841 .flex_1()
8842 .elevation_2(cx)
8843 .border_color(cx.theme().colors().border)
8844 .child(
8845 div()
8846 .flex_1()
8847 .py_1()
8848 .px_2()
8849 .overflow_hidden()
8850 .child(completion),
8851 )
8852 .when_some(accept_keystroke, |el, accept_keystroke| {
8853 if !accept_keystroke.modifiers.modified() {
8854 return el;
8855 }
8856
8857 el.child(
8858 h_flex()
8859 .h_full()
8860 .border_l_1()
8861 .rounded_r_lg()
8862 .border_color(cx.theme().colors().border)
8863 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8864 .gap_1()
8865 .py_1()
8866 .px_2()
8867 .child(
8868 h_flex()
8869 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8870 .when(is_platform_style_mac, |parent| parent.gap_1())
8871 .child(h_flex().children(ui::render_modifiers(
8872 &accept_keystroke.modifiers,
8873 PlatformStyle::platform(),
8874 Some(if !has_completion {
8875 Color::Muted
8876 } else {
8877 Color::Default
8878 }),
8879 None,
8880 false,
8881 ))),
8882 )
8883 .child(Label::new("Preview").into_any_element())
8884 .opacity(if has_completion { 1.0 } else { 0.4 }),
8885 )
8886 })
8887 .into_any(),
8888 )
8889 }
8890
8891 fn render_edit_prediction_cursor_popover_preview(
8892 &self,
8893 completion: &InlineCompletionState,
8894 cursor_point: Point,
8895 style: &EditorStyle,
8896 cx: &mut Context<Editor>,
8897 ) -> Option<Div> {
8898 use text::ToPoint as _;
8899
8900 fn render_relative_row_jump(
8901 prefix: impl Into<String>,
8902 current_row: u32,
8903 target_row: u32,
8904 ) -> Div {
8905 let (row_diff, arrow) = if target_row < current_row {
8906 (current_row - target_row, IconName::ArrowUp)
8907 } else {
8908 (target_row - current_row, IconName::ArrowDown)
8909 };
8910
8911 h_flex()
8912 .child(
8913 Label::new(format!("{}{}", prefix.into(), row_diff))
8914 .color(Color::Muted)
8915 .size(LabelSize::Small),
8916 )
8917 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8918 }
8919
8920 match &completion.completion {
8921 InlineCompletion::Move {
8922 target, snapshot, ..
8923 } => Some(
8924 h_flex()
8925 .px_2()
8926 .gap_2()
8927 .flex_1()
8928 .child(
8929 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8930 Icon::new(IconName::ZedPredictDown)
8931 } else {
8932 Icon::new(IconName::ZedPredictUp)
8933 },
8934 )
8935 .child(Label::new("Jump to Edit")),
8936 ),
8937
8938 InlineCompletion::Edit {
8939 edits,
8940 edit_preview,
8941 snapshot,
8942 display_mode: _,
8943 } => {
8944 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8945
8946 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8947 &snapshot,
8948 &edits,
8949 edit_preview.as_ref()?,
8950 true,
8951 cx,
8952 )
8953 .first_line_preview();
8954
8955 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8956 .with_default_highlights(&style.text, highlighted_edits.highlights);
8957
8958 let preview = h_flex()
8959 .gap_1()
8960 .min_w_16()
8961 .child(styled_text)
8962 .when(has_more_lines, |parent| parent.child("…"));
8963
8964 let left = if first_edit_row != cursor_point.row {
8965 render_relative_row_jump("", cursor_point.row, first_edit_row)
8966 .into_any_element()
8967 } else {
8968 Icon::new(IconName::ZedPredict).into_any_element()
8969 };
8970
8971 Some(
8972 h_flex()
8973 .h_full()
8974 .flex_1()
8975 .gap_2()
8976 .pr_1()
8977 .overflow_x_hidden()
8978 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8979 .child(left)
8980 .child(preview),
8981 )
8982 }
8983 }
8984 }
8985
8986 pub fn render_context_menu(
8987 &self,
8988 style: &EditorStyle,
8989 max_height_in_lines: u32,
8990 window: &mut Window,
8991 cx: &mut Context<Editor>,
8992 ) -> Option<AnyElement> {
8993 let menu = self.context_menu.borrow();
8994 let menu = menu.as_ref()?;
8995 if !menu.visible() {
8996 return None;
8997 };
8998 Some(menu.render(style, max_height_in_lines, window, cx))
8999 }
9000
9001 fn render_context_menu_aside(
9002 &mut self,
9003 max_size: Size<Pixels>,
9004 window: &mut Window,
9005 cx: &mut Context<Editor>,
9006 ) -> Option<AnyElement> {
9007 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9008 if menu.visible() {
9009 menu.render_aside(max_size, window, cx)
9010 } else {
9011 None
9012 }
9013 })
9014 }
9015
9016 fn hide_context_menu(
9017 &mut self,
9018 window: &mut Window,
9019 cx: &mut Context<Self>,
9020 ) -> Option<CodeContextMenu> {
9021 cx.notify();
9022 self.completion_tasks.clear();
9023 let context_menu = self.context_menu.borrow_mut().take();
9024 self.stale_inline_completion_in_menu.take();
9025 self.update_visible_inline_completion(window, cx);
9026 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9027 if let Some(completion_provider) = &self.completion_provider {
9028 completion_provider.selection_changed(None, window, cx);
9029 }
9030 }
9031 context_menu
9032 }
9033
9034 fn show_snippet_choices(
9035 &mut self,
9036 choices: &Vec<String>,
9037 selection: Range<Anchor>,
9038 cx: &mut Context<Self>,
9039 ) {
9040 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9041 (Some(a), Some(b)) if a == b => a,
9042 _ => {
9043 log::error!("expected anchor range to have matching buffer IDs");
9044 return;
9045 }
9046 };
9047 let multi_buffer = self.buffer().read(cx);
9048 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9049 return;
9050 };
9051
9052 let id = post_inc(&mut self.next_completion_id);
9053 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9054 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9055 CompletionsMenu::new_snippet_choices(
9056 id,
9057 true,
9058 choices,
9059 selection,
9060 buffer,
9061 snippet_sort_order,
9062 ),
9063 ));
9064 }
9065
9066 pub fn insert_snippet(
9067 &mut self,
9068 insertion_ranges: &[Range<usize>],
9069 snippet: Snippet,
9070 window: &mut Window,
9071 cx: &mut Context<Self>,
9072 ) -> Result<()> {
9073 struct Tabstop<T> {
9074 is_end_tabstop: bool,
9075 ranges: Vec<Range<T>>,
9076 choices: Option<Vec<String>>,
9077 }
9078
9079 let tabstops = self.buffer.update(cx, |buffer, cx| {
9080 let snippet_text: Arc<str> = snippet.text.clone().into();
9081 let edits = insertion_ranges
9082 .iter()
9083 .cloned()
9084 .map(|range| (range, snippet_text.clone()));
9085 let autoindent_mode = AutoindentMode::Block {
9086 original_indent_columns: Vec::new(),
9087 };
9088 buffer.edit(edits, Some(autoindent_mode), cx);
9089
9090 let snapshot = &*buffer.read(cx);
9091 let snippet = &snippet;
9092 snippet
9093 .tabstops
9094 .iter()
9095 .map(|tabstop| {
9096 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9097 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9098 });
9099 let mut tabstop_ranges = tabstop
9100 .ranges
9101 .iter()
9102 .flat_map(|tabstop_range| {
9103 let mut delta = 0_isize;
9104 insertion_ranges.iter().map(move |insertion_range| {
9105 let insertion_start = insertion_range.start as isize + delta;
9106 delta +=
9107 snippet.text.len() as isize - insertion_range.len() as isize;
9108
9109 let start = ((insertion_start + tabstop_range.start) as usize)
9110 .min(snapshot.len());
9111 let end = ((insertion_start + tabstop_range.end) as usize)
9112 .min(snapshot.len());
9113 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9114 })
9115 })
9116 .collect::<Vec<_>>();
9117 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9118
9119 Tabstop {
9120 is_end_tabstop,
9121 ranges: tabstop_ranges,
9122 choices: tabstop.choices.clone(),
9123 }
9124 })
9125 .collect::<Vec<_>>()
9126 });
9127 if let Some(tabstop) = tabstops.first() {
9128 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9129 // Reverse order so that the first range is the newest created selection.
9130 // Completions will use it and autoscroll will prioritize it.
9131 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9132 });
9133
9134 if let Some(choices) = &tabstop.choices {
9135 if let Some(selection) = tabstop.ranges.first() {
9136 self.show_snippet_choices(choices, selection.clone(), cx)
9137 }
9138 }
9139
9140 // If we're already at the last tabstop and it's at the end of the snippet,
9141 // we're done, we don't need to keep the state around.
9142 if !tabstop.is_end_tabstop {
9143 let choices = tabstops
9144 .iter()
9145 .map(|tabstop| tabstop.choices.clone())
9146 .collect();
9147
9148 let ranges = tabstops
9149 .into_iter()
9150 .map(|tabstop| tabstop.ranges)
9151 .collect::<Vec<_>>();
9152
9153 self.snippet_stack.push(SnippetState {
9154 active_index: 0,
9155 ranges,
9156 choices,
9157 });
9158 }
9159
9160 // Check whether the just-entered snippet ends with an auto-closable bracket.
9161 if self.autoclose_regions.is_empty() {
9162 let snapshot = self.buffer.read(cx).snapshot(cx);
9163 for selection in &mut self.selections.all::<Point>(cx) {
9164 let selection_head = selection.head();
9165 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9166 continue;
9167 };
9168
9169 let mut bracket_pair = None;
9170 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9171 let prev_chars = snapshot
9172 .reversed_chars_at(selection_head)
9173 .collect::<String>();
9174 for (pair, enabled) in scope.brackets() {
9175 if enabled
9176 && pair.close
9177 && prev_chars.starts_with(pair.start.as_str())
9178 && next_chars.starts_with(pair.end.as_str())
9179 {
9180 bracket_pair = Some(pair.clone());
9181 break;
9182 }
9183 }
9184 if let Some(pair) = bracket_pair {
9185 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9186 let autoclose_enabled =
9187 self.use_autoclose && snapshot_settings.use_autoclose;
9188 if autoclose_enabled {
9189 let start = snapshot.anchor_after(selection_head);
9190 let end = snapshot.anchor_after(selection_head);
9191 self.autoclose_regions.push(AutocloseRegion {
9192 selection_id: selection.id,
9193 range: start..end,
9194 pair,
9195 });
9196 }
9197 }
9198 }
9199 }
9200 }
9201 Ok(())
9202 }
9203
9204 pub fn move_to_next_snippet_tabstop(
9205 &mut self,
9206 window: &mut Window,
9207 cx: &mut Context<Self>,
9208 ) -> bool {
9209 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9210 }
9211
9212 pub fn move_to_prev_snippet_tabstop(
9213 &mut self,
9214 window: &mut Window,
9215 cx: &mut Context<Self>,
9216 ) -> bool {
9217 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9218 }
9219
9220 pub fn move_to_snippet_tabstop(
9221 &mut self,
9222 bias: Bias,
9223 window: &mut Window,
9224 cx: &mut Context<Self>,
9225 ) -> bool {
9226 if let Some(mut snippet) = self.snippet_stack.pop() {
9227 match bias {
9228 Bias::Left => {
9229 if snippet.active_index > 0 {
9230 snippet.active_index -= 1;
9231 } else {
9232 self.snippet_stack.push(snippet);
9233 return false;
9234 }
9235 }
9236 Bias::Right => {
9237 if snippet.active_index + 1 < snippet.ranges.len() {
9238 snippet.active_index += 1;
9239 } else {
9240 self.snippet_stack.push(snippet);
9241 return false;
9242 }
9243 }
9244 }
9245 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9246 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9247 // Reverse order so that the first range is the newest created selection.
9248 // Completions will use it and autoscroll will prioritize it.
9249 s.select_ranges(current_ranges.iter().rev().cloned())
9250 });
9251
9252 if let Some(choices) = &snippet.choices[snippet.active_index] {
9253 if let Some(selection) = current_ranges.first() {
9254 self.show_snippet_choices(&choices, selection.clone(), cx);
9255 }
9256 }
9257
9258 // If snippet state is not at the last tabstop, push it back on the stack
9259 if snippet.active_index + 1 < snippet.ranges.len() {
9260 self.snippet_stack.push(snippet);
9261 }
9262 return true;
9263 }
9264 }
9265
9266 false
9267 }
9268
9269 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9270 self.transact(window, cx, |this, window, cx| {
9271 this.select_all(&SelectAll, window, cx);
9272 this.insert("", window, cx);
9273 });
9274 }
9275
9276 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9277 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9278 self.transact(window, cx, |this, window, cx| {
9279 this.select_autoclose_pair(window, cx);
9280 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9281 if !this.linked_edit_ranges.is_empty() {
9282 let selections = this.selections.all::<MultiBufferPoint>(cx);
9283 let snapshot = this.buffer.read(cx).snapshot(cx);
9284
9285 for selection in selections.iter() {
9286 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9287 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9288 if selection_start.buffer_id != selection_end.buffer_id {
9289 continue;
9290 }
9291 if let Some(ranges) =
9292 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9293 {
9294 for (buffer, entries) in ranges {
9295 linked_ranges.entry(buffer).or_default().extend(entries);
9296 }
9297 }
9298 }
9299 }
9300
9301 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9302 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9303 for selection in &mut selections {
9304 if selection.is_empty() {
9305 let old_head = selection.head();
9306 let mut new_head =
9307 movement::left(&display_map, old_head.to_display_point(&display_map))
9308 .to_point(&display_map);
9309 if let Some((buffer, line_buffer_range)) = display_map
9310 .buffer_snapshot
9311 .buffer_line_for_row(MultiBufferRow(old_head.row))
9312 {
9313 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9314 let indent_len = match indent_size.kind {
9315 IndentKind::Space => {
9316 buffer.settings_at(line_buffer_range.start, cx).tab_size
9317 }
9318 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9319 };
9320 if old_head.column <= indent_size.len && old_head.column > 0 {
9321 let indent_len = indent_len.get();
9322 new_head = cmp::min(
9323 new_head,
9324 MultiBufferPoint::new(
9325 old_head.row,
9326 ((old_head.column - 1) / indent_len) * indent_len,
9327 ),
9328 );
9329 }
9330 }
9331
9332 selection.set_head(new_head, SelectionGoal::None);
9333 }
9334 }
9335
9336 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9337 s.select(selections)
9338 });
9339 this.insert("", window, cx);
9340 let empty_str: Arc<str> = Arc::from("");
9341 for (buffer, edits) in linked_ranges {
9342 let snapshot = buffer.read(cx).snapshot();
9343 use text::ToPoint as TP;
9344
9345 let edits = edits
9346 .into_iter()
9347 .map(|range| {
9348 let end_point = TP::to_point(&range.end, &snapshot);
9349 let mut start_point = TP::to_point(&range.start, &snapshot);
9350
9351 if end_point == start_point {
9352 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9353 .saturating_sub(1);
9354 start_point =
9355 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9356 };
9357
9358 (start_point..end_point, empty_str.clone())
9359 })
9360 .sorted_by_key(|(range, _)| range.start)
9361 .collect::<Vec<_>>();
9362 buffer.update(cx, |this, cx| {
9363 this.edit(edits, None, cx);
9364 })
9365 }
9366 this.refresh_inline_completion(true, false, window, cx);
9367 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9368 });
9369 }
9370
9371 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9372 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9373 self.transact(window, cx, |this, window, cx| {
9374 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9375 s.move_with(|map, selection| {
9376 if selection.is_empty() {
9377 let cursor = movement::right(map, selection.head());
9378 selection.end = cursor;
9379 selection.reversed = true;
9380 selection.goal = SelectionGoal::None;
9381 }
9382 })
9383 });
9384 this.insert("", window, cx);
9385 this.refresh_inline_completion(true, false, window, cx);
9386 });
9387 }
9388
9389 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9391 if self.move_to_prev_snippet_tabstop(window, cx) {
9392 return;
9393 }
9394 self.outdent(&Outdent, window, cx);
9395 }
9396
9397 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9398 if self.move_to_next_snippet_tabstop(window, cx) {
9399 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9400 return;
9401 }
9402 if self.read_only(cx) {
9403 return;
9404 }
9405 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9406 let mut selections = self.selections.all_adjusted(cx);
9407 let buffer = self.buffer.read(cx);
9408 let snapshot = buffer.snapshot(cx);
9409 let rows_iter = selections.iter().map(|s| s.head().row);
9410 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9411
9412 let has_some_cursor_in_whitespace = selections
9413 .iter()
9414 .filter(|selection| selection.is_empty())
9415 .any(|selection| {
9416 let cursor = selection.head();
9417 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9418 cursor.column < current_indent.len
9419 });
9420
9421 let mut edits = Vec::new();
9422 let mut prev_edited_row = 0;
9423 let mut row_delta = 0;
9424 for selection in &mut selections {
9425 if selection.start.row != prev_edited_row {
9426 row_delta = 0;
9427 }
9428 prev_edited_row = selection.end.row;
9429
9430 // If the selection is non-empty, then increase the indentation of the selected lines.
9431 if !selection.is_empty() {
9432 row_delta =
9433 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9434 continue;
9435 }
9436
9437 let cursor = selection.head();
9438 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9439 if let Some(suggested_indent) =
9440 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9441 {
9442 // Don't do anything if already at suggested indent
9443 // and there is any other cursor which is not
9444 if has_some_cursor_in_whitespace
9445 && cursor.column == current_indent.len
9446 && current_indent.len == suggested_indent.len
9447 {
9448 continue;
9449 }
9450
9451 // Adjust line and move cursor to suggested indent
9452 // if cursor is not at suggested indent
9453 if cursor.column < suggested_indent.len
9454 && cursor.column <= current_indent.len
9455 && current_indent.len <= suggested_indent.len
9456 {
9457 selection.start = Point::new(cursor.row, suggested_indent.len);
9458 selection.end = selection.start;
9459 if row_delta == 0 {
9460 edits.extend(Buffer::edit_for_indent_size_adjustment(
9461 cursor.row,
9462 current_indent,
9463 suggested_indent,
9464 ));
9465 row_delta = suggested_indent.len - current_indent.len;
9466 }
9467 continue;
9468 }
9469
9470 // If current indent is more than suggested indent
9471 // only move cursor to current indent and skip indent
9472 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9473 selection.start = Point::new(cursor.row, current_indent.len);
9474 selection.end = selection.start;
9475 continue;
9476 }
9477 }
9478
9479 // Otherwise, insert a hard or soft tab.
9480 let settings = buffer.language_settings_at(cursor, cx);
9481 let tab_size = if settings.hard_tabs {
9482 IndentSize::tab()
9483 } else {
9484 let tab_size = settings.tab_size.get();
9485 let indent_remainder = snapshot
9486 .text_for_range(Point::new(cursor.row, 0)..cursor)
9487 .flat_map(str::chars)
9488 .fold(row_delta % tab_size, |counter: u32, c| {
9489 if c == '\t' {
9490 0
9491 } else {
9492 (counter + 1) % tab_size
9493 }
9494 });
9495
9496 let chars_to_next_tab_stop = tab_size - indent_remainder;
9497 IndentSize::spaces(chars_to_next_tab_stop)
9498 };
9499 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9500 selection.end = selection.start;
9501 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9502 row_delta += tab_size.len;
9503 }
9504
9505 self.transact(window, cx, |this, window, cx| {
9506 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9507 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9508 s.select(selections)
9509 });
9510 this.refresh_inline_completion(true, false, window, cx);
9511 });
9512 }
9513
9514 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9515 if self.read_only(cx) {
9516 return;
9517 }
9518 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9519 let mut selections = self.selections.all::<Point>(cx);
9520 let mut prev_edited_row = 0;
9521 let mut row_delta = 0;
9522 let mut edits = Vec::new();
9523 let buffer = self.buffer.read(cx);
9524 let snapshot = buffer.snapshot(cx);
9525 for selection in &mut selections {
9526 if selection.start.row != prev_edited_row {
9527 row_delta = 0;
9528 }
9529 prev_edited_row = selection.end.row;
9530
9531 row_delta =
9532 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9533 }
9534
9535 self.transact(window, cx, |this, window, cx| {
9536 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9537 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9538 s.select(selections)
9539 });
9540 });
9541 }
9542
9543 fn indent_selection(
9544 buffer: &MultiBuffer,
9545 snapshot: &MultiBufferSnapshot,
9546 selection: &mut Selection<Point>,
9547 edits: &mut Vec<(Range<Point>, String)>,
9548 delta_for_start_row: u32,
9549 cx: &App,
9550 ) -> u32 {
9551 let settings = buffer.language_settings_at(selection.start, cx);
9552 let tab_size = settings.tab_size.get();
9553 let indent_kind = if settings.hard_tabs {
9554 IndentKind::Tab
9555 } else {
9556 IndentKind::Space
9557 };
9558 let mut start_row = selection.start.row;
9559 let mut end_row = selection.end.row + 1;
9560
9561 // If a selection ends at the beginning of a line, don't indent
9562 // that last line.
9563 if selection.end.column == 0 && selection.end.row > selection.start.row {
9564 end_row -= 1;
9565 }
9566
9567 // Avoid re-indenting a row that has already been indented by a
9568 // previous selection, but still update this selection's column
9569 // to reflect that indentation.
9570 if delta_for_start_row > 0 {
9571 start_row += 1;
9572 selection.start.column += delta_for_start_row;
9573 if selection.end.row == selection.start.row {
9574 selection.end.column += delta_for_start_row;
9575 }
9576 }
9577
9578 let mut delta_for_end_row = 0;
9579 let has_multiple_rows = start_row + 1 != end_row;
9580 for row in start_row..end_row {
9581 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9582 let indent_delta = match (current_indent.kind, indent_kind) {
9583 (IndentKind::Space, IndentKind::Space) => {
9584 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9585 IndentSize::spaces(columns_to_next_tab_stop)
9586 }
9587 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9588 (_, IndentKind::Tab) => IndentSize::tab(),
9589 };
9590
9591 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9592 0
9593 } else {
9594 selection.start.column
9595 };
9596 let row_start = Point::new(row, start);
9597 edits.push((
9598 row_start..row_start,
9599 indent_delta.chars().collect::<String>(),
9600 ));
9601
9602 // Update this selection's endpoints to reflect the indentation.
9603 if row == selection.start.row {
9604 selection.start.column += indent_delta.len;
9605 }
9606 if row == selection.end.row {
9607 selection.end.column += indent_delta.len;
9608 delta_for_end_row = indent_delta.len;
9609 }
9610 }
9611
9612 if selection.start.row == selection.end.row {
9613 delta_for_start_row + delta_for_end_row
9614 } else {
9615 delta_for_end_row
9616 }
9617 }
9618
9619 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9620 if self.read_only(cx) {
9621 return;
9622 }
9623 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9625 let selections = self.selections.all::<Point>(cx);
9626 let mut deletion_ranges = Vec::new();
9627 let mut last_outdent = None;
9628 {
9629 let buffer = self.buffer.read(cx);
9630 let snapshot = buffer.snapshot(cx);
9631 for selection in &selections {
9632 let settings = buffer.language_settings_at(selection.start, cx);
9633 let tab_size = settings.tab_size.get();
9634 let mut rows = selection.spanned_rows(false, &display_map);
9635
9636 // Avoid re-outdenting a row that has already been outdented by a
9637 // previous selection.
9638 if let Some(last_row) = last_outdent {
9639 if last_row == rows.start {
9640 rows.start = rows.start.next_row();
9641 }
9642 }
9643 let has_multiple_rows = rows.len() > 1;
9644 for row in rows.iter_rows() {
9645 let indent_size = snapshot.indent_size_for_line(row);
9646 if indent_size.len > 0 {
9647 let deletion_len = match indent_size.kind {
9648 IndentKind::Space => {
9649 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9650 if columns_to_prev_tab_stop == 0 {
9651 tab_size
9652 } else {
9653 columns_to_prev_tab_stop
9654 }
9655 }
9656 IndentKind::Tab => 1,
9657 };
9658 let start = if has_multiple_rows
9659 || deletion_len > selection.start.column
9660 || indent_size.len < selection.start.column
9661 {
9662 0
9663 } else {
9664 selection.start.column - deletion_len
9665 };
9666 deletion_ranges.push(
9667 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9668 );
9669 last_outdent = Some(row);
9670 }
9671 }
9672 }
9673 }
9674
9675 self.transact(window, cx, |this, window, cx| {
9676 this.buffer.update(cx, |buffer, cx| {
9677 let empty_str: Arc<str> = Arc::default();
9678 buffer.edit(
9679 deletion_ranges
9680 .into_iter()
9681 .map(|range| (range, empty_str.clone())),
9682 None,
9683 cx,
9684 );
9685 });
9686 let selections = this.selections.all::<usize>(cx);
9687 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9688 s.select(selections)
9689 });
9690 });
9691 }
9692
9693 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9694 if self.read_only(cx) {
9695 return;
9696 }
9697 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9698 let selections = self
9699 .selections
9700 .all::<usize>(cx)
9701 .into_iter()
9702 .map(|s| s.range());
9703
9704 self.transact(window, cx, |this, window, cx| {
9705 this.buffer.update(cx, |buffer, cx| {
9706 buffer.autoindent_ranges(selections, cx);
9707 });
9708 let selections = this.selections.all::<usize>(cx);
9709 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9710 s.select(selections)
9711 });
9712 });
9713 }
9714
9715 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9716 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9718 let selections = self.selections.all::<Point>(cx);
9719
9720 let mut new_cursors = Vec::new();
9721 let mut edit_ranges = Vec::new();
9722 let mut selections = selections.iter().peekable();
9723 while let Some(selection) = selections.next() {
9724 let mut rows = selection.spanned_rows(false, &display_map);
9725 let goal_display_column = selection.head().to_display_point(&display_map).column();
9726
9727 // Accumulate contiguous regions of rows that we want to delete.
9728 while let Some(next_selection) = selections.peek() {
9729 let next_rows = next_selection.spanned_rows(false, &display_map);
9730 if next_rows.start <= rows.end {
9731 rows.end = next_rows.end;
9732 selections.next().unwrap();
9733 } else {
9734 break;
9735 }
9736 }
9737
9738 let buffer = &display_map.buffer_snapshot;
9739 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9740 let edit_end;
9741 let cursor_buffer_row;
9742 if buffer.max_point().row >= rows.end.0 {
9743 // If there's a line after the range, delete the \n from the end of the row range
9744 // and position the cursor on the next line.
9745 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9746 cursor_buffer_row = rows.end;
9747 } else {
9748 // If there isn't a line after the range, delete the \n from the line before the
9749 // start of the row range and position the cursor there.
9750 edit_start = edit_start.saturating_sub(1);
9751 edit_end = buffer.len();
9752 cursor_buffer_row = rows.start.previous_row();
9753 }
9754
9755 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9756 *cursor.column_mut() =
9757 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9758
9759 new_cursors.push((
9760 selection.id,
9761 buffer.anchor_after(cursor.to_point(&display_map)),
9762 ));
9763 edit_ranges.push(edit_start..edit_end);
9764 }
9765
9766 self.transact(window, cx, |this, window, cx| {
9767 let buffer = this.buffer.update(cx, |buffer, cx| {
9768 let empty_str: Arc<str> = Arc::default();
9769 buffer.edit(
9770 edit_ranges
9771 .into_iter()
9772 .map(|range| (range, empty_str.clone())),
9773 None,
9774 cx,
9775 );
9776 buffer.snapshot(cx)
9777 });
9778 let new_selections = new_cursors
9779 .into_iter()
9780 .map(|(id, cursor)| {
9781 let cursor = cursor.to_point(&buffer);
9782 Selection {
9783 id,
9784 start: cursor,
9785 end: cursor,
9786 reversed: false,
9787 goal: SelectionGoal::None,
9788 }
9789 })
9790 .collect();
9791
9792 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9793 s.select(new_selections);
9794 });
9795 });
9796 }
9797
9798 pub fn join_lines_impl(
9799 &mut self,
9800 insert_whitespace: bool,
9801 window: &mut Window,
9802 cx: &mut Context<Self>,
9803 ) {
9804 if self.read_only(cx) {
9805 return;
9806 }
9807 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9808 for selection in self.selections.all::<Point>(cx) {
9809 let start = MultiBufferRow(selection.start.row);
9810 // Treat single line selections as if they include the next line. Otherwise this action
9811 // would do nothing for single line selections individual cursors.
9812 let end = if selection.start.row == selection.end.row {
9813 MultiBufferRow(selection.start.row + 1)
9814 } else {
9815 MultiBufferRow(selection.end.row)
9816 };
9817
9818 if let Some(last_row_range) = row_ranges.last_mut() {
9819 if start <= last_row_range.end {
9820 last_row_range.end = end;
9821 continue;
9822 }
9823 }
9824 row_ranges.push(start..end);
9825 }
9826
9827 let snapshot = self.buffer.read(cx).snapshot(cx);
9828 let mut cursor_positions = Vec::new();
9829 for row_range in &row_ranges {
9830 let anchor = snapshot.anchor_before(Point::new(
9831 row_range.end.previous_row().0,
9832 snapshot.line_len(row_range.end.previous_row()),
9833 ));
9834 cursor_positions.push(anchor..anchor);
9835 }
9836
9837 self.transact(window, cx, |this, window, cx| {
9838 for row_range in row_ranges.into_iter().rev() {
9839 for row in row_range.iter_rows().rev() {
9840 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9841 let next_line_row = row.next_row();
9842 let indent = snapshot.indent_size_for_line(next_line_row);
9843 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9844
9845 let replace =
9846 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9847 " "
9848 } else {
9849 ""
9850 };
9851
9852 this.buffer.update(cx, |buffer, cx| {
9853 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9854 });
9855 }
9856 }
9857
9858 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9859 s.select_anchor_ranges(cursor_positions)
9860 });
9861 });
9862 }
9863
9864 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9865 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9866 self.join_lines_impl(true, window, cx);
9867 }
9868
9869 pub fn sort_lines_case_sensitive(
9870 &mut self,
9871 _: &SortLinesCaseSensitive,
9872 window: &mut Window,
9873 cx: &mut Context<Self>,
9874 ) {
9875 self.manipulate_lines(window, cx, |lines| lines.sort())
9876 }
9877
9878 pub fn sort_lines_case_insensitive(
9879 &mut self,
9880 _: &SortLinesCaseInsensitive,
9881 window: &mut Window,
9882 cx: &mut Context<Self>,
9883 ) {
9884 self.manipulate_lines(window, cx, |lines| {
9885 lines.sort_by_key(|line| line.to_lowercase())
9886 })
9887 }
9888
9889 pub fn unique_lines_case_insensitive(
9890 &mut self,
9891 _: &UniqueLinesCaseInsensitive,
9892 window: &mut Window,
9893 cx: &mut Context<Self>,
9894 ) {
9895 self.manipulate_lines(window, cx, |lines| {
9896 let mut seen = HashSet::default();
9897 lines.retain(|line| seen.insert(line.to_lowercase()));
9898 })
9899 }
9900
9901 pub fn unique_lines_case_sensitive(
9902 &mut self,
9903 _: &UniqueLinesCaseSensitive,
9904 window: &mut Window,
9905 cx: &mut Context<Self>,
9906 ) {
9907 self.manipulate_lines(window, cx, |lines| {
9908 let mut seen = HashSet::default();
9909 lines.retain(|line| seen.insert(*line));
9910 })
9911 }
9912
9913 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9914 let Some(project) = self.project.clone() else {
9915 return;
9916 };
9917 self.reload(project, window, cx)
9918 .detach_and_notify_err(window, cx);
9919 }
9920
9921 pub fn restore_file(
9922 &mut self,
9923 _: &::git::RestoreFile,
9924 window: &mut Window,
9925 cx: &mut Context<Self>,
9926 ) {
9927 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9928 let mut buffer_ids = HashSet::default();
9929 let snapshot = self.buffer().read(cx).snapshot(cx);
9930 for selection in self.selections.all::<usize>(cx) {
9931 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9932 }
9933
9934 let buffer = self.buffer().read(cx);
9935 let ranges = buffer_ids
9936 .into_iter()
9937 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9938 .collect::<Vec<_>>();
9939
9940 self.restore_hunks_in_ranges(ranges, window, cx);
9941 }
9942
9943 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9944 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9945 let selections = self
9946 .selections
9947 .all(cx)
9948 .into_iter()
9949 .map(|s| s.range())
9950 .collect();
9951 self.restore_hunks_in_ranges(selections, window, cx);
9952 }
9953
9954 pub fn restore_hunks_in_ranges(
9955 &mut self,
9956 ranges: Vec<Range<Point>>,
9957 window: &mut Window,
9958 cx: &mut Context<Editor>,
9959 ) {
9960 let mut revert_changes = HashMap::default();
9961 let chunk_by = self
9962 .snapshot(window, cx)
9963 .hunks_for_ranges(ranges)
9964 .into_iter()
9965 .chunk_by(|hunk| hunk.buffer_id);
9966 for (buffer_id, hunks) in &chunk_by {
9967 let hunks = hunks.collect::<Vec<_>>();
9968 for hunk in &hunks {
9969 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9970 }
9971 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9972 }
9973 drop(chunk_by);
9974 if !revert_changes.is_empty() {
9975 self.transact(window, cx, |editor, window, cx| {
9976 editor.restore(revert_changes, window, cx);
9977 });
9978 }
9979 }
9980
9981 pub fn open_active_item_in_terminal(
9982 &mut self,
9983 _: &OpenInTerminal,
9984 window: &mut Window,
9985 cx: &mut Context<Self>,
9986 ) {
9987 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9988 let project_path = buffer.read(cx).project_path(cx)?;
9989 let project = self.project.as_ref()?.read(cx);
9990 let entry = project.entry_for_path(&project_path, cx)?;
9991 let parent = match &entry.canonical_path {
9992 Some(canonical_path) => canonical_path.to_path_buf(),
9993 None => project.absolute_path(&project_path, cx)?,
9994 }
9995 .parent()?
9996 .to_path_buf();
9997 Some(parent)
9998 }) {
9999 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10000 }
10001 }
10002
10003 fn set_breakpoint_context_menu(
10004 &mut self,
10005 display_row: DisplayRow,
10006 position: Option<Anchor>,
10007 clicked_point: gpui::Point<Pixels>,
10008 window: &mut Window,
10009 cx: &mut Context<Self>,
10010 ) {
10011 if !cx.has_flag::<DebuggerFeatureFlag>() {
10012 return;
10013 }
10014 let source = self
10015 .buffer
10016 .read(cx)
10017 .snapshot(cx)
10018 .anchor_before(Point::new(display_row.0, 0u32));
10019
10020 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10021
10022 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10023 self,
10024 source,
10025 clicked_point,
10026 context_menu,
10027 window,
10028 cx,
10029 );
10030 }
10031
10032 fn add_edit_breakpoint_block(
10033 &mut self,
10034 anchor: Anchor,
10035 breakpoint: &Breakpoint,
10036 edit_action: BreakpointPromptEditAction,
10037 window: &mut Window,
10038 cx: &mut Context<Self>,
10039 ) {
10040 let weak_editor = cx.weak_entity();
10041 let bp_prompt = cx.new(|cx| {
10042 BreakpointPromptEditor::new(
10043 weak_editor,
10044 anchor,
10045 breakpoint.clone(),
10046 edit_action,
10047 window,
10048 cx,
10049 )
10050 });
10051
10052 let height = bp_prompt.update(cx, |this, cx| {
10053 this.prompt
10054 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10055 });
10056 let cloned_prompt = bp_prompt.clone();
10057 let blocks = vec![BlockProperties {
10058 style: BlockStyle::Sticky,
10059 placement: BlockPlacement::Above(anchor),
10060 height: Some(height),
10061 render: Arc::new(move |cx| {
10062 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10063 cloned_prompt.clone().into_any_element()
10064 }),
10065 priority: 0,
10066 render_in_minimap: true,
10067 }];
10068
10069 let focus_handle = bp_prompt.focus_handle(cx);
10070 window.focus(&focus_handle);
10071
10072 let block_ids = self.insert_blocks(blocks, None, cx);
10073 bp_prompt.update(cx, |prompt, _| {
10074 prompt.add_block_ids(block_ids);
10075 });
10076 }
10077
10078 pub(crate) fn breakpoint_at_row(
10079 &self,
10080 row: u32,
10081 window: &mut Window,
10082 cx: &mut Context<Self>,
10083 ) -> Option<(Anchor, Breakpoint)> {
10084 let snapshot = self.snapshot(window, cx);
10085 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10086
10087 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10088 }
10089
10090 pub(crate) fn breakpoint_at_anchor(
10091 &self,
10092 breakpoint_position: Anchor,
10093 snapshot: &EditorSnapshot,
10094 cx: &mut Context<Self>,
10095 ) -> Option<(Anchor, Breakpoint)> {
10096 let project = self.project.clone()?;
10097
10098 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10099 snapshot
10100 .buffer_snapshot
10101 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10102 })?;
10103
10104 let enclosing_excerpt = breakpoint_position.excerpt_id;
10105 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10106 let buffer_snapshot = buffer.read(cx).snapshot();
10107
10108 let row = buffer_snapshot
10109 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10110 .row;
10111
10112 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10113 let anchor_end = snapshot
10114 .buffer_snapshot
10115 .anchor_after(Point::new(row, line_len));
10116
10117 let bp = self
10118 .breakpoint_store
10119 .as_ref()?
10120 .read_with(cx, |breakpoint_store, cx| {
10121 breakpoint_store
10122 .breakpoints(
10123 &buffer,
10124 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10125 &buffer_snapshot,
10126 cx,
10127 )
10128 .next()
10129 .and_then(|(bp, _)| {
10130 let breakpoint_row = buffer_snapshot
10131 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10132 .row;
10133
10134 if breakpoint_row == row {
10135 snapshot
10136 .buffer_snapshot
10137 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10138 .map(|position| (position, bp.bp.clone()))
10139 } else {
10140 None
10141 }
10142 })
10143 });
10144 bp
10145 }
10146
10147 pub fn edit_log_breakpoint(
10148 &mut self,
10149 _: &EditLogBreakpoint,
10150 window: &mut Window,
10151 cx: &mut Context<Self>,
10152 ) {
10153 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10154 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10155 message: None,
10156 state: BreakpointState::Enabled,
10157 condition: None,
10158 hit_condition: None,
10159 });
10160
10161 self.add_edit_breakpoint_block(
10162 anchor,
10163 &breakpoint,
10164 BreakpointPromptEditAction::Log,
10165 window,
10166 cx,
10167 );
10168 }
10169 }
10170
10171 fn breakpoints_at_cursors(
10172 &self,
10173 window: &mut Window,
10174 cx: &mut Context<Self>,
10175 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10176 let snapshot = self.snapshot(window, cx);
10177 let cursors = self
10178 .selections
10179 .disjoint_anchors()
10180 .into_iter()
10181 .map(|selection| {
10182 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10183
10184 let breakpoint_position = self
10185 .breakpoint_at_row(cursor_position.row, window, cx)
10186 .map(|bp| bp.0)
10187 .unwrap_or_else(|| {
10188 snapshot
10189 .display_snapshot
10190 .buffer_snapshot
10191 .anchor_after(Point::new(cursor_position.row, 0))
10192 });
10193
10194 let breakpoint = self
10195 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10196 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10197
10198 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10199 })
10200 // 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.
10201 .collect::<HashMap<Anchor, _>>();
10202
10203 cursors.into_iter().collect()
10204 }
10205
10206 pub fn enable_breakpoint(
10207 &mut self,
10208 _: &crate::actions::EnableBreakpoint,
10209 window: &mut Window,
10210 cx: &mut Context<Self>,
10211 ) {
10212 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10213 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10214 continue;
10215 };
10216 self.edit_breakpoint_at_anchor(
10217 anchor,
10218 breakpoint,
10219 BreakpointEditAction::InvertState,
10220 cx,
10221 );
10222 }
10223 }
10224
10225 pub fn disable_breakpoint(
10226 &mut self,
10227 _: &crate::actions::DisableBreakpoint,
10228 window: &mut Window,
10229 cx: &mut Context<Self>,
10230 ) {
10231 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10232 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10233 continue;
10234 };
10235 self.edit_breakpoint_at_anchor(
10236 anchor,
10237 breakpoint,
10238 BreakpointEditAction::InvertState,
10239 cx,
10240 );
10241 }
10242 }
10243
10244 pub fn toggle_breakpoint(
10245 &mut self,
10246 _: &crate::actions::ToggleBreakpoint,
10247 window: &mut Window,
10248 cx: &mut Context<Self>,
10249 ) {
10250 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10251 if let Some(breakpoint) = breakpoint {
10252 self.edit_breakpoint_at_anchor(
10253 anchor,
10254 breakpoint,
10255 BreakpointEditAction::Toggle,
10256 cx,
10257 );
10258 } else {
10259 self.edit_breakpoint_at_anchor(
10260 anchor,
10261 Breakpoint::new_standard(),
10262 BreakpointEditAction::Toggle,
10263 cx,
10264 );
10265 }
10266 }
10267 }
10268
10269 pub fn edit_breakpoint_at_anchor(
10270 &mut self,
10271 breakpoint_position: Anchor,
10272 breakpoint: Breakpoint,
10273 edit_action: BreakpointEditAction,
10274 cx: &mut Context<Self>,
10275 ) {
10276 let Some(breakpoint_store) = &self.breakpoint_store else {
10277 return;
10278 };
10279
10280 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10281 if breakpoint_position == Anchor::min() {
10282 self.buffer()
10283 .read(cx)
10284 .excerpt_buffer_ids()
10285 .into_iter()
10286 .next()
10287 } else {
10288 None
10289 }
10290 }) else {
10291 return;
10292 };
10293
10294 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10295 return;
10296 };
10297
10298 breakpoint_store.update(cx, |breakpoint_store, cx| {
10299 breakpoint_store.toggle_breakpoint(
10300 buffer,
10301 BreakpointWithPosition {
10302 position: breakpoint_position.text_anchor,
10303 bp: breakpoint,
10304 },
10305 edit_action,
10306 cx,
10307 );
10308 });
10309
10310 cx.notify();
10311 }
10312
10313 #[cfg(any(test, feature = "test-support"))]
10314 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10315 self.breakpoint_store.clone()
10316 }
10317
10318 pub fn prepare_restore_change(
10319 &self,
10320 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10321 hunk: &MultiBufferDiffHunk,
10322 cx: &mut App,
10323 ) -> Option<()> {
10324 if hunk.is_created_file() {
10325 return None;
10326 }
10327 let buffer = self.buffer.read(cx);
10328 let diff = buffer.diff_for(hunk.buffer_id)?;
10329 let buffer = buffer.buffer(hunk.buffer_id)?;
10330 let buffer = buffer.read(cx);
10331 let original_text = diff
10332 .read(cx)
10333 .base_text()
10334 .as_rope()
10335 .slice(hunk.diff_base_byte_range.clone());
10336 let buffer_snapshot = buffer.snapshot();
10337 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10338 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10339 probe
10340 .0
10341 .start
10342 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10343 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10344 }) {
10345 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10346 Some(())
10347 } else {
10348 None
10349 }
10350 }
10351
10352 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10353 self.manipulate_lines(window, cx, |lines| lines.reverse())
10354 }
10355
10356 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10357 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10358 }
10359
10360 fn manipulate_lines<Fn>(
10361 &mut self,
10362 window: &mut Window,
10363 cx: &mut Context<Self>,
10364 mut callback: Fn,
10365 ) where
10366 Fn: FnMut(&mut Vec<&str>),
10367 {
10368 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10369
10370 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10371 let buffer = self.buffer.read(cx).snapshot(cx);
10372
10373 let mut edits = Vec::new();
10374
10375 let selections = self.selections.all::<Point>(cx);
10376 let mut selections = selections.iter().peekable();
10377 let mut contiguous_row_selections = Vec::new();
10378 let mut new_selections = Vec::new();
10379 let mut added_lines = 0;
10380 let mut removed_lines = 0;
10381
10382 while let Some(selection) = selections.next() {
10383 let (start_row, end_row) = consume_contiguous_rows(
10384 &mut contiguous_row_selections,
10385 selection,
10386 &display_map,
10387 &mut selections,
10388 );
10389
10390 let start_point = Point::new(start_row.0, 0);
10391 let end_point = Point::new(
10392 end_row.previous_row().0,
10393 buffer.line_len(end_row.previous_row()),
10394 );
10395 let text = buffer
10396 .text_for_range(start_point..end_point)
10397 .collect::<String>();
10398
10399 let mut lines = text.split('\n').collect_vec();
10400
10401 let lines_before = lines.len();
10402 callback(&mut lines);
10403 let lines_after = lines.len();
10404
10405 edits.push((start_point..end_point, lines.join("\n")));
10406
10407 // Selections must change based on added and removed line count
10408 let start_row =
10409 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10410 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10411 new_selections.push(Selection {
10412 id: selection.id,
10413 start: start_row,
10414 end: end_row,
10415 goal: SelectionGoal::None,
10416 reversed: selection.reversed,
10417 });
10418
10419 if lines_after > lines_before {
10420 added_lines += lines_after - lines_before;
10421 } else if lines_before > lines_after {
10422 removed_lines += lines_before - lines_after;
10423 }
10424 }
10425
10426 self.transact(window, cx, |this, window, cx| {
10427 let buffer = this.buffer.update(cx, |buffer, cx| {
10428 buffer.edit(edits, None, cx);
10429 buffer.snapshot(cx)
10430 });
10431
10432 // Recalculate offsets on newly edited buffer
10433 let new_selections = new_selections
10434 .iter()
10435 .map(|s| {
10436 let start_point = Point::new(s.start.0, 0);
10437 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10438 Selection {
10439 id: s.id,
10440 start: buffer.point_to_offset(start_point),
10441 end: buffer.point_to_offset(end_point),
10442 goal: s.goal,
10443 reversed: s.reversed,
10444 }
10445 })
10446 .collect();
10447
10448 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10449 s.select(new_selections);
10450 });
10451
10452 this.request_autoscroll(Autoscroll::fit(), cx);
10453 });
10454 }
10455
10456 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10457 self.manipulate_text(window, cx, |text| {
10458 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10459 if has_upper_case_characters {
10460 text.to_lowercase()
10461 } else {
10462 text.to_uppercase()
10463 }
10464 })
10465 }
10466
10467 pub fn convert_to_upper_case(
10468 &mut self,
10469 _: &ConvertToUpperCase,
10470 window: &mut Window,
10471 cx: &mut Context<Self>,
10472 ) {
10473 self.manipulate_text(window, cx, |text| text.to_uppercase())
10474 }
10475
10476 pub fn convert_to_lower_case(
10477 &mut self,
10478 _: &ConvertToLowerCase,
10479 window: &mut Window,
10480 cx: &mut Context<Self>,
10481 ) {
10482 self.manipulate_text(window, cx, |text| text.to_lowercase())
10483 }
10484
10485 pub fn convert_to_title_case(
10486 &mut self,
10487 _: &ConvertToTitleCase,
10488 window: &mut Window,
10489 cx: &mut Context<Self>,
10490 ) {
10491 self.manipulate_text(window, cx, |text| {
10492 text.split('\n')
10493 .map(|line| line.to_case(Case::Title))
10494 .join("\n")
10495 })
10496 }
10497
10498 pub fn convert_to_snake_case(
10499 &mut self,
10500 _: &ConvertToSnakeCase,
10501 window: &mut Window,
10502 cx: &mut Context<Self>,
10503 ) {
10504 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10505 }
10506
10507 pub fn convert_to_kebab_case(
10508 &mut self,
10509 _: &ConvertToKebabCase,
10510 window: &mut Window,
10511 cx: &mut Context<Self>,
10512 ) {
10513 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10514 }
10515
10516 pub fn convert_to_upper_camel_case(
10517 &mut self,
10518 _: &ConvertToUpperCamelCase,
10519 window: &mut Window,
10520 cx: &mut Context<Self>,
10521 ) {
10522 self.manipulate_text(window, cx, |text| {
10523 text.split('\n')
10524 .map(|line| line.to_case(Case::UpperCamel))
10525 .join("\n")
10526 })
10527 }
10528
10529 pub fn convert_to_lower_camel_case(
10530 &mut self,
10531 _: &ConvertToLowerCamelCase,
10532 window: &mut Window,
10533 cx: &mut Context<Self>,
10534 ) {
10535 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10536 }
10537
10538 pub fn convert_to_opposite_case(
10539 &mut self,
10540 _: &ConvertToOppositeCase,
10541 window: &mut Window,
10542 cx: &mut Context<Self>,
10543 ) {
10544 self.manipulate_text(window, cx, |text| {
10545 text.chars()
10546 .fold(String::with_capacity(text.len()), |mut t, c| {
10547 if c.is_uppercase() {
10548 t.extend(c.to_lowercase());
10549 } else {
10550 t.extend(c.to_uppercase());
10551 }
10552 t
10553 })
10554 })
10555 }
10556
10557 pub fn convert_to_rot13(
10558 &mut self,
10559 _: &ConvertToRot13,
10560 window: &mut Window,
10561 cx: &mut Context<Self>,
10562 ) {
10563 self.manipulate_text(window, cx, |text| {
10564 text.chars()
10565 .map(|c| match c {
10566 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10567 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10568 _ => c,
10569 })
10570 .collect()
10571 })
10572 }
10573
10574 pub fn convert_to_rot47(
10575 &mut self,
10576 _: &ConvertToRot47,
10577 window: &mut Window,
10578 cx: &mut Context<Self>,
10579 ) {
10580 self.manipulate_text(window, cx, |text| {
10581 text.chars()
10582 .map(|c| {
10583 let code_point = c as u32;
10584 if code_point >= 33 && code_point <= 126 {
10585 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10586 }
10587 c
10588 })
10589 .collect()
10590 })
10591 }
10592
10593 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10594 where
10595 Fn: FnMut(&str) -> String,
10596 {
10597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10598 let buffer = self.buffer.read(cx).snapshot(cx);
10599
10600 let mut new_selections = Vec::new();
10601 let mut edits = Vec::new();
10602 let mut selection_adjustment = 0i32;
10603
10604 for selection in self.selections.all::<usize>(cx) {
10605 let selection_is_empty = selection.is_empty();
10606
10607 let (start, end) = if selection_is_empty {
10608 let word_range = movement::surrounding_word(
10609 &display_map,
10610 selection.start.to_display_point(&display_map),
10611 );
10612 let start = word_range.start.to_offset(&display_map, Bias::Left);
10613 let end = word_range.end.to_offset(&display_map, Bias::Left);
10614 (start, end)
10615 } else {
10616 (selection.start, selection.end)
10617 };
10618
10619 let text = buffer.text_for_range(start..end).collect::<String>();
10620 let old_length = text.len() as i32;
10621 let text = callback(&text);
10622
10623 new_selections.push(Selection {
10624 start: (start as i32 - selection_adjustment) as usize,
10625 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10626 goal: SelectionGoal::None,
10627 ..selection
10628 });
10629
10630 selection_adjustment += old_length - text.len() as i32;
10631
10632 edits.push((start..end, text));
10633 }
10634
10635 self.transact(window, cx, |this, window, cx| {
10636 this.buffer.update(cx, |buffer, cx| {
10637 buffer.edit(edits, None, cx);
10638 });
10639
10640 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10641 s.select(new_selections);
10642 });
10643
10644 this.request_autoscroll(Autoscroll::fit(), cx);
10645 });
10646 }
10647
10648 pub fn move_selection_on_drop(
10649 &mut self,
10650 selection: &Selection<Anchor>,
10651 target: DisplayPoint,
10652 is_cut: bool,
10653 window: &mut Window,
10654 cx: &mut Context<Self>,
10655 ) {
10656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10657 let buffer = &display_map.buffer_snapshot;
10658 let mut edits = Vec::new();
10659 let insert_point = display_map
10660 .clip_point(target, Bias::Left)
10661 .to_point(&display_map);
10662 let text = buffer
10663 .text_for_range(selection.start..selection.end)
10664 .collect::<String>();
10665 if is_cut {
10666 edits.push(((selection.start..selection.end), String::new()));
10667 }
10668 let insert_anchor = buffer.anchor_before(insert_point);
10669 edits.push(((insert_anchor..insert_anchor), text));
10670 let last_edit_start = insert_anchor.bias_left(buffer);
10671 let last_edit_end = insert_anchor.bias_right(buffer);
10672 self.transact(window, cx, |this, window, cx| {
10673 this.buffer.update(cx, |buffer, cx| {
10674 buffer.edit(edits, None, cx);
10675 });
10676 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10677 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10678 });
10679 });
10680 }
10681
10682 pub fn clear_selection_drag_state(&mut self) {
10683 self.selection_drag_state = SelectionDragState::None;
10684 }
10685
10686 pub fn duplicate(
10687 &mut self,
10688 upwards: bool,
10689 whole_lines: bool,
10690 window: &mut Window,
10691 cx: &mut Context<Self>,
10692 ) {
10693 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10694
10695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10696 let buffer = &display_map.buffer_snapshot;
10697 let selections = self.selections.all::<Point>(cx);
10698
10699 let mut edits = Vec::new();
10700 let mut selections_iter = selections.iter().peekable();
10701 while let Some(selection) = selections_iter.next() {
10702 let mut rows = selection.spanned_rows(false, &display_map);
10703 // duplicate line-wise
10704 if whole_lines || selection.start == selection.end {
10705 // Avoid duplicating the same lines twice.
10706 while let Some(next_selection) = selections_iter.peek() {
10707 let next_rows = next_selection.spanned_rows(false, &display_map);
10708 if next_rows.start < rows.end {
10709 rows.end = next_rows.end;
10710 selections_iter.next().unwrap();
10711 } else {
10712 break;
10713 }
10714 }
10715
10716 // Copy the text from the selected row region and splice it either at the start
10717 // or end of the region.
10718 let start = Point::new(rows.start.0, 0);
10719 let end = Point::new(
10720 rows.end.previous_row().0,
10721 buffer.line_len(rows.end.previous_row()),
10722 );
10723 let text = buffer
10724 .text_for_range(start..end)
10725 .chain(Some("\n"))
10726 .collect::<String>();
10727 let insert_location = if upwards {
10728 Point::new(rows.end.0, 0)
10729 } else {
10730 start
10731 };
10732 edits.push((insert_location..insert_location, text));
10733 } else {
10734 // duplicate character-wise
10735 let start = selection.start;
10736 let end = selection.end;
10737 let text = buffer.text_for_range(start..end).collect::<String>();
10738 edits.push((selection.end..selection.end, text));
10739 }
10740 }
10741
10742 self.transact(window, cx, |this, _, cx| {
10743 this.buffer.update(cx, |buffer, cx| {
10744 buffer.edit(edits, None, cx);
10745 });
10746
10747 this.request_autoscroll(Autoscroll::fit(), cx);
10748 });
10749 }
10750
10751 pub fn duplicate_line_up(
10752 &mut self,
10753 _: &DuplicateLineUp,
10754 window: &mut Window,
10755 cx: &mut Context<Self>,
10756 ) {
10757 self.duplicate(true, true, window, cx);
10758 }
10759
10760 pub fn duplicate_line_down(
10761 &mut self,
10762 _: &DuplicateLineDown,
10763 window: &mut Window,
10764 cx: &mut Context<Self>,
10765 ) {
10766 self.duplicate(false, true, window, cx);
10767 }
10768
10769 pub fn duplicate_selection(
10770 &mut self,
10771 _: &DuplicateSelection,
10772 window: &mut Window,
10773 cx: &mut Context<Self>,
10774 ) {
10775 self.duplicate(false, false, window, cx);
10776 }
10777
10778 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10779 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10780
10781 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10782 let buffer = self.buffer.read(cx).snapshot(cx);
10783
10784 let mut edits = Vec::new();
10785 let mut unfold_ranges = Vec::new();
10786 let mut refold_creases = Vec::new();
10787
10788 let selections = self.selections.all::<Point>(cx);
10789 let mut selections = selections.iter().peekable();
10790 let mut contiguous_row_selections = Vec::new();
10791 let mut new_selections = Vec::new();
10792
10793 while let Some(selection) = selections.next() {
10794 // Find all the selections that span a contiguous row range
10795 let (start_row, end_row) = consume_contiguous_rows(
10796 &mut contiguous_row_selections,
10797 selection,
10798 &display_map,
10799 &mut selections,
10800 );
10801
10802 // Move the text spanned by the row range to be before the line preceding the row range
10803 if start_row.0 > 0 {
10804 let range_to_move = Point::new(
10805 start_row.previous_row().0,
10806 buffer.line_len(start_row.previous_row()),
10807 )
10808 ..Point::new(
10809 end_row.previous_row().0,
10810 buffer.line_len(end_row.previous_row()),
10811 );
10812 let insertion_point = display_map
10813 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10814 .0;
10815
10816 // Don't move lines across excerpts
10817 if buffer
10818 .excerpt_containing(insertion_point..range_to_move.end)
10819 .is_some()
10820 {
10821 let text = buffer
10822 .text_for_range(range_to_move.clone())
10823 .flat_map(|s| s.chars())
10824 .skip(1)
10825 .chain(['\n'])
10826 .collect::<String>();
10827
10828 edits.push((
10829 buffer.anchor_after(range_to_move.start)
10830 ..buffer.anchor_before(range_to_move.end),
10831 String::new(),
10832 ));
10833 let insertion_anchor = buffer.anchor_after(insertion_point);
10834 edits.push((insertion_anchor..insertion_anchor, text));
10835
10836 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10837
10838 // Move selections up
10839 new_selections.extend(contiguous_row_selections.drain(..).map(
10840 |mut selection| {
10841 selection.start.row -= row_delta;
10842 selection.end.row -= row_delta;
10843 selection
10844 },
10845 ));
10846
10847 // Move folds up
10848 unfold_ranges.push(range_to_move.clone());
10849 for fold in display_map.folds_in_range(
10850 buffer.anchor_before(range_to_move.start)
10851 ..buffer.anchor_after(range_to_move.end),
10852 ) {
10853 let mut start = fold.range.start.to_point(&buffer);
10854 let mut end = fold.range.end.to_point(&buffer);
10855 start.row -= row_delta;
10856 end.row -= row_delta;
10857 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10858 }
10859 }
10860 }
10861
10862 // If we didn't move line(s), preserve the existing selections
10863 new_selections.append(&mut contiguous_row_selections);
10864 }
10865
10866 self.transact(window, cx, |this, window, cx| {
10867 this.unfold_ranges(&unfold_ranges, true, true, cx);
10868 this.buffer.update(cx, |buffer, cx| {
10869 for (range, text) in edits {
10870 buffer.edit([(range, text)], None, cx);
10871 }
10872 });
10873 this.fold_creases(refold_creases, true, window, cx);
10874 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10875 s.select(new_selections);
10876 })
10877 });
10878 }
10879
10880 pub fn move_line_down(
10881 &mut self,
10882 _: &MoveLineDown,
10883 window: &mut Window,
10884 cx: &mut Context<Self>,
10885 ) {
10886 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10887
10888 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10889 let buffer = self.buffer.read(cx).snapshot(cx);
10890
10891 let mut edits = Vec::new();
10892 let mut unfold_ranges = Vec::new();
10893 let mut refold_creases = Vec::new();
10894
10895 let selections = self.selections.all::<Point>(cx);
10896 let mut selections = selections.iter().peekable();
10897 let mut contiguous_row_selections = Vec::new();
10898 let mut new_selections = Vec::new();
10899
10900 while let Some(selection) = selections.next() {
10901 // Find all the selections that span a contiguous row range
10902 let (start_row, end_row) = consume_contiguous_rows(
10903 &mut contiguous_row_selections,
10904 selection,
10905 &display_map,
10906 &mut selections,
10907 );
10908
10909 // Move the text spanned by the row range to be after the last line of the row range
10910 if end_row.0 <= buffer.max_point().row {
10911 let range_to_move =
10912 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10913 let insertion_point = display_map
10914 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10915 .0;
10916
10917 // Don't move lines across excerpt boundaries
10918 if buffer
10919 .excerpt_containing(range_to_move.start..insertion_point)
10920 .is_some()
10921 {
10922 let mut text = String::from("\n");
10923 text.extend(buffer.text_for_range(range_to_move.clone()));
10924 text.pop(); // Drop trailing newline
10925 edits.push((
10926 buffer.anchor_after(range_to_move.start)
10927 ..buffer.anchor_before(range_to_move.end),
10928 String::new(),
10929 ));
10930 let insertion_anchor = buffer.anchor_after(insertion_point);
10931 edits.push((insertion_anchor..insertion_anchor, text));
10932
10933 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10934
10935 // Move selections down
10936 new_selections.extend(contiguous_row_selections.drain(..).map(
10937 |mut selection| {
10938 selection.start.row += row_delta;
10939 selection.end.row += row_delta;
10940 selection
10941 },
10942 ));
10943
10944 // Move folds down
10945 unfold_ranges.push(range_to_move.clone());
10946 for fold in display_map.folds_in_range(
10947 buffer.anchor_before(range_to_move.start)
10948 ..buffer.anchor_after(range_to_move.end),
10949 ) {
10950 let mut start = fold.range.start.to_point(&buffer);
10951 let mut end = fold.range.end.to_point(&buffer);
10952 start.row += row_delta;
10953 end.row += row_delta;
10954 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10955 }
10956 }
10957 }
10958
10959 // If we didn't move line(s), preserve the existing selections
10960 new_selections.append(&mut contiguous_row_selections);
10961 }
10962
10963 self.transact(window, cx, |this, window, cx| {
10964 this.unfold_ranges(&unfold_ranges, true, true, cx);
10965 this.buffer.update(cx, |buffer, cx| {
10966 for (range, text) in edits {
10967 buffer.edit([(range, text)], None, cx);
10968 }
10969 });
10970 this.fold_creases(refold_creases, true, window, cx);
10971 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10972 s.select(new_selections)
10973 });
10974 });
10975 }
10976
10977 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10978 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10979 let text_layout_details = &self.text_layout_details(window);
10980 self.transact(window, cx, |this, window, cx| {
10981 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10982 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10983 s.move_with(|display_map, selection| {
10984 if !selection.is_empty() {
10985 return;
10986 }
10987
10988 let mut head = selection.head();
10989 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10990 if head.column() == display_map.line_len(head.row()) {
10991 transpose_offset = display_map
10992 .buffer_snapshot
10993 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10994 }
10995
10996 if transpose_offset == 0 {
10997 return;
10998 }
10999
11000 *head.column_mut() += 1;
11001 head = display_map.clip_point(head, Bias::Right);
11002 let goal = SelectionGoal::HorizontalPosition(
11003 display_map
11004 .x_for_display_point(head, text_layout_details)
11005 .into(),
11006 );
11007 selection.collapse_to(head, goal);
11008
11009 let transpose_start = display_map
11010 .buffer_snapshot
11011 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11012 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11013 let transpose_end = display_map
11014 .buffer_snapshot
11015 .clip_offset(transpose_offset + 1, Bias::Right);
11016 if let Some(ch) =
11017 display_map.buffer_snapshot.chars_at(transpose_start).next()
11018 {
11019 edits.push((transpose_start..transpose_offset, String::new()));
11020 edits.push((transpose_end..transpose_end, ch.to_string()));
11021 }
11022 }
11023 });
11024 edits
11025 });
11026 this.buffer
11027 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11028 let selections = this.selections.all::<usize>(cx);
11029 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11030 s.select(selections);
11031 });
11032 });
11033 }
11034
11035 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11036 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11037 self.rewrap_impl(RewrapOptions::default(), cx)
11038 }
11039
11040 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11041 let buffer = self.buffer.read(cx).snapshot(cx);
11042 let selections = self.selections.all::<Point>(cx);
11043
11044 // Shrink and split selections to respect paragraph boundaries.
11045 let ranges = selections.into_iter().flat_map(|selection| {
11046 let language_settings = buffer.language_settings_at(selection.head(), cx);
11047 let language_scope = buffer.language_scope_at(selection.head());
11048
11049 let Some(start_row) = (selection.start.row..=selection.end.row)
11050 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11051 else {
11052 return vec![];
11053 };
11054 let Some(end_row) = (selection.start.row..=selection.end.row)
11055 .rev()
11056 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11057 else {
11058 return vec![];
11059 };
11060
11061 let mut row = start_row;
11062 let mut ranges = Vec::new();
11063 while let Some(blank_row) =
11064 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11065 {
11066 let next_paragraph_start = (blank_row + 1..=end_row)
11067 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11068 .unwrap();
11069 ranges.push((
11070 language_settings.clone(),
11071 language_scope.clone(),
11072 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11073 ));
11074 row = next_paragraph_start;
11075 }
11076 ranges.push((
11077 language_settings.clone(),
11078 language_scope.clone(),
11079 Point::new(row, 0)..Point::new(end_row, 0),
11080 ));
11081
11082 ranges
11083 });
11084
11085 let mut edits = Vec::new();
11086 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11087
11088 for (language_settings, language_scope, range) in ranges {
11089 let mut start_row = range.start.row;
11090 let mut end_row = range.end.row;
11091
11092 // Skip selections that overlap with a range that has already been rewrapped.
11093 let selection_range = start_row..end_row;
11094 if rewrapped_row_ranges
11095 .iter()
11096 .any(|range| range.overlaps(&selection_range))
11097 {
11098 continue;
11099 }
11100
11101 let tab_size = language_settings.tab_size;
11102
11103 // Since not all lines in the selection may be at the same indent
11104 // level, choose the indent size that is the most common between all
11105 // of the lines.
11106 //
11107 // If there is a tie, we use the deepest indent.
11108 let (indent_size, indent_end) = {
11109 let mut indent_size_occurrences = HashMap::default();
11110 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11111
11112 for row in start_row..=end_row {
11113 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11114 rows_by_indent_size.entry(indent).or_default().push(row);
11115 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11116 }
11117
11118 let indent_size = indent_size_occurrences
11119 .into_iter()
11120 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11121 .map(|(indent, _)| indent)
11122 .unwrap_or_default();
11123 let row = rows_by_indent_size[&indent_size][0];
11124 let indent_end = Point::new(row, indent_size.len);
11125
11126 (indent_size, indent_end)
11127 };
11128
11129 let mut line_prefix = indent_size.chars().collect::<String>();
11130
11131 let mut inside_comment = false;
11132 if let Some(comment_prefix) = language_scope.and_then(|language| {
11133 language
11134 .line_comment_prefixes()
11135 .iter()
11136 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11137 .cloned()
11138 }) {
11139 line_prefix.push_str(&comment_prefix);
11140 inside_comment = true;
11141 }
11142
11143 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11144 RewrapBehavior::InComments => inside_comment,
11145 RewrapBehavior::InSelections => !range.is_empty(),
11146 RewrapBehavior::Anywhere => true,
11147 };
11148
11149 let should_rewrap = options.override_language_settings
11150 || allow_rewrap_based_on_language
11151 || self.hard_wrap.is_some();
11152 if !should_rewrap {
11153 continue;
11154 }
11155
11156 if range.is_empty() {
11157 'expand_upwards: while start_row > 0 {
11158 let prev_row = start_row - 1;
11159 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11160 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11161 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11162 {
11163 start_row = prev_row;
11164 } else {
11165 break 'expand_upwards;
11166 }
11167 }
11168
11169 'expand_downwards: while end_row < buffer.max_point().row {
11170 let next_row = end_row + 1;
11171 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11172 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11173 && !buffer.is_line_blank(MultiBufferRow(next_row))
11174 {
11175 end_row = next_row;
11176 } else {
11177 break 'expand_downwards;
11178 }
11179 }
11180 }
11181
11182 let start = Point::new(start_row, 0);
11183 let start_offset = start.to_offset(&buffer);
11184 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11185 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11186 let Some(lines_without_prefixes) = selection_text
11187 .lines()
11188 .map(|line| {
11189 line.strip_prefix(&line_prefix)
11190 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11191 .with_context(|| {
11192 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11193 })
11194 })
11195 .collect::<Result<Vec<_>, _>>()
11196 .log_err()
11197 else {
11198 continue;
11199 };
11200
11201 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11202 buffer
11203 .language_settings_at(Point::new(start_row, 0), cx)
11204 .preferred_line_length as usize
11205 });
11206 let wrapped_text = wrap_with_prefix(
11207 line_prefix,
11208 lines_without_prefixes.join("\n"),
11209 wrap_column,
11210 tab_size,
11211 options.preserve_existing_whitespace,
11212 );
11213
11214 // TODO: should always use char-based diff while still supporting cursor behavior that
11215 // matches vim.
11216 let mut diff_options = DiffOptions::default();
11217 if options.override_language_settings {
11218 diff_options.max_word_diff_len = 0;
11219 diff_options.max_word_diff_line_count = 0;
11220 } else {
11221 diff_options.max_word_diff_len = usize::MAX;
11222 diff_options.max_word_diff_line_count = usize::MAX;
11223 }
11224
11225 for (old_range, new_text) in
11226 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11227 {
11228 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11229 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11230 edits.push((edit_start..edit_end, new_text));
11231 }
11232
11233 rewrapped_row_ranges.push(start_row..=end_row);
11234 }
11235
11236 self.buffer
11237 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11238 }
11239
11240 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11241 let mut text = String::new();
11242 let buffer = self.buffer.read(cx).snapshot(cx);
11243 let mut selections = self.selections.all::<Point>(cx);
11244 let mut clipboard_selections = Vec::with_capacity(selections.len());
11245 {
11246 let max_point = buffer.max_point();
11247 let mut is_first = true;
11248 for selection in &mut selections {
11249 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11250 if is_entire_line {
11251 selection.start = Point::new(selection.start.row, 0);
11252 if !selection.is_empty() && selection.end.column == 0 {
11253 selection.end = cmp::min(max_point, selection.end);
11254 } else {
11255 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11256 }
11257 selection.goal = SelectionGoal::None;
11258 }
11259 if is_first {
11260 is_first = false;
11261 } else {
11262 text += "\n";
11263 }
11264 let mut len = 0;
11265 for chunk in buffer.text_for_range(selection.start..selection.end) {
11266 text.push_str(chunk);
11267 len += chunk.len();
11268 }
11269 clipboard_selections.push(ClipboardSelection {
11270 len,
11271 is_entire_line,
11272 first_line_indent: buffer
11273 .indent_size_for_line(MultiBufferRow(selection.start.row))
11274 .len,
11275 });
11276 }
11277 }
11278
11279 self.transact(window, cx, |this, window, cx| {
11280 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11281 s.select(selections);
11282 });
11283 this.insert("", window, cx);
11284 });
11285 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11286 }
11287
11288 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11289 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11290 let item = self.cut_common(window, cx);
11291 cx.write_to_clipboard(item);
11292 }
11293
11294 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11295 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11296 self.change_selections(None, window, cx, |s| {
11297 s.move_with(|snapshot, sel| {
11298 if sel.is_empty() {
11299 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11300 }
11301 });
11302 });
11303 let item = self.cut_common(window, cx);
11304 cx.set_global(KillRing(item))
11305 }
11306
11307 pub fn kill_ring_yank(
11308 &mut self,
11309 _: &KillRingYank,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11314 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11315 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11316 (kill_ring.text().to_string(), kill_ring.metadata_json())
11317 } else {
11318 return;
11319 }
11320 } else {
11321 return;
11322 };
11323 self.do_paste(&text, metadata, false, window, cx);
11324 }
11325
11326 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11327 self.do_copy(true, cx);
11328 }
11329
11330 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11331 self.do_copy(false, cx);
11332 }
11333
11334 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11335 let selections = self.selections.all::<Point>(cx);
11336 let buffer = self.buffer.read(cx).read(cx);
11337 let mut text = String::new();
11338
11339 let mut clipboard_selections = Vec::with_capacity(selections.len());
11340 {
11341 let max_point = buffer.max_point();
11342 let mut is_first = true;
11343 for selection in &selections {
11344 let mut start = selection.start;
11345 let mut end = selection.end;
11346 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11347 if is_entire_line {
11348 start = Point::new(start.row, 0);
11349 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11350 }
11351
11352 let mut trimmed_selections = Vec::new();
11353 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11354 let row = MultiBufferRow(start.row);
11355 let first_indent = buffer.indent_size_for_line(row);
11356 if first_indent.len == 0 || start.column > first_indent.len {
11357 trimmed_selections.push(start..end);
11358 } else {
11359 trimmed_selections.push(
11360 Point::new(row.0, first_indent.len)
11361 ..Point::new(row.0, buffer.line_len(row)),
11362 );
11363 for row in start.row + 1..=end.row {
11364 let mut line_len = buffer.line_len(MultiBufferRow(row));
11365 if row == end.row {
11366 line_len = end.column;
11367 }
11368 if line_len == 0 {
11369 trimmed_selections
11370 .push(Point::new(row, 0)..Point::new(row, line_len));
11371 continue;
11372 }
11373 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11374 if row_indent_size.len >= first_indent.len {
11375 trimmed_selections.push(
11376 Point::new(row, first_indent.len)..Point::new(row, line_len),
11377 );
11378 } else {
11379 trimmed_selections.clear();
11380 trimmed_selections.push(start..end);
11381 break;
11382 }
11383 }
11384 }
11385 } else {
11386 trimmed_selections.push(start..end);
11387 }
11388
11389 for trimmed_range in trimmed_selections {
11390 if is_first {
11391 is_first = false;
11392 } else {
11393 text += "\n";
11394 }
11395 let mut len = 0;
11396 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11397 text.push_str(chunk);
11398 len += chunk.len();
11399 }
11400 clipboard_selections.push(ClipboardSelection {
11401 len,
11402 is_entire_line,
11403 first_line_indent: buffer
11404 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11405 .len,
11406 });
11407 }
11408 }
11409 }
11410
11411 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11412 text,
11413 clipboard_selections,
11414 ));
11415 }
11416
11417 pub fn do_paste(
11418 &mut self,
11419 text: &String,
11420 clipboard_selections: Option<Vec<ClipboardSelection>>,
11421 handle_entire_lines: bool,
11422 window: &mut Window,
11423 cx: &mut Context<Self>,
11424 ) {
11425 if self.read_only(cx) {
11426 return;
11427 }
11428
11429 let clipboard_text = Cow::Borrowed(text);
11430
11431 self.transact(window, cx, |this, window, cx| {
11432 if let Some(mut clipboard_selections) = clipboard_selections {
11433 let old_selections = this.selections.all::<usize>(cx);
11434 let all_selections_were_entire_line =
11435 clipboard_selections.iter().all(|s| s.is_entire_line);
11436 let first_selection_indent_column =
11437 clipboard_selections.first().map(|s| s.first_line_indent);
11438 if clipboard_selections.len() != old_selections.len() {
11439 clipboard_selections.drain(..);
11440 }
11441 let cursor_offset = this.selections.last::<usize>(cx).head();
11442 let mut auto_indent_on_paste = true;
11443
11444 this.buffer.update(cx, |buffer, cx| {
11445 let snapshot = buffer.read(cx);
11446 auto_indent_on_paste = snapshot
11447 .language_settings_at(cursor_offset, cx)
11448 .auto_indent_on_paste;
11449
11450 let mut start_offset = 0;
11451 let mut edits = Vec::new();
11452 let mut original_indent_columns = Vec::new();
11453 for (ix, selection) in old_selections.iter().enumerate() {
11454 let to_insert;
11455 let entire_line;
11456 let original_indent_column;
11457 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11458 let end_offset = start_offset + clipboard_selection.len;
11459 to_insert = &clipboard_text[start_offset..end_offset];
11460 entire_line = clipboard_selection.is_entire_line;
11461 start_offset = end_offset + 1;
11462 original_indent_column = Some(clipboard_selection.first_line_indent);
11463 } else {
11464 to_insert = clipboard_text.as_str();
11465 entire_line = all_selections_were_entire_line;
11466 original_indent_column = first_selection_indent_column
11467 }
11468
11469 // If the corresponding selection was empty when this slice of the
11470 // clipboard text was written, then the entire line containing the
11471 // selection was copied. If this selection is also currently empty,
11472 // then paste the line before the current line of the buffer.
11473 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11474 let column = selection.start.to_point(&snapshot).column as usize;
11475 let line_start = selection.start - column;
11476 line_start..line_start
11477 } else {
11478 selection.range()
11479 };
11480
11481 edits.push((range, to_insert));
11482 original_indent_columns.push(original_indent_column);
11483 }
11484 drop(snapshot);
11485
11486 buffer.edit(
11487 edits,
11488 if auto_indent_on_paste {
11489 Some(AutoindentMode::Block {
11490 original_indent_columns,
11491 })
11492 } else {
11493 None
11494 },
11495 cx,
11496 );
11497 });
11498
11499 let selections = this.selections.all::<usize>(cx);
11500 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11501 s.select(selections)
11502 });
11503 } else {
11504 this.insert(&clipboard_text, window, cx);
11505 }
11506 });
11507 }
11508
11509 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11510 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11511 if let Some(item) = cx.read_from_clipboard() {
11512 let entries = item.entries();
11513
11514 match entries.first() {
11515 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11516 // of all the pasted entries.
11517 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11518 .do_paste(
11519 clipboard_string.text(),
11520 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11521 true,
11522 window,
11523 cx,
11524 ),
11525 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11526 }
11527 }
11528 }
11529
11530 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11531 if self.read_only(cx) {
11532 return;
11533 }
11534
11535 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11536
11537 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11538 if let Some((selections, _)) =
11539 self.selection_history.transaction(transaction_id).cloned()
11540 {
11541 self.change_selections(None, window, cx, |s| {
11542 s.select_anchors(selections.to_vec());
11543 });
11544 } else {
11545 log::error!(
11546 "No entry in selection_history found for undo. \
11547 This may correspond to a bug where undo does not update the selection. \
11548 If this is occurring, please add details to \
11549 https://github.com/zed-industries/zed/issues/22692"
11550 );
11551 }
11552 self.request_autoscroll(Autoscroll::fit(), cx);
11553 self.unmark_text(window, cx);
11554 self.refresh_inline_completion(true, false, window, cx);
11555 cx.emit(EditorEvent::Edited { transaction_id });
11556 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11557 }
11558 }
11559
11560 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11561 if self.read_only(cx) {
11562 return;
11563 }
11564
11565 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11566
11567 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11568 if let Some((_, Some(selections))) =
11569 self.selection_history.transaction(transaction_id).cloned()
11570 {
11571 self.change_selections(None, window, cx, |s| {
11572 s.select_anchors(selections.to_vec());
11573 });
11574 } else {
11575 log::error!(
11576 "No entry in selection_history found for redo. \
11577 This may correspond to a bug where undo does not update the selection. \
11578 If this is occurring, please add details to \
11579 https://github.com/zed-industries/zed/issues/22692"
11580 );
11581 }
11582 self.request_autoscroll(Autoscroll::fit(), cx);
11583 self.unmark_text(window, cx);
11584 self.refresh_inline_completion(true, false, window, cx);
11585 cx.emit(EditorEvent::Edited { transaction_id });
11586 }
11587 }
11588
11589 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11590 self.buffer
11591 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11592 }
11593
11594 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11595 self.buffer
11596 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11597 }
11598
11599 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11600 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11601 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11602 s.move_with(|map, selection| {
11603 let cursor = if selection.is_empty() {
11604 movement::left(map, selection.start)
11605 } else {
11606 selection.start
11607 };
11608 selection.collapse_to(cursor, SelectionGoal::None);
11609 });
11610 })
11611 }
11612
11613 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11614 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11615 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11616 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11617 })
11618 }
11619
11620 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11621 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11622 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11623 s.move_with(|map, selection| {
11624 let cursor = if selection.is_empty() {
11625 movement::right(map, selection.end)
11626 } else {
11627 selection.end
11628 };
11629 selection.collapse_to(cursor, SelectionGoal::None)
11630 });
11631 })
11632 }
11633
11634 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11635 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11636 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11637 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11638 })
11639 }
11640
11641 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11642 if self.take_rename(true, window, cx).is_some() {
11643 return;
11644 }
11645
11646 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11647 cx.propagate();
11648 return;
11649 }
11650
11651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11652
11653 let text_layout_details = &self.text_layout_details(window);
11654 let selection_count = self.selections.count();
11655 let first_selection = self.selections.first_anchor();
11656
11657 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11658 s.move_with(|map, selection| {
11659 if !selection.is_empty() {
11660 selection.goal = SelectionGoal::None;
11661 }
11662 let (cursor, goal) = movement::up(
11663 map,
11664 selection.start,
11665 selection.goal,
11666 false,
11667 text_layout_details,
11668 );
11669 selection.collapse_to(cursor, goal);
11670 });
11671 });
11672
11673 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11674 {
11675 cx.propagate();
11676 }
11677 }
11678
11679 pub fn move_up_by_lines(
11680 &mut self,
11681 action: &MoveUpByLines,
11682 window: &mut Window,
11683 cx: &mut Context<Self>,
11684 ) {
11685 if self.take_rename(true, window, cx).is_some() {
11686 return;
11687 }
11688
11689 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11690 cx.propagate();
11691 return;
11692 }
11693
11694 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11695
11696 let text_layout_details = &self.text_layout_details(window);
11697
11698 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11699 s.move_with(|map, selection| {
11700 if !selection.is_empty() {
11701 selection.goal = SelectionGoal::None;
11702 }
11703 let (cursor, goal) = movement::up_by_rows(
11704 map,
11705 selection.start,
11706 action.lines,
11707 selection.goal,
11708 false,
11709 text_layout_details,
11710 );
11711 selection.collapse_to(cursor, goal);
11712 });
11713 })
11714 }
11715
11716 pub fn move_down_by_lines(
11717 &mut self,
11718 action: &MoveDownByLines,
11719 window: &mut Window,
11720 cx: &mut Context<Self>,
11721 ) {
11722 if self.take_rename(true, window, cx).is_some() {
11723 return;
11724 }
11725
11726 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11727 cx.propagate();
11728 return;
11729 }
11730
11731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11732
11733 let text_layout_details = &self.text_layout_details(window);
11734
11735 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11736 s.move_with(|map, selection| {
11737 if !selection.is_empty() {
11738 selection.goal = SelectionGoal::None;
11739 }
11740 let (cursor, goal) = movement::down_by_rows(
11741 map,
11742 selection.start,
11743 action.lines,
11744 selection.goal,
11745 false,
11746 text_layout_details,
11747 );
11748 selection.collapse_to(cursor, goal);
11749 });
11750 })
11751 }
11752
11753 pub fn select_down_by_lines(
11754 &mut self,
11755 action: &SelectDownByLines,
11756 window: &mut Window,
11757 cx: &mut Context<Self>,
11758 ) {
11759 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11760 let text_layout_details = &self.text_layout_details(window);
11761 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11762 s.move_heads_with(|map, head, goal| {
11763 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11764 })
11765 })
11766 }
11767
11768 pub fn select_up_by_lines(
11769 &mut self,
11770 action: &SelectUpByLines,
11771 window: &mut Window,
11772 cx: &mut Context<Self>,
11773 ) {
11774 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11775 let text_layout_details = &self.text_layout_details(window);
11776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11777 s.move_heads_with(|map, head, goal| {
11778 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11779 })
11780 })
11781 }
11782
11783 pub fn select_page_up(
11784 &mut self,
11785 _: &SelectPageUp,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) {
11789 let Some(row_count) = self.visible_row_count() else {
11790 return;
11791 };
11792
11793 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11794
11795 let text_layout_details = &self.text_layout_details(window);
11796
11797 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11798 s.move_heads_with(|map, head, goal| {
11799 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11800 })
11801 })
11802 }
11803
11804 pub fn move_page_up(
11805 &mut self,
11806 action: &MovePageUp,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 ) {
11810 if self.take_rename(true, window, cx).is_some() {
11811 return;
11812 }
11813
11814 if self
11815 .context_menu
11816 .borrow_mut()
11817 .as_mut()
11818 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11819 .unwrap_or(false)
11820 {
11821 return;
11822 }
11823
11824 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11825 cx.propagate();
11826 return;
11827 }
11828
11829 let Some(row_count) = self.visible_row_count() else {
11830 return;
11831 };
11832
11833 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11834
11835 let autoscroll = if action.center_cursor {
11836 Autoscroll::center()
11837 } else {
11838 Autoscroll::fit()
11839 };
11840
11841 let text_layout_details = &self.text_layout_details(window);
11842
11843 self.change_selections(Some(autoscroll), window, cx, |s| {
11844 s.move_with(|map, selection| {
11845 if !selection.is_empty() {
11846 selection.goal = SelectionGoal::None;
11847 }
11848 let (cursor, goal) = movement::up_by_rows(
11849 map,
11850 selection.end,
11851 row_count,
11852 selection.goal,
11853 false,
11854 text_layout_details,
11855 );
11856 selection.collapse_to(cursor, goal);
11857 });
11858 });
11859 }
11860
11861 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11862 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11863 let text_layout_details = &self.text_layout_details(window);
11864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11865 s.move_heads_with(|map, head, goal| {
11866 movement::up(map, head, goal, false, text_layout_details)
11867 })
11868 })
11869 }
11870
11871 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11872 self.take_rename(true, window, cx);
11873
11874 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11875 cx.propagate();
11876 return;
11877 }
11878
11879 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11880
11881 let text_layout_details = &self.text_layout_details(window);
11882 let selection_count = self.selections.count();
11883 let first_selection = self.selections.first_anchor();
11884
11885 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11886 s.move_with(|map, selection| {
11887 if !selection.is_empty() {
11888 selection.goal = SelectionGoal::None;
11889 }
11890 let (cursor, goal) = movement::down(
11891 map,
11892 selection.end,
11893 selection.goal,
11894 false,
11895 text_layout_details,
11896 );
11897 selection.collapse_to(cursor, goal);
11898 });
11899 });
11900
11901 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11902 {
11903 cx.propagate();
11904 }
11905 }
11906
11907 pub fn select_page_down(
11908 &mut self,
11909 _: &SelectPageDown,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 let Some(row_count) = self.visible_row_count() else {
11914 return;
11915 };
11916
11917 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11918
11919 let text_layout_details = &self.text_layout_details(window);
11920
11921 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11922 s.move_heads_with(|map, head, goal| {
11923 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11924 })
11925 })
11926 }
11927
11928 pub fn move_page_down(
11929 &mut self,
11930 action: &MovePageDown,
11931 window: &mut Window,
11932 cx: &mut Context<Self>,
11933 ) {
11934 if self.take_rename(true, window, cx).is_some() {
11935 return;
11936 }
11937
11938 if self
11939 .context_menu
11940 .borrow_mut()
11941 .as_mut()
11942 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11943 .unwrap_or(false)
11944 {
11945 return;
11946 }
11947
11948 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11949 cx.propagate();
11950 return;
11951 }
11952
11953 let Some(row_count) = self.visible_row_count() else {
11954 return;
11955 };
11956
11957 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11958
11959 let autoscroll = if action.center_cursor {
11960 Autoscroll::center()
11961 } else {
11962 Autoscroll::fit()
11963 };
11964
11965 let text_layout_details = &self.text_layout_details(window);
11966 self.change_selections(Some(autoscroll), window, cx, |s| {
11967 s.move_with(|map, selection| {
11968 if !selection.is_empty() {
11969 selection.goal = SelectionGoal::None;
11970 }
11971 let (cursor, goal) = movement::down_by_rows(
11972 map,
11973 selection.end,
11974 row_count,
11975 selection.goal,
11976 false,
11977 text_layout_details,
11978 );
11979 selection.collapse_to(cursor, goal);
11980 });
11981 });
11982 }
11983
11984 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11985 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11986 let text_layout_details = &self.text_layout_details(window);
11987 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11988 s.move_heads_with(|map, head, goal| {
11989 movement::down(map, head, goal, false, text_layout_details)
11990 })
11991 });
11992 }
11993
11994 pub fn context_menu_first(
11995 &mut self,
11996 _: &ContextMenuFirst,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12001 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12002 }
12003 }
12004
12005 pub fn context_menu_prev(
12006 &mut self,
12007 _: &ContextMenuPrevious,
12008 window: &mut Window,
12009 cx: &mut Context<Self>,
12010 ) {
12011 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12012 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12013 }
12014 }
12015
12016 pub fn context_menu_next(
12017 &mut self,
12018 _: &ContextMenuNext,
12019 window: &mut Window,
12020 cx: &mut Context<Self>,
12021 ) {
12022 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12023 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12024 }
12025 }
12026
12027 pub fn context_menu_last(
12028 &mut self,
12029 _: &ContextMenuLast,
12030 window: &mut Window,
12031 cx: &mut Context<Self>,
12032 ) {
12033 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12034 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12035 }
12036 }
12037
12038 pub fn move_to_previous_word_start(
12039 &mut self,
12040 _: &MoveToPreviousWordStart,
12041 window: &mut Window,
12042 cx: &mut Context<Self>,
12043 ) {
12044 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12045 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12046 s.move_cursors_with(|map, head, _| {
12047 (
12048 movement::previous_word_start(map, head),
12049 SelectionGoal::None,
12050 )
12051 });
12052 })
12053 }
12054
12055 pub fn move_to_previous_subword_start(
12056 &mut self,
12057 _: &MoveToPreviousSubwordStart,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) {
12061 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12063 s.move_cursors_with(|map, head, _| {
12064 (
12065 movement::previous_subword_start(map, head),
12066 SelectionGoal::None,
12067 )
12068 });
12069 })
12070 }
12071
12072 pub fn select_to_previous_word_start(
12073 &mut self,
12074 _: &SelectToPreviousWordStart,
12075 window: &mut Window,
12076 cx: &mut Context<Self>,
12077 ) {
12078 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12079 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12080 s.move_heads_with(|map, head, _| {
12081 (
12082 movement::previous_word_start(map, head),
12083 SelectionGoal::None,
12084 )
12085 });
12086 })
12087 }
12088
12089 pub fn select_to_previous_subword_start(
12090 &mut self,
12091 _: &SelectToPreviousSubwordStart,
12092 window: &mut Window,
12093 cx: &mut Context<Self>,
12094 ) {
12095 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12096 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12097 s.move_heads_with(|map, head, _| {
12098 (
12099 movement::previous_subword_start(map, head),
12100 SelectionGoal::None,
12101 )
12102 });
12103 })
12104 }
12105
12106 pub fn delete_to_previous_word_start(
12107 &mut self,
12108 action: &DeleteToPreviousWordStart,
12109 window: &mut Window,
12110 cx: &mut Context<Self>,
12111 ) {
12112 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12113 self.transact(window, cx, |this, window, cx| {
12114 this.select_autoclose_pair(window, cx);
12115 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12116 s.move_with(|map, selection| {
12117 if selection.is_empty() {
12118 let cursor = if action.ignore_newlines {
12119 movement::previous_word_start(map, selection.head())
12120 } else {
12121 movement::previous_word_start_or_newline(map, selection.head())
12122 };
12123 selection.set_head(cursor, SelectionGoal::None);
12124 }
12125 });
12126 });
12127 this.insert("", window, cx);
12128 });
12129 }
12130
12131 pub fn delete_to_previous_subword_start(
12132 &mut self,
12133 _: &DeleteToPreviousSubwordStart,
12134 window: &mut Window,
12135 cx: &mut Context<Self>,
12136 ) {
12137 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12138 self.transact(window, cx, |this, window, cx| {
12139 this.select_autoclose_pair(window, cx);
12140 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12141 s.move_with(|map, selection| {
12142 if selection.is_empty() {
12143 let cursor = movement::previous_subword_start(map, selection.head());
12144 selection.set_head(cursor, SelectionGoal::None);
12145 }
12146 });
12147 });
12148 this.insert("", window, cx);
12149 });
12150 }
12151
12152 pub fn move_to_next_word_end(
12153 &mut self,
12154 _: &MoveToNextWordEnd,
12155 window: &mut Window,
12156 cx: &mut Context<Self>,
12157 ) {
12158 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12159 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12160 s.move_cursors_with(|map, head, _| {
12161 (movement::next_word_end(map, head), SelectionGoal::None)
12162 });
12163 })
12164 }
12165
12166 pub fn move_to_next_subword_end(
12167 &mut self,
12168 _: &MoveToNextSubwordEnd,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12173 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12174 s.move_cursors_with(|map, head, _| {
12175 (movement::next_subword_end(map, head), SelectionGoal::None)
12176 });
12177 })
12178 }
12179
12180 pub fn select_to_next_word_end(
12181 &mut self,
12182 _: &SelectToNextWordEnd,
12183 window: &mut Window,
12184 cx: &mut Context<Self>,
12185 ) {
12186 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12188 s.move_heads_with(|map, head, _| {
12189 (movement::next_word_end(map, head), SelectionGoal::None)
12190 });
12191 })
12192 }
12193
12194 pub fn select_to_next_subword_end(
12195 &mut self,
12196 _: &SelectToNextSubwordEnd,
12197 window: &mut Window,
12198 cx: &mut Context<Self>,
12199 ) {
12200 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12201 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12202 s.move_heads_with(|map, head, _| {
12203 (movement::next_subword_end(map, head), SelectionGoal::None)
12204 });
12205 })
12206 }
12207
12208 pub fn delete_to_next_word_end(
12209 &mut self,
12210 action: &DeleteToNextWordEnd,
12211 window: &mut Window,
12212 cx: &mut Context<Self>,
12213 ) {
12214 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12215 self.transact(window, cx, |this, window, cx| {
12216 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12217 s.move_with(|map, selection| {
12218 if selection.is_empty() {
12219 let cursor = if action.ignore_newlines {
12220 movement::next_word_end(map, selection.head())
12221 } else {
12222 movement::next_word_end_or_newline(map, selection.head())
12223 };
12224 selection.set_head(cursor, SelectionGoal::None);
12225 }
12226 });
12227 });
12228 this.insert("", window, cx);
12229 });
12230 }
12231
12232 pub fn delete_to_next_subword_end(
12233 &mut self,
12234 _: &DeleteToNextSubwordEnd,
12235 window: &mut Window,
12236 cx: &mut Context<Self>,
12237 ) {
12238 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12239 self.transact(window, cx, |this, window, cx| {
12240 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12241 s.move_with(|map, selection| {
12242 if selection.is_empty() {
12243 let cursor = movement::next_subword_end(map, selection.head());
12244 selection.set_head(cursor, SelectionGoal::None);
12245 }
12246 });
12247 });
12248 this.insert("", window, cx);
12249 });
12250 }
12251
12252 pub fn move_to_beginning_of_line(
12253 &mut self,
12254 action: &MoveToBeginningOfLine,
12255 window: &mut Window,
12256 cx: &mut Context<Self>,
12257 ) {
12258 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12259 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12260 s.move_cursors_with(|map, head, _| {
12261 (
12262 movement::indented_line_beginning(
12263 map,
12264 head,
12265 action.stop_at_soft_wraps,
12266 action.stop_at_indent,
12267 ),
12268 SelectionGoal::None,
12269 )
12270 });
12271 })
12272 }
12273
12274 pub fn select_to_beginning_of_line(
12275 &mut self,
12276 action: &SelectToBeginningOfLine,
12277 window: &mut Window,
12278 cx: &mut Context<Self>,
12279 ) {
12280 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12281 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12282 s.move_heads_with(|map, head, _| {
12283 (
12284 movement::indented_line_beginning(
12285 map,
12286 head,
12287 action.stop_at_soft_wraps,
12288 action.stop_at_indent,
12289 ),
12290 SelectionGoal::None,
12291 )
12292 });
12293 });
12294 }
12295
12296 pub fn delete_to_beginning_of_line(
12297 &mut self,
12298 action: &DeleteToBeginningOfLine,
12299 window: &mut Window,
12300 cx: &mut Context<Self>,
12301 ) {
12302 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12303 self.transact(window, cx, |this, window, cx| {
12304 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12305 s.move_with(|_, selection| {
12306 selection.reversed = true;
12307 });
12308 });
12309
12310 this.select_to_beginning_of_line(
12311 &SelectToBeginningOfLine {
12312 stop_at_soft_wraps: false,
12313 stop_at_indent: action.stop_at_indent,
12314 },
12315 window,
12316 cx,
12317 );
12318 this.backspace(&Backspace, window, cx);
12319 });
12320 }
12321
12322 pub fn move_to_end_of_line(
12323 &mut self,
12324 action: &MoveToEndOfLine,
12325 window: &mut Window,
12326 cx: &mut Context<Self>,
12327 ) {
12328 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12330 s.move_cursors_with(|map, head, _| {
12331 (
12332 movement::line_end(map, head, action.stop_at_soft_wraps),
12333 SelectionGoal::None,
12334 )
12335 });
12336 })
12337 }
12338
12339 pub fn select_to_end_of_line(
12340 &mut self,
12341 action: &SelectToEndOfLine,
12342 window: &mut Window,
12343 cx: &mut Context<Self>,
12344 ) {
12345 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12346 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12347 s.move_heads_with(|map, head, _| {
12348 (
12349 movement::line_end(map, head, action.stop_at_soft_wraps),
12350 SelectionGoal::None,
12351 )
12352 });
12353 })
12354 }
12355
12356 pub fn delete_to_end_of_line(
12357 &mut self,
12358 _: &DeleteToEndOfLine,
12359 window: &mut Window,
12360 cx: &mut Context<Self>,
12361 ) {
12362 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12363 self.transact(window, cx, |this, window, cx| {
12364 this.select_to_end_of_line(
12365 &SelectToEndOfLine {
12366 stop_at_soft_wraps: false,
12367 },
12368 window,
12369 cx,
12370 );
12371 this.delete(&Delete, window, cx);
12372 });
12373 }
12374
12375 pub fn cut_to_end_of_line(
12376 &mut self,
12377 _: &CutToEndOfLine,
12378 window: &mut Window,
12379 cx: &mut Context<Self>,
12380 ) {
12381 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12382 self.transact(window, cx, |this, window, cx| {
12383 this.select_to_end_of_line(
12384 &SelectToEndOfLine {
12385 stop_at_soft_wraps: false,
12386 },
12387 window,
12388 cx,
12389 );
12390 this.cut(&Cut, window, cx);
12391 });
12392 }
12393
12394 pub fn move_to_start_of_paragraph(
12395 &mut self,
12396 _: &MoveToStartOfParagraph,
12397 window: &mut Window,
12398 cx: &mut Context<Self>,
12399 ) {
12400 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12401 cx.propagate();
12402 return;
12403 }
12404 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12405 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12406 s.move_with(|map, selection| {
12407 selection.collapse_to(
12408 movement::start_of_paragraph(map, selection.head(), 1),
12409 SelectionGoal::None,
12410 )
12411 });
12412 })
12413 }
12414
12415 pub fn move_to_end_of_paragraph(
12416 &mut self,
12417 _: &MoveToEndOfParagraph,
12418 window: &mut Window,
12419 cx: &mut Context<Self>,
12420 ) {
12421 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12422 cx.propagate();
12423 return;
12424 }
12425 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12426 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12427 s.move_with(|map, selection| {
12428 selection.collapse_to(
12429 movement::end_of_paragraph(map, selection.head(), 1),
12430 SelectionGoal::None,
12431 )
12432 });
12433 })
12434 }
12435
12436 pub fn select_to_start_of_paragraph(
12437 &mut self,
12438 _: &SelectToStartOfParagraph,
12439 window: &mut Window,
12440 cx: &mut Context<Self>,
12441 ) {
12442 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12443 cx.propagate();
12444 return;
12445 }
12446 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12447 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12448 s.move_heads_with(|map, head, _| {
12449 (
12450 movement::start_of_paragraph(map, head, 1),
12451 SelectionGoal::None,
12452 )
12453 });
12454 })
12455 }
12456
12457 pub fn select_to_end_of_paragraph(
12458 &mut self,
12459 _: &SelectToEndOfParagraph,
12460 window: &mut Window,
12461 cx: &mut Context<Self>,
12462 ) {
12463 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12464 cx.propagate();
12465 return;
12466 }
12467 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12468 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12469 s.move_heads_with(|map, head, _| {
12470 (
12471 movement::end_of_paragraph(map, head, 1),
12472 SelectionGoal::None,
12473 )
12474 });
12475 })
12476 }
12477
12478 pub fn move_to_start_of_excerpt(
12479 &mut self,
12480 _: &MoveToStartOfExcerpt,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 ) {
12484 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12485 cx.propagate();
12486 return;
12487 }
12488 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12489 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12490 s.move_with(|map, selection| {
12491 selection.collapse_to(
12492 movement::start_of_excerpt(
12493 map,
12494 selection.head(),
12495 workspace::searchable::Direction::Prev,
12496 ),
12497 SelectionGoal::None,
12498 )
12499 });
12500 })
12501 }
12502
12503 pub fn move_to_start_of_next_excerpt(
12504 &mut self,
12505 _: &MoveToStartOfNextExcerpt,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) {
12509 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12510 cx.propagate();
12511 return;
12512 }
12513
12514 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12515 s.move_with(|map, selection| {
12516 selection.collapse_to(
12517 movement::start_of_excerpt(
12518 map,
12519 selection.head(),
12520 workspace::searchable::Direction::Next,
12521 ),
12522 SelectionGoal::None,
12523 )
12524 });
12525 })
12526 }
12527
12528 pub fn move_to_end_of_excerpt(
12529 &mut self,
12530 _: &MoveToEndOfExcerpt,
12531 window: &mut Window,
12532 cx: &mut Context<Self>,
12533 ) {
12534 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12535 cx.propagate();
12536 return;
12537 }
12538 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12539 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12540 s.move_with(|map, selection| {
12541 selection.collapse_to(
12542 movement::end_of_excerpt(
12543 map,
12544 selection.head(),
12545 workspace::searchable::Direction::Next,
12546 ),
12547 SelectionGoal::None,
12548 )
12549 });
12550 })
12551 }
12552
12553 pub fn move_to_end_of_previous_excerpt(
12554 &mut self,
12555 _: &MoveToEndOfPreviousExcerpt,
12556 window: &mut Window,
12557 cx: &mut Context<Self>,
12558 ) {
12559 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12560 cx.propagate();
12561 return;
12562 }
12563 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12564 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12565 s.move_with(|map, selection| {
12566 selection.collapse_to(
12567 movement::end_of_excerpt(
12568 map,
12569 selection.head(),
12570 workspace::searchable::Direction::Prev,
12571 ),
12572 SelectionGoal::None,
12573 )
12574 });
12575 })
12576 }
12577
12578 pub fn select_to_start_of_excerpt(
12579 &mut self,
12580 _: &SelectToStartOfExcerpt,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12585 cx.propagate();
12586 return;
12587 }
12588 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12589 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12590 s.move_heads_with(|map, head, _| {
12591 (
12592 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12593 SelectionGoal::None,
12594 )
12595 });
12596 })
12597 }
12598
12599 pub fn select_to_start_of_next_excerpt(
12600 &mut self,
12601 _: &SelectToStartOfNextExcerpt,
12602 window: &mut Window,
12603 cx: &mut Context<Self>,
12604 ) {
12605 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12606 cx.propagate();
12607 return;
12608 }
12609 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12610 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12611 s.move_heads_with(|map, head, _| {
12612 (
12613 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12614 SelectionGoal::None,
12615 )
12616 });
12617 })
12618 }
12619
12620 pub fn select_to_end_of_excerpt(
12621 &mut self,
12622 _: &SelectToEndOfExcerpt,
12623 window: &mut Window,
12624 cx: &mut Context<Self>,
12625 ) {
12626 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12627 cx.propagate();
12628 return;
12629 }
12630 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12631 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12632 s.move_heads_with(|map, head, _| {
12633 (
12634 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12635 SelectionGoal::None,
12636 )
12637 });
12638 })
12639 }
12640
12641 pub fn select_to_end_of_previous_excerpt(
12642 &mut self,
12643 _: &SelectToEndOfPreviousExcerpt,
12644 window: &mut Window,
12645 cx: &mut Context<Self>,
12646 ) {
12647 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12648 cx.propagate();
12649 return;
12650 }
12651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12653 s.move_heads_with(|map, head, _| {
12654 (
12655 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12656 SelectionGoal::None,
12657 )
12658 });
12659 })
12660 }
12661
12662 pub fn move_to_beginning(
12663 &mut self,
12664 _: &MoveToBeginning,
12665 window: &mut Window,
12666 cx: &mut Context<Self>,
12667 ) {
12668 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12669 cx.propagate();
12670 return;
12671 }
12672 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12673 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12674 s.select_ranges(vec![0..0]);
12675 });
12676 }
12677
12678 pub fn select_to_beginning(
12679 &mut self,
12680 _: &SelectToBeginning,
12681 window: &mut Window,
12682 cx: &mut Context<Self>,
12683 ) {
12684 let mut selection = self.selections.last::<Point>(cx);
12685 selection.set_head(Point::zero(), SelectionGoal::None);
12686 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12687 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12688 s.select(vec![selection]);
12689 });
12690 }
12691
12692 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12693 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12694 cx.propagate();
12695 return;
12696 }
12697 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12698 let cursor = self.buffer.read(cx).read(cx).len();
12699 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12700 s.select_ranges(vec![cursor..cursor])
12701 });
12702 }
12703
12704 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12705 self.nav_history = nav_history;
12706 }
12707
12708 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12709 self.nav_history.as_ref()
12710 }
12711
12712 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12713 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12714 }
12715
12716 fn push_to_nav_history(
12717 &mut self,
12718 cursor_anchor: Anchor,
12719 new_position: Option<Point>,
12720 is_deactivate: bool,
12721 cx: &mut Context<Self>,
12722 ) {
12723 if let Some(nav_history) = self.nav_history.as_mut() {
12724 let buffer = self.buffer.read(cx).read(cx);
12725 let cursor_position = cursor_anchor.to_point(&buffer);
12726 let scroll_state = self.scroll_manager.anchor();
12727 let scroll_top_row = scroll_state.top_row(&buffer);
12728 drop(buffer);
12729
12730 if let Some(new_position) = new_position {
12731 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12732 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12733 return;
12734 }
12735 }
12736
12737 nav_history.push(
12738 Some(NavigationData {
12739 cursor_anchor,
12740 cursor_position,
12741 scroll_anchor: scroll_state,
12742 scroll_top_row,
12743 }),
12744 cx,
12745 );
12746 cx.emit(EditorEvent::PushedToNavHistory {
12747 anchor: cursor_anchor,
12748 is_deactivate,
12749 })
12750 }
12751 }
12752
12753 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12754 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12755 let buffer = self.buffer.read(cx).snapshot(cx);
12756 let mut selection = self.selections.first::<usize>(cx);
12757 selection.set_head(buffer.len(), SelectionGoal::None);
12758 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12759 s.select(vec![selection]);
12760 });
12761 }
12762
12763 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12764 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12765 let end = self.buffer.read(cx).read(cx).len();
12766 self.change_selections(None, window, cx, |s| {
12767 s.select_ranges(vec![0..end]);
12768 });
12769 }
12770
12771 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12772 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12773 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12774 let mut selections = self.selections.all::<Point>(cx);
12775 let max_point = display_map.buffer_snapshot.max_point();
12776 for selection in &mut selections {
12777 let rows = selection.spanned_rows(true, &display_map);
12778 selection.start = Point::new(rows.start.0, 0);
12779 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12780 selection.reversed = false;
12781 }
12782 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12783 s.select(selections);
12784 });
12785 }
12786
12787 pub fn split_selection_into_lines(
12788 &mut self,
12789 _: &SplitSelectionIntoLines,
12790 window: &mut Window,
12791 cx: &mut Context<Self>,
12792 ) {
12793 let selections = self
12794 .selections
12795 .all::<Point>(cx)
12796 .into_iter()
12797 .map(|selection| selection.start..selection.end)
12798 .collect::<Vec<_>>();
12799 self.unfold_ranges(&selections, true, true, cx);
12800
12801 let mut new_selection_ranges = Vec::new();
12802 {
12803 let buffer = self.buffer.read(cx).read(cx);
12804 for selection in selections {
12805 for row in selection.start.row..selection.end.row {
12806 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12807 new_selection_ranges.push(cursor..cursor);
12808 }
12809
12810 let is_multiline_selection = selection.start.row != selection.end.row;
12811 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12812 // so this action feels more ergonomic when paired with other selection operations
12813 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12814 if !should_skip_last {
12815 new_selection_ranges.push(selection.end..selection.end);
12816 }
12817 }
12818 }
12819 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12820 s.select_ranges(new_selection_ranges);
12821 });
12822 }
12823
12824 pub fn add_selection_above(
12825 &mut self,
12826 _: &AddSelectionAbove,
12827 window: &mut Window,
12828 cx: &mut Context<Self>,
12829 ) {
12830 self.add_selection(true, window, cx);
12831 }
12832
12833 pub fn add_selection_below(
12834 &mut self,
12835 _: &AddSelectionBelow,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) {
12839 self.add_selection(false, window, cx);
12840 }
12841
12842 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12843 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12844
12845 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12846 let all_selections = self.selections.all::<Point>(cx);
12847 let text_layout_details = self.text_layout_details(window);
12848
12849 let (mut columnar_selections, new_selections_to_columnarize) = {
12850 if let Some(state) = self.add_selections_state.as_ref() {
12851 let columnar_selection_ids: HashSet<_> = state
12852 .groups
12853 .iter()
12854 .flat_map(|group| group.stack.iter())
12855 .copied()
12856 .collect();
12857
12858 all_selections
12859 .into_iter()
12860 .partition(|s| columnar_selection_ids.contains(&s.id))
12861 } else {
12862 (Vec::new(), all_selections)
12863 }
12864 };
12865
12866 let mut state = self
12867 .add_selections_state
12868 .take()
12869 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12870
12871 for selection in new_selections_to_columnarize {
12872 let range = selection.display_range(&display_map).sorted();
12873 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12874 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12875 let positions = start_x.min(end_x)..start_x.max(end_x);
12876 let mut stack = Vec::new();
12877 for row in range.start.row().0..=range.end.row().0 {
12878 if let Some(selection) = self.selections.build_columnar_selection(
12879 &display_map,
12880 DisplayRow(row),
12881 &positions,
12882 selection.reversed,
12883 &text_layout_details,
12884 ) {
12885 stack.push(selection.id);
12886 columnar_selections.push(selection);
12887 }
12888 }
12889 if !stack.is_empty() {
12890 if above {
12891 stack.reverse();
12892 }
12893 state.groups.push(AddSelectionsGroup { above, stack });
12894 }
12895 }
12896
12897 let mut final_selections = Vec::new();
12898 let end_row = if above {
12899 DisplayRow(0)
12900 } else {
12901 display_map.max_point().row()
12902 };
12903
12904 let mut last_added_item_per_group = HashMap::default();
12905 for group in state.groups.iter_mut() {
12906 if let Some(last_id) = group.stack.last() {
12907 last_added_item_per_group.insert(*last_id, group);
12908 }
12909 }
12910
12911 for selection in columnar_selections {
12912 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12913 if above == group.above {
12914 let range = selection.display_range(&display_map).sorted();
12915 debug_assert_eq!(range.start.row(), range.end.row());
12916 let mut row = range.start.row();
12917 let positions =
12918 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12919 px(start)..px(end)
12920 } else {
12921 let start_x =
12922 display_map.x_for_display_point(range.start, &text_layout_details);
12923 let end_x =
12924 display_map.x_for_display_point(range.end, &text_layout_details);
12925 start_x.min(end_x)..start_x.max(end_x)
12926 };
12927
12928 let mut maybe_new_selection = None;
12929 while row != end_row {
12930 if above {
12931 row.0 -= 1;
12932 } else {
12933 row.0 += 1;
12934 }
12935 if let Some(new_selection) = self.selections.build_columnar_selection(
12936 &display_map,
12937 row,
12938 &positions,
12939 selection.reversed,
12940 &text_layout_details,
12941 ) {
12942 maybe_new_selection = Some(new_selection);
12943 break;
12944 }
12945 }
12946
12947 if let Some(new_selection) = maybe_new_selection {
12948 group.stack.push(new_selection.id);
12949 if above {
12950 final_selections.push(new_selection);
12951 final_selections.push(selection);
12952 } else {
12953 final_selections.push(selection);
12954 final_selections.push(new_selection);
12955 }
12956 } else {
12957 final_selections.push(selection);
12958 }
12959 } else {
12960 group.stack.pop();
12961 }
12962 } else {
12963 final_selections.push(selection);
12964 }
12965 }
12966
12967 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12968 s.select(final_selections);
12969 });
12970
12971 let final_selection_ids: HashSet<_> = self
12972 .selections
12973 .all::<Point>(cx)
12974 .iter()
12975 .map(|s| s.id)
12976 .collect();
12977 state.groups.retain_mut(|group| {
12978 // selections might get merged above so we remove invalid items from stacks
12979 group.stack.retain(|id| final_selection_ids.contains(id));
12980
12981 // single selection in stack can be treated as initial state
12982 group.stack.len() > 1
12983 });
12984
12985 if !state.groups.is_empty() {
12986 self.add_selections_state = Some(state);
12987 }
12988 }
12989
12990 fn select_match_ranges(
12991 &mut self,
12992 range: Range<usize>,
12993 reversed: bool,
12994 replace_newest: bool,
12995 auto_scroll: Option<Autoscroll>,
12996 window: &mut Window,
12997 cx: &mut Context<Editor>,
12998 ) {
12999 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13000 self.change_selections(auto_scroll, window, cx, |s| {
13001 if replace_newest {
13002 s.delete(s.newest_anchor().id);
13003 }
13004 if reversed {
13005 s.insert_range(range.end..range.start);
13006 } else {
13007 s.insert_range(range);
13008 }
13009 });
13010 }
13011
13012 pub fn select_next_match_internal(
13013 &mut self,
13014 display_map: &DisplaySnapshot,
13015 replace_newest: bool,
13016 autoscroll: Option<Autoscroll>,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) -> Result<()> {
13020 let buffer = &display_map.buffer_snapshot;
13021 let mut selections = self.selections.all::<usize>(cx);
13022 if let Some(mut select_next_state) = self.select_next_state.take() {
13023 let query = &select_next_state.query;
13024 if !select_next_state.done {
13025 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13026 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13027 let mut next_selected_range = None;
13028
13029 let bytes_after_last_selection =
13030 buffer.bytes_in_range(last_selection.end..buffer.len());
13031 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13032 let query_matches = query
13033 .stream_find_iter(bytes_after_last_selection)
13034 .map(|result| (last_selection.end, result))
13035 .chain(
13036 query
13037 .stream_find_iter(bytes_before_first_selection)
13038 .map(|result| (0, result)),
13039 );
13040
13041 for (start_offset, query_match) in query_matches {
13042 let query_match = query_match.unwrap(); // can only fail due to I/O
13043 let offset_range =
13044 start_offset + query_match.start()..start_offset + query_match.end();
13045 let display_range = offset_range.start.to_display_point(display_map)
13046 ..offset_range.end.to_display_point(display_map);
13047
13048 if !select_next_state.wordwise
13049 || (!movement::is_inside_word(display_map, display_range.start)
13050 && !movement::is_inside_word(display_map, display_range.end))
13051 {
13052 // TODO: This is n^2, because we might check all the selections
13053 if !selections
13054 .iter()
13055 .any(|selection| selection.range().overlaps(&offset_range))
13056 {
13057 next_selected_range = Some(offset_range);
13058 break;
13059 }
13060 }
13061 }
13062
13063 if let Some(next_selected_range) = next_selected_range {
13064 self.select_match_ranges(
13065 next_selected_range,
13066 last_selection.reversed,
13067 replace_newest,
13068 autoscroll,
13069 window,
13070 cx,
13071 );
13072 } else {
13073 select_next_state.done = true;
13074 }
13075 }
13076
13077 self.select_next_state = Some(select_next_state);
13078 } else {
13079 let mut only_carets = true;
13080 let mut same_text_selected = true;
13081 let mut selected_text = None;
13082
13083 let mut selections_iter = selections.iter().peekable();
13084 while let Some(selection) = selections_iter.next() {
13085 if selection.start != selection.end {
13086 only_carets = false;
13087 }
13088
13089 if same_text_selected {
13090 if selected_text.is_none() {
13091 selected_text =
13092 Some(buffer.text_for_range(selection.range()).collect::<String>());
13093 }
13094
13095 if let Some(next_selection) = selections_iter.peek() {
13096 if next_selection.range().len() == selection.range().len() {
13097 let next_selected_text = buffer
13098 .text_for_range(next_selection.range())
13099 .collect::<String>();
13100 if Some(next_selected_text) != selected_text {
13101 same_text_selected = false;
13102 selected_text = None;
13103 }
13104 } else {
13105 same_text_selected = false;
13106 selected_text = None;
13107 }
13108 }
13109 }
13110 }
13111
13112 if only_carets {
13113 for selection in &mut selections {
13114 let word_range = movement::surrounding_word(
13115 display_map,
13116 selection.start.to_display_point(display_map),
13117 );
13118 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13119 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13120 selection.goal = SelectionGoal::None;
13121 selection.reversed = false;
13122 self.select_match_ranges(
13123 selection.start..selection.end,
13124 selection.reversed,
13125 replace_newest,
13126 autoscroll,
13127 window,
13128 cx,
13129 );
13130 }
13131
13132 if selections.len() == 1 {
13133 let selection = selections
13134 .last()
13135 .expect("ensured that there's only one selection");
13136 let query = buffer
13137 .text_for_range(selection.start..selection.end)
13138 .collect::<String>();
13139 let is_empty = query.is_empty();
13140 let select_state = SelectNextState {
13141 query: AhoCorasick::new(&[query])?,
13142 wordwise: true,
13143 done: is_empty,
13144 };
13145 self.select_next_state = Some(select_state);
13146 } else {
13147 self.select_next_state = None;
13148 }
13149 } else if let Some(selected_text) = selected_text {
13150 self.select_next_state = Some(SelectNextState {
13151 query: AhoCorasick::new(&[selected_text])?,
13152 wordwise: false,
13153 done: false,
13154 });
13155 self.select_next_match_internal(
13156 display_map,
13157 replace_newest,
13158 autoscroll,
13159 window,
13160 cx,
13161 )?;
13162 }
13163 }
13164 Ok(())
13165 }
13166
13167 pub fn select_all_matches(
13168 &mut self,
13169 _action: &SelectAllMatches,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) -> Result<()> {
13173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13174
13175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13176
13177 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13178 let Some(select_next_state) = self.select_next_state.as_mut() else {
13179 return Ok(());
13180 };
13181 if select_next_state.done {
13182 return Ok(());
13183 }
13184
13185 let mut new_selections = Vec::new();
13186
13187 let reversed = self.selections.oldest::<usize>(cx).reversed;
13188 let buffer = &display_map.buffer_snapshot;
13189 let query_matches = select_next_state
13190 .query
13191 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13192
13193 for query_match in query_matches.into_iter() {
13194 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13195 let offset_range = if reversed {
13196 query_match.end()..query_match.start()
13197 } else {
13198 query_match.start()..query_match.end()
13199 };
13200 let display_range = offset_range.start.to_display_point(&display_map)
13201 ..offset_range.end.to_display_point(&display_map);
13202
13203 if !select_next_state.wordwise
13204 || (!movement::is_inside_word(&display_map, display_range.start)
13205 && !movement::is_inside_word(&display_map, display_range.end))
13206 {
13207 new_selections.push(offset_range.start..offset_range.end);
13208 }
13209 }
13210
13211 select_next_state.done = true;
13212 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13213 self.change_selections(None, window, cx, |selections| {
13214 selections.select_ranges(new_selections)
13215 });
13216
13217 Ok(())
13218 }
13219
13220 pub fn select_next(
13221 &mut self,
13222 action: &SelectNext,
13223 window: &mut Window,
13224 cx: &mut Context<Self>,
13225 ) -> Result<()> {
13226 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13228 self.select_next_match_internal(
13229 &display_map,
13230 action.replace_newest,
13231 Some(Autoscroll::newest()),
13232 window,
13233 cx,
13234 )?;
13235 Ok(())
13236 }
13237
13238 pub fn select_previous(
13239 &mut self,
13240 action: &SelectPrevious,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) -> Result<()> {
13244 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13246 let buffer = &display_map.buffer_snapshot;
13247 let mut selections = self.selections.all::<usize>(cx);
13248 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13249 let query = &select_prev_state.query;
13250 if !select_prev_state.done {
13251 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13252 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13253 let mut next_selected_range = None;
13254 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13255 let bytes_before_last_selection =
13256 buffer.reversed_bytes_in_range(0..last_selection.start);
13257 let bytes_after_first_selection =
13258 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13259 let query_matches = query
13260 .stream_find_iter(bytes_before_last_selection)
13261 .map(|result| (last_selection.start, result))
13262 .chain(
13263 query
13264 .stream_find_iter(bytes_after_first_selection)
13265 .map(|result| (buffer.len(), result)),
13266 );
13267 for (end_offset, query_match) in query_matches {
13268 let query_match = query_match.unwrap(); // can only fail due to I/O
13269 let offset_range =
13270 end_offset - query_match.end()..end_offset - query_match.start();
13271 let display_range = offset_range.start.to_display_point(&display_map)
13272 ..offset_range.end.to_display_point(&display_map);
13273
13274 if !select_prev_state.wordwise
13275 || (!movement::is_inside_word(&display_map, display_range.start)
13276 && !movement::is_inside_word(&display_map, display_range.end))
13277 {
13278 next_selected_range = Some(offset_range);
13279 break;
13280 }
13281 }
13282
13283 if let Some(next_selected_range) = next_selected_range {
13284 self.select_match_ranges(
13285 next_selected_range,
13286 last_selection.reversed,
13287 action.replace_newest,
13288 Some(Autoscroll::newest()),
13289 window,
13290 cx,
13291 );
13292 } else {
13293 select_prev_state.done = true;
13294 }
13295 }
13296
13297 self.select_prev_state = Some(select_prev_state);
13298 } else {
13299 let mut only_carets = true;
13300 let mut same_text_selected = true;
13301 let mut selected_text = None;
13302
13303 let mut selections_iter = selections.iter().peekable();
13304 while let Some(selection) = selections_iter.next() {
13305 if selection.start != selection.end {
13306 only_carets = false;
13307 }
13308
13309 if same_text_selected {
13310 if selected_text.is_none() {
13311 selected_text =
13312 Some(buffer.text_for_range(selection.range()).collect::<String>());
13313 }
13314
13315 if let Some(next_selection) = selections_iter.peek() {
13316 if next_selection.range().len() == selection.range().len() {
13317 let next_selected_text = buffer
13318 .text_for_range(next_selection.range())
13319 .collect::<String>();
13320 if Some(next_selected_text) != selected_text {
13321 same_text_selected = false;
13322 selected_text = None;
13323 }
13324 } else {
13325 same_text_selected = false;
13326 selected_text = None;
13327 }
13328 }
13329 }
13330 }
13331
13332 if only_carets {
13333 for selection in &mut selections {
13334 let word_range = movement::surrounding_word(
13335 &display_map,
13336 selection.start.to_display_point(&display_map),
13337 );
13338 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13339 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13340 selection.goal = SelectionGoal::None;
13341 selection.reversed = false;
13342 self.select_match_ranges(
13343 selection.start..selection.end,
13344 selection.reversed,
13345 action.replace_newest,
13346 Some(Autoscroll::newest()),
13347 window,
13348 cx,
13349 );
13350 }
13351 if selections.len() == 1 {
13352 let selection = selections
13353 .last()
13354 .expect("ensured that there's only one selection");
13355 let query = buffer
13356 .text_for_range(selection.start..selection.end)
13357 .collect::<String>();
13358 let is_empty = query.is_empty();
13359 let select_state = SelectNextState {
13360 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13361 wordwise: true,
13362 done: is_empty,
13363 };
13364 self.select_prev_state = Some(select_state);
13365 } else {
13366 self.select_prev_state = None;
13367 }
13368 } else if let Some(selected_text) = selected_text {
13369 self.select_prev_state = Some(SelectNextState {
13370 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13371 wordwise: false,
13372 done: false,
13373 });
13374 self.select_previous(action, window, cx)?;
13375 }
13376 }
13377 Ok(())
13378 }
13379
13380 pub fn find_next_match(
13381 &mut self,
13382 _: &FindNextMatch,
13383 window: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) -> Result<()> {
13386 let selections = self.selections.disjoint_anchors();
13387 match selections.first() {
13388 Some(first) if selections.len() >= 2 => {
13389 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13390 s.select_ranges([first.range()]);
13391 });
13392 }
13393 _ => self.select_next(
13394 &SelectNext {
13395 replace_newest: true,
13396 },
13397 window,
13398 cx,
13399 )?,
13400 }
13401 Ok(())
13402 }
13403
13404 pub fn find_previous_match(
13405 &mut self,
13406 _: &FindPreviousMatch,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) -> Result<()> {
13410 let selections = self.selections.disjoint_anchors();
13411 match selections.last() {
13412 Some(last) if selections.len() >= 2 => {
13413 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13414 s.select_ranges([last.range()]);
13415 });
13416 }
13417 _ => self.select_previous(
13418 &SelectPrevious {
13419 replace_newest: true,
13420 },
13421 window,
13422 cx,
13423 )?,
13424 }
13425 Ok(())
13426 }
13427
13428 pub fn toggle_comments(
13429 &mut self,
13430 action: &ToggleComments,
13431 window: &mut Window,
13432 cx: &mut Context<Self>,
13433 ) {
13434 if self.read_only(cx) {
13435 return;
13436 }
13437 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13438 let text_layout_details = &self.text_layout_details(window);
13439 self.transact(window, cx, |this, window, cx| {
13440 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13441 let mut edits = Vec::new();
13442 let mut selection_edit_ranges = Vec::new();
13443 let mut last_toggled_row = None;
13444 let snapshot = this.buffer.read(cx).read(cx);
13445 let empty_str: Arc<str> = Arc::default();
13446 let mut suffixes_inserted = Vec::new();
13447 let ignore_indent = action.ignore_indent;
13448
13449 fn comment_prefix_range(
13450 snapshot: &MultiBufferSnapshot,
13451 row: MultiBufferRow,
13452 comment_prefix: &str,
13453 comment_prefix_whitespace: &str,
13454 ignore_indent: bool,
13455 ) -> Range<Point> {
13456 let indent_size = if ignore_indent {
13457 0
13458 } else {
13459 snapshot.indent_size_for_line(row).len
13460 };
13461
13462 let start = Point::new(row.0, indent_size);
13463
13464 let mut line_bytes = snapshot
13465 .bytes_in_range(start..snapshot.max_point())
13466 .flatten()
13467 .copied();
13468
13469 // If this line currently begins with the line comment prefix, then record
13470 // the range containing the prefix.
13471 if line_bytes
13472 .by_ref()
13473 .take(comment_prefix.len())
13474 .eq(comment_prefix.bytes())
13475 {
13476 // Include any whitespace that matches the comment prefix.
13477 let matching_whitespace_len = line_bytes
13478 .zip(comment_prefix_whitespace.bytes())
13479 .take_while(|(a, b)| a == b)
13480 .count() as u32;
13481 let end = Point::new(
13482 start.row,
13483 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13484 );
13485 start..end
13486 } else {
13487 start..start
13488 }
13489 }
13490
13491 fn comment_suffix_range(
13492 snapshot: &MultiBufferSnapshot,
13493 row: MultiBufferRow,
13494 comment_suffix: &str,
13495 comment_suffix_has_leading_space: bool,
13496 ) -> Range<Point> {
13497 let end = Point::new(row.0, snapshot.line_len(row));
13498 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13499
13500 let mut line_end_bytes = snapshot
13501 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13502 .flatten()
13503 .copied();
13504
13505 let leading_space_len = if suffix_start_column > 0
13506 && line_end_bytes.next() == Some(b' ')
13507 && comment_suffix_has_leading_space
13508 {
13509 1
13510 } else {
13511 0
13512 };
13513
13514 // If this line currently begins with the line comment prefix, then record
13515 // the range containing the prefix.
13516 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13517 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13518 start..end
13519 } else {
13520 end..end
13521 }
13522 }
13523
13524 // TODO: Handle selections that cross excerpts
13525 for selection in &mut selections {
13526 let start_column = snapshot
13527 .indent_size_for_line(MultiBufferRow(selection.start.row))
13528 .len;
13529 let language = if let Some(language) =
13530 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13531 {
13532 language
13533 } else {
13534 continue;
13535 };
13536
13537 selection_edit_ranges.clear();
13538
13539 // If multiple selections contain a given row, avoid processing that
13540 // row more than once.
13541 let mut start_row = MultiBufferRow(selection.start.row);
13542 if last_toggled_row == Some(start_row) {
13543 start_row = start_row.next_row();
13544 }
13545 let end_row =
13546 if selection.end.row > selection.start.row && selection.end.column == 0 {
13547 MultiBufferRow(selection.end.row - 1)
13548 } else {
13549 MultiBufferRow(selection.end.row)
13550 };
13551 last_toggled_row = Some(end_row);
13552
13553 if start_row > end_row {
13554 continue;
13555 }
13556
13557 // If the language has line comments, toggle those.
13558 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13559
13560 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13561 if ignore_indent {
13562 full_comment_prefixes = full_comment_prefixes
13563 .into_iter()
13564 .map(|s| Arc::from(s.trim_end()))
13565 .collect();
13566 }
13567
13568 if !full_comment_prefixes.is_empty() {
13569 let first_prefix = full_comment_prefixes
13570 .first()
13571 .expect("prefixes is non-empty");
13572 let prefix_trimmed_lengths = full_comment_prefixes
13573 .iter()
13574 .map(|p| p.trim_end_matches(' ').len())
13575 .collect::<SmallVec<[usize; 4]>>();
13576
13577 let mut all_selection_lines_are_comments = true;
13578
13579 for row in start_row.0..=end_row.0 {
13580 let row = MultiBufferRow(row);
13581 if start_row < end_row && snapshot.is_line_blank(row) {
13582 continue;
13583 }
13584
13585 let prefix_range = full_comment_prefixes
13586 .iter()
13587 .zip(prefix_trimmed_lengths.iter().copied())
13588 .map(|(prefix, trimmed_prefix_len)| {
13589 comment_prefix_range(
13590 snapshot.deref(),
13591 row,
13592 &prefix[..trimmed_prefix_len],
13593 &prefix[trimmed_prefix_len..],
13594 ignore_indent,
13595 )
13596 })
13597 .max_by_key(|range| range.end.column - range.start.column)
13598 .expect("prefixes is non-empty");
13599
13600 if prefix_range.is_empty() {
13601 all_selection_lines_are_comments = false;
13602 }
13603
13604 selection_edit_ranges.push(prefix_range);
13605 }
13606
13607 if all_selection_lines_are_comments {
13608 edits.extend(
13609 selection_edit_ranges
13610 .iter()
13611 .cloned()
13612 .map(|range| (range, empty_str.clone())),
13613 );
13614 } else {
13615 let min_column = selection_edit_ranges
13616 .iter()
13617 .map(|range| range.start.column)
13618 .min()
13619 .unwrap_or(0);
13620 edits.extend(selection_edit_ranges.iter().map(|range| {
13621 let position = Point::new(range.start.row, min_column);
13622 (position..position, first_prefix.clone())
13623 }));
13624 }
13625 } else if let Some((full_comment_prefix, comment_suffix)) =
13626 language.block_comment_delimiters()
13627 {
13628 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13629 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13630 let prefix_range = comment_prefix_range(
13631 snapshot.deref(),
13632 start_row,
13633 comment_prefix,
13634 comment_prefix_whitespace,
13635 ignore_indent,
13636 );
13637 let suffix_range = comment_suffix_range(
13638 snapshot.deref(),
13639 end_row,
13640 comment_suffix.trim_start_matches(' '),
13641 comment_suffix.starts_with(' '),
13642 );
13643
13644 if prefix_range.is_empty() || suffix_range.is_empty() {
13645 edits.push((
13646 prefix_range.start..prefix_range.start,
13647 full_comment_prefix.clone(),
13648 ));
13649 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13650 suffixes_inserted.push((end_row, comment_suffix.len()));
13651 } else {
13652 edits.push((prefix_range, empty_str.clone()));
13653 edits.push((suffix_range, empty_str.clone()));
13654 }
13655 } else {
13656 continue;
13657 }
13658 }
13659
13660 drop(snapshot);
13661 this.buffer.update(cx, |buffer, cx| {
13662 buffer.edit(edits, None, cx);
13663 });
13664
13665 // Adjust selections so that they end before any comment suffixes that
13666 // were inserted.
13667 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13668 let mut selections = this.selections.all::<Point>(cx);
13669 let snapshot = this.buffer.read(cx).read(cx);
13670 for selection in &mut selections {
13671 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13672 match row.cmp(&MultiBufferRow(selection.end.row)) {
13673 Ordering::Less => {
13674 suffixes_inserted.next();
13675 continue;
13676 }
13677 Ordering::Greater => break,
13678 Ordering::Equal => {
13679 if selection.end.column == snapshot.line_len(row) {
13680 if selection.is_empty() {
13681 selection.start.column -= suffix_len as u32;
13682 }
13683 selection.end.column -= suffix_len as u32;
13684 }
13685 break;
13686 }
13687 }
13688 }
13689 }
13690
13691 drop(snapshot);
13692 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13693 s.select(selections)
13694 });
13695
13696 let selections = this.selections.all::<Point>(cx);
13697 let selections_on_single_row = selections.windows(2).all(|selections| {
13698 selections[0].start.row == selections[1].start.row
13699 && selections[0].end.row == selections[1].end.row
13700 && selections[0].start.row == selections[0].end.row
13701 });
13702 let selections_selecting = selections
13703 .iter()
13704 .any(|selection| selection.start != selection.end);
13705 let advance_downwards = action.advance_downwards
13706 && selections_on_single_row
13707 && !selections_selecting
13708 && !matches!(this.mode, EditorMode::SingleLine { .. });
13709
13710 if advance_downwards {
13711 let snapshot = this.buffer.read(cx).snapshot(cx);
13712
13713 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13714 s.move_cursors_with(|display_snapshot, display_point, _| {
13715 let mut point = display_point.to_point(display_snapshot);
13716 point.row += 1;
13717 point = snapshot.clip_point(point, Bias::Left);
13718 let display_point = point.to_display_point(display_snapshot);
13719 let goal = SelectionGoal::HorizontalPosition(
13720 display_snapshot
13721 .x_for_display_point(display_point, text_layout_details)
13722 .into(),
13723 );
13724 (display_point, goal)
13725 })
13726 });
13727 }
13728 });
13729 }
13730
13731 pub fn select_enclosing_symbol(
13732 &mut self,
13733 _: &SelectEnclosingSymbol,
13734 window: &mut Window,
13735 cx: &mut Context<Self>,
13736 ) {
13737 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13738
13739 let buffer = self.buffer.read(cx).snapshot(cx);
13740 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13741
13742 fn update_selection(
13743 selection: &Selection<usize>,
13744 buffer_snap: &MultiBufferSnapshot,
13745 ) -> Option<Selection<usize>> {
13746 let cursor = selection.head();
13747 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13748 for symbol in symbols.iter().rev() {
13749 let start = symbol.range.start.to_offset(buffer_snap);
13750 let end = symbol.range.end.to_offset(buffer_snap);
13751 let new_range = start..end;
13752 if start < selection.start || end > selection.end {
13753 return Some(Selection {
13754 id: selection.id,
13755 start: new_range.start,
13756 end: new_range.end,
13757 goal: SelectionGoal::None,
13758 reversed: selection.reversed,
13759 });
13760 }
13761 }
13762 None
13763 }
13764
13765 let mut selected_larger_symbol = false;
13766 let new_selections = old_selections
13767 .iter()
13768 .map(|selection| match update_selection(selection, &buffer) {
13769 Some(new_selection) => {
13770 if new_selection.range() != selection.range() {
13771 selected_larger_symbol = true;
13772 }
13773 new_selection
13774 }
13775 None => selection.clone(),
13776 })
13777 .collect::<Vec<_>>();
13778
13779 if selected_larger_symbol {
13780 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13781 s.select(new_selections);
13782 });
13783 }
13784 }
13785
13786 pub fn select_larger_syntax_node(
13787 &mut self,
13788 _: &SelectLargerSyntaxNode,
13789 window: &mut Window,
13790 cx: &mut Context<Self>,
13791 ) {
13792 let Some(visible_row_count) = self.visible_row_count() else {
13793 return;
13794 };
13795 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13796 if old_selections.is_empty() {
13797 return;
13798 }
13799
13800 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13801
13802 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13803 let buffer = self.buffer.read(cx).snapshot(cx);
13804
13805 let mut selected_larger_node = false;
13806 let mut new_selections = old_selections
13807 .iter()
13808 .map(|selection| {
13809 let old_range = selection.start..selection.end;
13810
13811 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13812 // manually select word at selection
13813 if ["string_content", "inline"].contains(&node.kind()) {
13814 let word_range = {
13815 let display_point = buffer
13816 .offset_to_point(old_range.start)
13817 .to_display_point(&display_map);
13818 let Range { start, end } =
13819 movement::surrounding_word(&display_map, display_point);
13820 start.to_point(&display_map).to_offset(&buffer)
13821 ..end.to_point(&display_map).to_offset(&buffer)
13822 };
13823 // ignore if word is already selected
13824 if !word_range.is_empty() && old_range != word_range {
13825 let last_word_range = {
13826 let display_point = buffer
13827 .offset_to_point(old_range.end)
13828 .to_display_point(&display_map);
13829 let Range { start, end } =
13830 movement::surrounding_word(&display_map, display_point);
13831 start.to_point(&display_map).to_offset(&buffer)
13832 ..end.to_point(&display_map).to_offset(&buffer)
13833 };
13834 // only select word if start and end point belongs to same word
13835 if word_range == last_word_range {
13836 selected_larger_node = true;
13837 return Selection {
13838 id: selection.id,
13839 start: word_range.start,
13840 end: word_range.end,
13841 goal: SelectionGoal::None,
13842 reversed: selection.reversed,
13843 };
13844 }
13845 }
13846 }
13847 }
13848
13849 let mut new_range = old_range.clone();
13850 while let Some((_node, containing_range)) =
13851 buffer.syntax_ancestor(new_range.clone())
13852 {
13853 new_range = match containing_range {
13854 MultiOrSingleBufferOffsetRange::Single(_) => break,
13855 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13856 };
13857 if !display_map.intersects_fold(new_range.start)
13858 && !display_map.intersects_fold(new_range.end)
13859 {
13860 break;
13861 }
13862 }
13863
13864 selected_larger_node |= new_range != old_range;
13865 Selection {
13866 id: selection.id,
13867 start: new_range.start,
13868 end: new_range.end,
13869 goal: SelectionGoal::None,
13870 reversed: selection.reversed,
13871 }
13872 })
13873 .collect::<Vec<_>>();
13874
13875 if !selected_larger_node {
13876 return; // don't put this call in the history
13877 }
13878
13879 // scroll based on transformation done to the last selection created by the user
13880 let (last_old, last_new) = old_selections
13881 .last()
13882 .zip(new_selections.last().cloned())
13883 .expect("old_selections isn't empty");
13884
13885 // revert selection
13886 let is_selection_reversed = {
13887 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13888 new_selections.last_mut().expect("checked above").reversed =
13889 should_newest_selection_be_reversed;
13890 should_newest_selection_be_reversed
13891 };
13892
13893 if selected_larger_node {
13894 self.select_syntax_node_history.disable_clearing = true;
13895 self.change_selections(None, window, cx, |s| {
13896 s.select(new_selections.clone());
13897 });
13898 self.select_syntax_node_history.disable_clearing = false;
13899 }
13900
13901 let start_row = last_new.start.to_display_point(&display_map).row().0;
13902 let end_row = last_new.end.to_display_point(&display_map).row().0;
13903 let selection_height = end_row - start_row + 1;
13904 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13905
13906 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13907 let scroll_behavior = if fits_on_the_screen {
13908 self.request_autoscroll(Autoscroll::fit(), cx);
13909 SelectSyntaxNodeScrollBehavior::FitSelection
13910 } else if is_selection_reversed {
13911 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13912 SelectSyntaxNodeScrollBehavior::CursorTop
13913 } else {
13914 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13915 SelectSyntaxNodeScrollBehavior::CursorBottom
13916 };
13917
13918 self.select_syntax_node_history.push((
13919 old_selections,
13920 scroll_behavior,
13921 is_selection_reversed,
13922 ));
13923 }
13924
13925 pub fn select_smaller_syntax_node(
13926 &mut self,
13927 _: &SelectSmallerSyntaxNode,
13928 window: &mut Window,
13929 cx: &mut Context<Self>,
13930 ) {
13931 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13932
13933 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13934 self.select_syntax_node_history.pop()
13935 {
13936 if let Some(selection) = selections.last_mut() {
13937 selection.reversed = is_selection_reversed;
13938 }
13939
13940 self.select_syntax_node_history.disable_clearing = true;
13941 self.change_selections(None, window, cx, |s| {
13942 s.select(selections.to_vec());
13943 });
13944 self.select_syntax_node_history.disable_clearing = false;
13945
13946 match scroll_behavior {
13947 SelectSyntaxNodeScrollBehavior::CursorTop => {
13948 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13949 }
13950 SelectSyntaxNodeScrollBehavior::FitSelection => {
13951 self.request_autoscroll(Autoscroll::fit(), cx);
13952 }
13953 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13954 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13955 }
13956 }
13957 }
13958 }
13959
13960 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13961 if !EditorSettings::get_global(cx).gutter.runnables {
13962 self.clear_tasks();
13963 return Task::ready(());
13964 }
13965 let project = self.project.as_ref().map(Entity::downgrade);
13966 let task_sources = self.lsp_task_sources(cx);
13967 let multi_buffer = self.buffer.downgrade();
13968 cx.spawn_in(window, async move |editor, cx| {
13969 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13970 let Some(project) = project.and_then(|p| p.upgrade()) else {
13971 return;
13972 };
13973 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13974 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13975 }) else {
13976 return;
13977 };
13978
13979 let hide_runnables = project
13980 .update(cx, |project, cx| {
13981 // Do not display any test indicators in non-dev server remote projects.
13982 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13983 })
13984 .unwrap_or(true);
13985 if hide_runnables {
13986 return;
13987 }
13988 let new_rows =
13989 cx.background_spawn({
13990 let snapshot = display_snapshot.clone();
13991 async move {
13992 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13993 }
13994 })
13995 .await;
13996 let Ok(lsp_tasks) =
13997 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13998 else {
13999 return;
14000 };
14001 let lsp_tasks = lsp_tasks.await;
14002
14003 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14004 lsp_tasks
14005 .into_iter()
14006 .flat_map(|(kind, tasks)| {
14007 tasks.into_iter().filter_map(move |(location, task)| {
14008 Some((kind.clone(), location?, task))
14009 })
14010 })
14011 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14012 let buffer = location.target.buffer;
14013 let buffer_snapshot = buffer.read(cx).snapshot();
14014 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14015 |(excerpt_id, snapshot, _)| {
14016 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14017 display_snapshot
14018 .buffer_snapshot
14019 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14020 } else {
14021 None
14022 }
14023 },
14024 );
14025 if let Some(offset) = offset {
14026 let task_buffer_range =
14027 location.target.range.to_point(&buffer_snapshot);
14028 let context_buffer_range =
14029 task_buffer_range.to_offset(&buffer_snapshot);
14030 let context_range = BufferOffset(context_buffer_range.start)
14031 ..BufferOffset(context_buffer_range.end);
14032
14033 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14034 .or_insert_with(|| RunnableTasks {
14035 templates: Vec::new(),
14036 offset,
14037 column: task_buffer_range.start.column,
14038 extra_variables: HashMap::default(),
14039 context_range,
14040 })
14041 .templates
14042 .push((kind, task.original_task().clone()));
14043 }
14044
14045 acc
14046 })
14047 }) else {
14048 return;
14049 };
14050
14051 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14052 buffer.language_settings(cx).tasks.prefer_lsp
14053 }) else {
14054 return;
14055 };
14056
14057 let rows = Self::runnable_rows(
14058 project,
14059 display_snapshot,
14060 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14061 new_rows,
14062 cx.clone(),
14063 )
14064 .await;
14065 editor
14066 .update(cx, |editor, _| {
14067 editor.clear_tasks();
14068 for (key, mut value) in rows {
14069 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14070 value.templates.extend(lsp_tasks.templates);
14071 }
14072
14073 editor.insert_tasks(key, value);
14074 }
14075 for (key, value) in lsp_tasks_by_rows {
14076 editor.insert_tasks(key, value);
14077 }
14078 })
14079 .ok();
14080 })
14081 }
14082 fn fetch_runnable_ranges(
14083 snapshot: &DisplaySnapshot,
14084 range: Range<Anchor>,
14085 ) -> Vec<language::RunnableRange> {
14086 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14087 }
14088
14089 fn runnable_rows(
14090 project: Entity<Project>,
14091 snapshot: DisplaySnapshot,
14092 prefer_lsp: bool,
14093 runnable_ranges: Vec<RunnableRange>,
14094 cx: AsyncWindowContext,
14095 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14096 cx.spawn(async move |cx| {
14097 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14098 for mut runnable in runnable_ranges {
14099 let Some(tasks) = cx
14100 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14101 .ok()
14102 else {
14103 continue;
14104 };
14105 let mut tasks = tasks.await;
14106
14107 if prefer_lsp {
14108 tasks.retain(|(task_kind, _)| {
14109 !matches!(task_kind, TaskSourceKind::Language { .. })
14110 });
14111 }
14112 if tasks.is_empty() {
14113 continue;
14114 }
14115
14116 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14117 let Some(row) = snapshot
14118 .buffer_snapshot
14119 .buffer_line_for_row(MultiBufferRow(point.row))
14120 .map(|(_, range)| range.start.row)
14121 else {
14122 continue;
14123 };
14124
14125 let context_range =
14126 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14127 runnable_rows.push((
14128 (runnable.buffer_id, row),
14129 RunnableTasks {
14130 templates: tasks,
14131 offset: snapshot
14132 .buffer_snapshot
14133 .anchor_before(runnable.run_range.start),
14134 context_range,
14135 column: point.column,
14136 extra_variables: runnable.extra_captures,
14137 },
14138 ));
14139 }
14140 runnable_rows
14141 })
14142 }
14143
14144 fn templates_with_tags(
14145 project: &Entity<Project>,
14146 runnable: &mut Runnable,
14147 cx: &mut App,
14148 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14149 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14150 let (worktree_id, file) = project
14151 .buffer_for_id(runnable.buffer, cx)
14152 .and_then(|buffer| buffer.read(cx).file())
14153 .map(|file| (file.worktree_id(cx), file.clone()))
14154 .unzip();
14155
14156 (
14157 project.task_store().read(cx).task_inventory().cloned(),
14158 worktree_id,
14159 file,
14160 )
14161 });
14162
14163 let tags = mem::take(&mut runnable.tags);
14164 let language = runnable.language.clone();
14165 cx.spawn(async move |cx| {
14166 let mut templates_with_tags = Vec::new();
14167 if let Some(inventory) = inventory {
14168 for RunnableTag(tag) in tags {
14169 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14170 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14171 }) else {
14172 return templates_with_tags;
14173 };
14174 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14175 move |(_, template)| {
14176 template.tags.iter().any(|source_tag| source_tag == &tag)
14177 },
14178 ));
14179 }
14180 }
14181 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14182
14183 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14184 // Strongest source wins; if we have worktree tag binding, prefer that to
14185 // global and language bindings;
14186 // if we have a global binding, prefer that to language binding.
14187 let first_mismatch = templates_with_tags
14188 .iter()
14189 .position(|(tag_source, _)| tag_source != leading_tag_source);
14190 if let Some(index) = first_mismatch {
14191 templates_with_tags.truncate(index);
14192 }
14193 }
14194
14195 templates_with_tags
14196 })
14197 }
14198
14199 pub fn move_to_enclosing_bracket(
14200 &mut self,
14201 _: &MoveToEnclosingBracket,
14202 window: &mut Window,
14203 cx: &mut Context<Self>,
14204 ) {
14205 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14206 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14207 s.move_offsets_with(|snapshot, selection| {
14208 let Some(enclosing_bracket_ranges) =
14209 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14210 else {
14211 return;
14212 };
14213
14214 let mut best_length = usize::MAX;
14215 let mut best_inside = false;
14216 let mut best_in_bracket_range = false;
14217 let mut best_destination = None;
14218 for (open, close) in enclosing_bracket_ranges {
14219 let close = close.to_inclusive();
14220 let length = close.end() - open.start;
14221 let inside = selection.start >= open.end && selection.end <= *close.start();
14222 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14223 || close.contains(&selection.head());
14224
14225 // If best is next to a bracket and current isn't, skip
14226 if !in_bracket_range && best_in_bracket_range {
14227 continue;
14228 }
14229
14230 // Prefer smaller lengths unless best is inside and current isn't
14231 if length > best_length && (best_inside || !inside) {
14232 continue;
14233 }
14234
14235 best_length = length;
14236 best_inside = inside;
14237 best_in_bracket_range = in_bracket_range;
14238 best_destination = Some(
14239 if close.contains(&selection.start) && close.contains(&selection.end) {
14240 if inside { open.end } else { open.start }
14241 } else if inside {
14242 *close.start()
14243 } else {
14244 *close.end()
14245 },
14246 );
14247 }
14248
14249 if let Some(destination) = best_destination {
14250 selection.collapse_to(destination, SelectionGoal::None);
14251 }
14252 })
14253 });
14254 }
14255
14256 pub fn undo_selection(
14257 &mut self,
14258 _: &UndoSelection,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) {
14262 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14263 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14264 self.selection_history.mode = SelectionHistoryMode::Undoing;
14265 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14266 this.end_selection(window, cx);
14267 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14268 s.select_anchors(entry.selections.to_vec())
14269 });
14270 });
14271 self.selection_history.mode = SelectionHistoryMode::Normal;
14272
14273 self.select_next_state = entry.select_next_state;
14274 self.select_prev_state = entry.select_prev_state;
14275 self.add_selections_state = entry.add_selections_state;
14276 }
14277 }
14278
14279 pub fn redo_selection(
14280 &mut self,
14281 _: &RedoSelection,
14282 window: &mut Window,
14283 cx: &mut Context<Self>,
14284 ) {
14285 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14286 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14287 self.selection_history.mode = SelectionHistoryMode::Redoing;
14288 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14289 this.end_selection(window, cx);
14290 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14291 s.select_anchors(entry.selections.to_vec())
14292 });
14293 });
14294 self.selection_history.mode = SelectionHistoryMode::Normal;
14295
14296 self.select_next_state = entry.select_next_state;
14297 self.select_prev_state = entry.select_prev_state;
14298 self.add_selections_state = entry.add_selections_state;
14299 }
14300 }
14301
14302 pub fn expand_excerpts(
14303 &mut self,
14304 action: &ExpandExcerpts,
14305 _: &mut Window,
14306 cx: &mut Context<Self>,
14307 ) {
14308 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14309 }
14310
14311 pub fn expand_excerpts_down(
14312 &mut self,
14313 action: &ExpandExcerptsDown,
14314 _: &mut Window,
14315 cx: &mut Context<Self>,
14316 ) {
14317 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14318 }
14319
14320 pub fn expand_excerpts_up(
14321 &mut self,
14322 action: &ExpandExcerptsUp,
14323 _: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) {
14326 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14327 }
14328
14329 pub fn expand_excerpts_for_direction(
14330 &mut self,
14331 lines: u32,
14332 direction: ExpandExcerptDirection,
14333
14334 cx: &mut Context<Self>,
14335 ) {
14336 let selections = self.selections.disjoint_anchors();
14337
14338 let lines = if lines == 0 {
14339 EditorSettings::get_global(cx).expand_excerpt_lines
14340 } else {
14341 lines
14342 };
14343
14344 self.buffer.update(cx, |buffer, cx| {
14345 let snapshot = buffer.snapshot(cx);
14346 let mut excerpt_ids = selections
14347 .iter()
14348 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14349 .collect::<Vec<_>>();
14350 excerpt_ids.sort();
14351 excerpt_ids.dedup();
14352 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14353 })
14354 }
14355
14356 pub fn expand_excerpt(
14357 &mut self,
14358 excerpt: ExcerptId,
14359 direction: ExpandExcerptDirection,
14360 window: &mut Window,
14361 cx: &mut Context<Self>,
14362 ) {
14363 let current_scroll_position = self.scroll_position(cx);
14364 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14365 let mut should_scroll_up = false;
14366
14367 if direction == ExpandExcerptDirection::Down {
14368 let multi_buffer = self.buffer.read(cx);
14369 let snapshot = multi_buffer.snapshot(cx);
14370 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14371 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14372 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14373 let buffer_snapshot = buffer.read(cx).snapshot();
14374 let excerpt_end_row =
14375 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14376 let last_row = buffer_snapshot.max_point().row;
14377 let lines_below = last_row.saturating_sub(excerpt_end_row);
14378 should_scroll_up = lines_below >= lines_to_expand;
14379 }
14380 }
14381 }
14382 }
14383
14384 self.buffer.update(cx, |buffer, cx| {
14385 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14386 });
14387
14388 if should_scroll_up {
14389 let new_scroll_position =
14390 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14391 self.set_scroll_position(new_scroll_position, window, cx);
14392 }
14393 }
14394
14395 pub fn go_to_singleton_buffer_point(
14396 &mut self,
14397 point: Point,
14398 window: &mut Window,
14399 cx: &mut Context<Self>,
14400 ) {
14401 self.go_to_singleton_buffer_range(point..point, window, cx);
14402 }
14403
14404 pub fn go_to_singleton_buffer_range(
14405 &mut self,
14406 range: Range<Point>,
14407 window: &mut Window,
14408 cx: &mut Context<Self>,
14409 ) {
14410 let multibuffer = self.buffer().read(cx);
14411 let Some(buffer) = multibuffer.as_singleton() else {
14412 return;
14413 };
14414 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14415 return;
14416 };
14417 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14418 return;
14419 };
14420 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14421 s.select_anchor_ranges([start..end])
14422 });
14423 }
14424
14425 pub fn go_to_diagnostic(
14426 &mut self,
14427 _: &GoToDiagnostic,
14428 window: &mut Window,
14429 cx: &mut Context<Self>,
14430 ) {
14431 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14432 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14433 }
14434
14435 pub fn go_to_prev_diagnostic(
14436 &mut self,
14437 _: &GoToPreviousDiagnostic,
14438 window: &mut Window,
14439 cx: &mut Context<Self>,
14440 ) {
14441 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14442 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14443 }
14444
14445 pub fn go_to_diagnostic_impl(
14446 &mut self,
14447 direction: Direction,
14448 window: &mut Window,
14449 cx: &mut Context<Self>,
14450 ) {
14451 let buffer = self.buffer.read(cx).snapshot(cx);
14452 let selection = self.selections.newest::<usize>(cx);
14453
14454 let mut active_group_id = None;
14455 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14456 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14457 active_group_id = Some(active_group.group_id);
14458 }
14459 }
14460
14461 fn filtered(
14462 snapshot: EditorSnapshot,
14463 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14464 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14465 diagnostics
14466 .filter(|entry| entry.range.start != entry.range.end)
14467 .filter(|entry| !entry.diagnostic.is_unnecessary)
14468 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14469 }
14470
14471 let snapshot = self.snapshot(window, cx);
14472 let before = filtered(
14473 snapshot.clone(),
14474 buffer
14475 .diagnostics_in_range(0..selection.start)
14476 .filter(|entry| entry.range.start <= selection.start),
14477 );
14478 let after = filtered(
14479 snapshot,
14480 buffer
14481 .diagnostics_in_range(selection.start..buffer.len())
14482 .filter(|entry| entry.range.start >= selection.start),
14483 );
14484
14485 let mut found: Option<DiagnosticEntry<usize>> = None;
14486 if direction == Direction::Prev {
14487 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14488 {
14489 for diagnostic in prev_diagnostics.into_iter().rev() {
14490 if diagnostic.range.start != selection.start
14491 || active_group_id
14492 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14493 {
14494 found = Some(diagnostic);
14495 break 'outer;
14496 }
14497 }
14498 }
14499 } else {
14500 for diagnostic in after.chain(before) {
14501 if diagnostic.range.start != selection.start
14502 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14503 {
14504 found = Some(diagnostic);
14505 break;
14506 }
14507 }
14508 }
14509 let Some(next_diagnostic) = found else {
14510 return;
14511 };
14512
14513 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14514 return;
14515 };
14516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14517 s.select_ranges(vec![
14518 next_diagnostic.range.start..next_diagnostic.range.start,
14519 ])
14520 });
14521 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14522 self.refresh_inline_completion(false, true, window, cx);
14523 }
14524
14525 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14526 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14527 let snapshot = self.snapshot(window, cx);
14528 let selection = self.selections.newest::<Point>(cx);
14529 self.go_to_hunk_before_or_after_position(
14530 &snapshot,
14531 selection.head(),
14532 Direction::Next,
14533 window,
14534 cx,
14535 );
14536 }
14537
14538 pub fn go_to_hunk_before_or_after_position(
14539 &mut self,
14540 snapshot: &EditorSnapshot,
14541 position: Point,
14542 direction: Direction,
14543 window: &mut Window,
14544 cx: &mut Context<Editor>,
14545 ) {
14546 let row = if direction == Direction::Next {
14547 self.hunk_after_position(snapshot, position)
14548 .map(|hunk| hunk.row_range.start)
14549 } else {
14550 self.hunk_before_position(snapshot, position)
14551 };
14552
14553 if let Some(row) = row {
14554 let destination = Point::new(row.0, 0);
14555 let autoscroll = Autoscroll::center();
14556
14557 self.unfold_ranges(&[destination..destination], false, false, cx);
14558 self.change_selections(Some(autoscroll), window, cx, |s| {
14559 s.select_ranges([destination..destination]);
14560 });
14561 }
14562 }
14563
14564 fn hunk_after_position(
14565 &mut self,
14566 snapshot: &EditorSnapshot,
14567 position: Point,
14568 ) -> Option<MultiBufferDiffHunk> {
14569 snapshot
14570 .buffer_snapshot
14571 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14572 .find(|hunk| hunk.row_range.start.0 > position.row)
14573 .or_else(|| {
14574 snapshot
14575 .buffer_snapshot
14576 .diff_hunks_in_range(Point::zero()..position)
14577 .find(|hunk| hunk.row_range.end.0 < position.row)
14578 })
14579 }
14580
14581 fn go_to_prev_hunk(
14582 &mut self,
14583 _: &GoToPreviousHunk,
14584 window: &mut Window,
14585 cx: &mut Context<Self>,
14586 ) {
14587 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14588 let snapshot = self.snapshot(window, cx);
14589 let selection = self.selections.newest::<Point>(cx);
14590 self.go_to_hunk_before_or_after_position(
14591 &snapshot,
14592 selection.head(),
14593 Direction::Prev,
14594 window,
14595 cx,
14596 );
14597 }
14598
14599 fn hunk_before_position(
14600 &mut self,
14601 snapshot: &EditorSnapshot,
14602 position: Point,
14603 ) -> Option<MultiBufferRow> {
14604 snapshot
14605 .buffer_snapshot
14606 .diff_hunk_before(position)
14607 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14608 }
14609
14610 fn go_to_next_change(
14611 &mut self,
14612 _: &GoToNextChange,
14613 window: &mut Window,
14614 cx: &mut Context<Self>,
14615 ) {
14616 if let Some(selections) = self
14617 .change_list
14618 .next_change(1, Direction::Next)
14619 .map(|s| s.to_vec())
14620 {
14621 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14622 let map = s.display_map();
14623 s.select_display_ranges(selections.iter().map(|a| {
14624 let point = a.to_display_point(&map);
14625 point..point
14626 }))
14627 })
14628 }
14629 }
14630
14631 fn go_to_previous_change(
14632 &mut self,
14633 _: &GoToPreviousChange,
14634 window: &mut Window,
14635 cx: &mut Context<Self>,
14636 ) {
14637 if let Some(selections) = self
14638 .change_list
14639 .next_change(1, Direction::Prev)
14640 .map(|s| s.to_vec())
14641 {
14642 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14643 let map = s.display_map();
14644 s.select_display_ranges(selections.iter().map(|a| {
14645 let point = a.to_display_point(&map);
14646 point..point
14647 }))
14648 })
14649 }
14650 }
14651
14652 fn go_to_line<T: 'static>(
14653 &mut self,
14654 position: Anchor,
14655 highlight_color: Option<Hsla>,
14656 window: &mut Window,
14657 cx: &mut Context<Self>,
14658 ) {
14659 let snapshot = self.snapshot(window, cx).display_snapshot;
14660 let position = position.to_point(&snapshot.buffer_snapshot);
14661 let start = snapshot
14662 .buffer_snapshot
14663 .clip_point(Point::new(position.row, 0), Bias::Left);
14664 let end = start + Point::new(1, 0);
14665 let start = snapshot.buffer_snapshot.anchor_before(start);
14666 let end = snapshot.buffer_snapshot.anchor_before(end);
14667
14668 self.highlight_rows::<T>(
14669 start..end,
14670 highlight_color
14671 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14672 Default::default(),
14673 cx,
14674 );
14675
14676 if self.buffer.read(cx).is_singleton() {
14677 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14678 }
14679 }
14680
14681 pub fn go_to_definition(
14682 &mut self,
14683 _: &GoToDefinition,
14684 window: &mut Window,
14685 cx: &mut Context<Self>,
14686 ) -> Task<Result<Navigated>> {
14687 let definition =
14688 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14689 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14690 cx.spawn_in(window, async move |editor, cx| {
14691 if definition.await? == Navigated::Yes {
14692 return Ok(Navigated::Yes);
14693 }
14694 match fallback_strategy {
14695 GoToDefinitionFallback::None => Ok(Navigated::No),
14696 GoToDefinitionFallback::FindAllReferences => {
14697 match editor.update_in(cx, |editor, window, cx| {
14698 editor.find_all_references(&FindAllReferences, window, cx)
14699 })? {
14700 Some(references) => references.await,
14701 None => Ok(Navigated::No),
14702 }
14703 }
14704 }
14705 })
14706 }
14707
14708 pub fn go_to_declaration(
14709 &mut self,
14710 _: &GoToDeclaration,
14711 window: &mut Window,
14712 cx: &mut Context<Self>,
14713 ) -> Task<Result<Navigated>> {
14714 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14715 }
14716
14717 pub fn go_to_declaration_split(
14718 &mut self,
14719 _: &GoToDeclaration,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) -> Task<Result<Navigated>> {
14723 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14724 }
14725
14726 pub fn go_to_implementation(
14727 &mut self,
14728 _: &GoToImplementation,
14729 window: &mut Window,
14730 cx: &mut Context<Self>,
14731 ) -> Task<Result<Navigated>> {
14732 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14733 }
14734
14735 pub fn go_to_implementation_split(
14736 &mut self,
14737 _: &GoToImplementationSplit,
14738 window: &mut Window,
14739 cx: &mut Context<Self>,
14740 ) -> Task<Result<Navigated>> {
14741 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14742 }
14743
14744 pub fn go_to_type_definition(
14745 &mut self,
14746 _: &GoToTypeDefinition,
14747 window: &mut Window,
14748 cx: &mut Context<Self>,
14749 ) -> Task<Result<Navigated>> {
14750 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14751 }
14752
14753 pub fn go_to_definition_split(
14754 &mut self,
14755 _: &GoToDefinitionSplit,
14756 window: &mut Window,
14757 cx: &mut Context<Self>,
14758 ) -> Task<Result<Navigated>> {
14759 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14760 }
14761
14762 pub fn go_to_type_definition_split(
14763 &mut self,
14764 _: &GoToTypeDefinitionSplit,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) -> Task<Result<Navigated>> {
14768 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14769 }
14770
14771 fn go_to_definition_of_kind(
14772 &mut self,
14773 kind: GotoDefinitionKind,
14774 split: bool,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) -> Task<Result<Navigated>> {
14778 let Some(provider) = self.semantics_provider.clone() else {
14779 return Task::ready(Ok(Navigated::No));
14780 };
14781 let head = self.selections.newest::<usize>(cx).head();
14782 let buffer = self.buffer.read(cx);
14783 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14784 text_anchor
14785 } else {
14786 return Task::ready(Ok(Navigated::No));
14787 };
14788
14789 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14790 return Task::ready(Ok(Navigated::No));
14791 };
14792
14793 cx.spawn_in(window, async move |editor, cx| {
14794 let definitions = definitions.await?;
14795 let navigated = editor
14796 .update_in(cx, |editor, window, cx| {
14797 editor.navigate_to_hover_links(
14798 Some(kind),
14799 definitions
14800 .into_iter()
14801 .filter(|location| {
14802 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14803 })
14804 .map(HoverLink::Text)
14805 .collect::<Vec<_>>(),
14806 split,
14807 window,
14808 cx,
14809 )
14810 })?
14811 .await?;
14812 anyhow::Ok(navigated)
14813 })
14814 }
14815
14816 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14817 let selection = self.selections.newest_anchor();
14818 let head = selection.head();
14819 let tail = selection.tail();
14820
14821 let Some((buffer, start_position)) =
14822 self.buffer.read(cx).text_anchor_for_position(head, cx)
14823 else {
14824 return;
14825 };
14826
14827 let end_position = if head != tail {
14828 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14829 return;
14830 };
14831 Some(pos)
14832 } else {
14833 None
14834 };
14835
14836 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14837 let url = if let Some(end_pos) = end_position {
14838 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14839 } else {
14840 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14841 };
14842
14843 if let Some(url) = url {
14844 editor.update(cx, |_, cx| {
14845 cx.open_url(&url);
14846 })
14847 } else {
14848 Ok(())
14849 }
14850 });
14851
14852 url_finder.detach();
14853 }
14854
14855 pub fn open_selected_filename(
14856 &mut self,
14857 _: &OpenSelectedFilename,
14858 window: &mut Window,
14859 cx: &mut Context<Self>,
14860 ) {
14861 let Some(workspace) = self.workspace() else {
14862 return;
14863 };
14864
14865 let position = self.selections.newest_anchor().head();
14866
14867 let Some((buffer, buffer_position)) =
14868 self.buffer.read(cx).text_anchor_for_position(position, cx)
14869 else {
14870 return;
14871 };
14872
14873 let project = self.project.clone();
14874
14875 cx.spawn_in(window, async move |_, cx| {
14876 let result = find_file(&buffer, project, buffer_position, cx).await;
14877
14878 if let Some((_, path)) = result {
14879 workspace
14880 .update_in(cx, |workspace, window, cx| {
14881 workspace.open_resolved_path(path, window, cx)
14882 })?
14883 .await?;
14884 }
14885 anyhow::Ok(())
14886 })
14887 .detach();
14888 }
14889
14890 pub(crate) fn navigate_to_hover_links(
14891 &mut self,
14892 kind: Option<GotoDefinitionKind>,
14893 mut definitions: Vec<HoverLink>,
14894 split: bool,
14895 window: &mut Window,
14896 cx: &mut Context<Editor>,
14897 ) -> Task<Result<Navigated>> {
14898 // If there is one definition, just open it directly
14899 if definitions.len() == 1 {
14900 let definition = definitions.pop().unwrap();
14901
14902 enum TargetTaskResult {
14903 Location(Option<Location>),
14904 AlreadyNavigated,
14905 }
14906
14907 let target_task = match definition {
14908 HoverLink::Text(link) => {
14909 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14910 }
14911 HoverLink::InlayHint(lsp_location, server_id) => {
14912 let computation =
14913 self.compute_target_location(lsp_location, server_id, window, cx);
14914 cx.background_spawn(async move {
14915 let location = computation.await?;
14916 Ok(TargetTaskResult::Location(location))
14917 })
14918 }
14919 HoverLink::Url(url) => {
14920 cx.open_url(&url);
14921 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14922 }
14923 HoverLink::File(path) => {
14924 if let Some(workspace) = self.workspace() {
14925 cx.spawn_in(window, async move |_, cx| {
14926 workspace
14927 .update_in(cx, |workspace, window, cx| {
14928 workspace.open_resolved_path(path, window, cx)
14929 })?
14930 .await
14931 .map(|_| TargetTaskResult::AlreadyNavigated)
14932 })
14933 } else {
14934 Task::ready(Ok(TargetTaskResult::Location(None)))
14935 }
14936 }
14937 };
14938 cx.spawn_in(window, async move |editor, cx| {
14939 let target = match target_task.await.context("target resolution task")? {
14940 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14941 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14942 TargetTaskResult::Location(Some(target)) => target,
14943 };
14944
14945 editor.update_in(cx, |editor, window, cx| {
14946 let Some(workspace) = editor.workspace() else {
14947 return Navigated::No;
14948 };
14949 let pane = workspace.read(cx).active_pane().clone();
14950
14951 let range = target.range.to_point(target.buffer.read(cx));
14952 let range = editor.range_for_match(&range);
14953 let range = collapse_multiline_range(range);
14954
14955 if !split
14956 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14957 {
14958 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14959 } else {
14960 window.defer(cx, move |window, cx| {
14961 let target_editor: Entity<Self> =
14962 workspace.update(cx, |workspace, cx| {
14963 let pane = if split {
14964 workspace.adjacent_pane(window, cx)
14965 } else {
14966 workspace.active_pane().clone()
14967 };
14968
14969 workspace.open_project_item(
14970 pane,
14971 target.buffer.clone(),
14972 true,
14973 true,
14974 window,
14975 cx,
14976 )
14977 });
14978 target_editor.update(cx, |target_editor, cx| {
14979 // When selecting a definition in a different buffer, disable the nav history
14980 // to avoid creating a history entry at the previous cursor location.
14981 pane.update(cx, |pane, _| pane.disable_history());
14982 target_editor.go_to_singleton_buffer_range(range, window, cx);
14983 pane.update(cx, |pane, _| pane.enable_history());
14984 });
14985 });
14986 }
14987 Navigated::Yes
14988 })
14989 })
14990 } else if !definitions.is_empty() {
14991 cx.spawn_in(window, async move |editor, cx| {
14992 let (title, location_tasks, workspace) = editor
14993 .update_in(cx, |editor, window, cx| {
14994 let tab_kind = match kind {
14995 Some(GotoDefinitionKind::Implementation) => "Implementations",
14996 _ => "Definitions",
14997 };
14998 let title = definitions
14999 .iter()
15000 .find_map(|definition| match definition {
15001 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15002 let buffer = origin.buffer.read(cx);
15003 format!(
15004 "{} for {}",
15005 tab_kind,
15006 buffer
15007 .text_for_range(origin.range.clone())
15008 .collect::<String>()
15009 )
15010 }),
15011 HoverLink::InlayHint(_, _) => None,
15012 HoverLink::Url(_) => None,
15013 HoverLink::File(_) => None,
15014 })
15015 .unwrap_or(tab_kind.to_string());
15016 let location_tasks = definitions
15017 .into_iter()
15018 .map(|definition| match definition {
15019 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15020 HoverLink::InlayHint(lsp_location, server_id) => editor
15021 .compute_target_location(lsp_location, server_id, window, cx),
15022 HoverLink::Url(_) => Task::ready(Ok(None)),
15023 HoverLink::File(_) => Task::ready(Ok(None)),
15024 })
15025 .collect::<Vec<_>>();
15026 (title, location_tasks, editor.workspace().clone())
15027 })
15028 .context("location tasks preparation")?;
15029
15030 let locations = future::join_all(location_tasks)
15031 .await
15032 .into_iter()
15033 .filter_map(|location| location.transpose())
15034 .collect::<Result<_>>()
15035 .context("location tasks")?;
15036
15037 let Some(workspace) = workspace else {
15038 return Ok(Navigated::No);
15039 };
15040 let opened = workspace
15041 .update_in(cx, |workspace, window, cx| {
15042 Self::open_locations_in_multibuffer(
15043 workspace,
15044 locations,
15045 title,
15046 split,
15047 MultibufferSelectionMode::First,
15048 window,
15049 cx,
15050 )
15051 })
15052 .ok();
15053
15054 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15055 })
15056 } else {
15057 Task::ready(Ok(Navigated::No))
15058 }
15059 }
15060
15061 fn compute_target_location(
15062 &self,
15063 lsp_location: lsp::Location,
15064 server_id: LanguageServerId,
15065 window: &mut Window,
15066 cx: &mut Context<Self>,
15067 ) -> Task<anyhow::Result<Option<Location>>> {
15068 let Some(project) = self.project.clone() else {
15069 return Task::ready(Ok(None));
15070 };
15071
15072 cx.spawn_in(window, async move |editor, cx| {
15073 let location_task = editor.update(cx, |_, cx| {
15074 project.update(cx, |project, cx| {
15075 let language_server_name = project
15076 .language_server_statuses(cx)
15077 .find(|(id, _)| server_id == *id)
15078 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15079 language_server_name.map(|language_server_name| {
15080 project.open_local_buffer_via_lsp(
15081 lsp_location.uri.clone(),
15082 server_id,
15083 language_server_name,
15084 cx,
15085 )
15086 })
15087 })
15088 })?;
15089 let location = match location_task {
15090 Some(task) => Some({
15091 let target_buffer_handle = task.await.context("open local buffer")?;
15092 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15093 let target_start = target_buffer
15094 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15095 let target_end = target_buffer
15096 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15097 target_buffer.anchor_after(target_start)
15098 ..target_buffer.anchor_before(target_end)
15099 })?;
15100 Location {
15101 buffer: target_buffer_handle,
15102 range,
15103 }
15104 }),
15105 None => None,
15106 };
15107 Ok(location)
15108 })
15109 }
15110
15111 pub fn find_all_references(
15112 &mut self,
15113 _: &FindAllReferences,
15114 window: &mut Window,
15115 cx: &mut Context<Self>,
15116 ) -> Option<Task<Result<Navigated>>> {
15117 let selection = self.selections.newest::<usize>(cx);
15118 let multi_buffer = self.buffer.read(cx);
15119 let head = selection.head();
15120
15121 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15122 let head_anchor = multi_buffer_snapshot.anchor_at(
15123 head,
15124 if head < selection.tail() {
15125 Bias::Right
15126 } else {
15127 Bias::Left
15128 },
15129 );
15130
15131 match self
15132 .find_all_references_task_sources
15133 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15134 {
15135 Ok(_) => {
15136 log::info!(
15137 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15138 );
15139 return None;
15140 }
15141 Err(i) => {
15142 self.find_all_references_task_sources.insert(i, head_anchor);
15143 }
15144 }
15145
15146 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15147 let workspace = self.workspace()?;
15148 let project = workspace.read(cx).project().clone();
15149 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15150 Some(cx.spawn_in(window, async move |editor, cx| {
15151 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15152 if let Ok(i) = editor
15153 .find_all_references_task_sources
15154 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15155 {
15156 editor.find_all_references_task_sources.remove(i);
15157 }
15158 });
15159
15160 let locations = references.await?;
15161 if locations.is_empty() {
15162 return anyhow::Ok(Navigated::No);
15163 }
15164
15165 workspace.update_in(cx, |workspace, window, cx| {
15166 let title = locations
15167 .first()
15168 .as_ref()
15169 .map(|location| {
15170 let buffer = location.buffer.read(cx);
15171 format!(
15172 "References to `{}`",
15173 buffer
15174 .text_for_range(location.range.clone())
15175 .collect::<String>()
15176 )
15177 })
15178 .unwrap();
15179 Self::open_locations_in_multibuffer(
15180 workspace,
15181 locations,
15182 title,
15183 false,
15184 MultibufferSelectionMode::First,
15185 window,
15186 cx,
15187 );
15188 Navigated::Yes
15189 })
15190 }))
15191 }
15192
15193 /// Opens a multibuffer with the given project locations in it
15194 pub fn open_locations_in_multibuffer(
15195 workspace: &mut Workspace,
15196 mut locations: Vec<Location>,
15197 title: String,
15198 split: bool,
15199 multibuffer_selection_mode: MultibufferSelectionMode,
15200 window: &mut Window,
15201 cx: &mut Context<Workspace>,
15202 ) {
15203 // If there are multiple definitions, open them in a multibuffer
15204 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15205 let mut locations = locations.into_iter().peekable();
15206 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15207 let capability = workspace.project().read(cx).capability();
15208
15209 let excerpt_buffer = cx.new(|cx| {
15210 let mut multibuffer = MultiBuffer::new(capability);
15211 while let Some(location) = locations.next() {
15212 let buffer = location.buffer.read(cx);
15213 let mut ranges_for_buffer = Vec::new();
15214 let range = location.range.to_point(buffer);
15215 ranges_for_buffer.push(range.clone());
15216
15217 while let Some(next_location) = locations.peek() {
15218 if next_location.buffer == location.buffer {
15219 ranges_for_buffer.push(next_location.range.to_point(buffer));
15220 locations.next();
15221 } else {
15222 break;
15223 }
15224 }
15225
15226 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15227 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15228 PathKey::for_buffer(&location.buffer, cx),
15229 location.buffer.clone(),
15230 ranges_for_buffer,
15231 DEFAULT_MULTIBUFFER_CONTEXT,
15232 cx,
15233 );
15234 ranges.extend(new_ranges)
15235 }
15236
15237 multibuffer.with_title(title)
15238 });
15239
15240 let editor = cx.new(|cx| {
15241 Editor::for_multibuffer(
15242 excerpt_buffer,
15243 Some(workspace.project().clone()),
15244 window,
15245 cx,
15246 )
15247 });
15248 editor.update(cx, |editor, cx| {
15249 match multibuffer_selection_mode {
15250 MultibufferSelectionMode::First => {
15251 if let Some(first_range) = ranges.first() {
15252 editor.change_selections(None, window, cx, |selections| {
15253 selections.clear_disjoint();
15254 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15255 });
15256 }
15257 editor.highlight_background::<Self>(
15258 &ranges,
15259 |theme| theme.editor_highlighted_line_background,
15260 cx,
15261 );
15262 }
15263 MultibufferSelectionMode::All => {
15264 editor.change_selections(None, window, cx, |selections| {
15265 selections.clear_disjoint();
15266 selections.select_anchor_ranges(ranges);
15267 });
15268 }
15269 }
15270 editor.register_buffers_with_language_servers(cx);
15271 });
15272
15273 let item = Box::new(editor);
15274 let item_id = item.item_id();
15275
15276 if split {
15277 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15278 } else {
15279 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15280 let (preview_item_id, preview_item_idx) =
15281 workspace.active_pane().read_with(cx, |pane, _| {
15282 (pane.preview_item_id(), pane.preview_item_idx())
15283 });
15284
15285 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15286
15287 if let Some(preview_item_id) = preview_item_id {
15288 workspace.active_pane().update(cx, |pane, cx| {
15289 pane.remove_item(preview_item_id, false, false, window, cx);
15290 });
15291 }
15292 } else {
15293 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15294 }
15295 }
15296 workspace.active_pane().update(cx, |pane, cx| {
15297 pane.set_preview_item_id(Some(item_id), cx);
15298 });
15299 }
15300
15301 pub fn rename(
15302 &mut self,
15303 _: &Rename,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) -> Option<Task<Result<()>>> {
15307 use language::ToOffset as _;
15308
15309 let provider = self.semantics_provider.clone()?;
15310 let selection = self.selections.newest_anchor().clone();
15311 let (cursor_buffer, cursor_buffer_position) = self
15312 .buffer
15313 .read(cx)
15314 .text_anchor_for_position(selection.head(), cx)?;
15315 let (tail_buffer, cursor_buffer_position_end) = self
15316 .buffer
15317 .read(cx)
15318 .text_anchor_for_position(selection.tail(), cx)?;
15319 if tail_buffer != cursor_buffer {
15320 return None;
15321 }
15322
15323 let snapshot = cursor_buffer.read(cx).snapshot();
15324 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15325 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15326 let prepare_rename = provider
15327 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15328 .unwrap_or_else(|| Task::ready(Ok(None)));
15329 drop(snapshot);
15330
15331 Some(cx.spawn_in(window, async move |this, cx| {
15332 let rename_range = if let Some(range) = prepare_rename.await? {
15333 Some(range)
15334 } else {
15335 this.update(cx, |this, cx| {
15336 let buffer = this.buffer.read(cx).snapshot(cx);
15337 let mut buffer_highlights = this
15338 .document_highlights_for_position(selection.head(), &buffer)
15339 .filter(|highlight| {
15340 highlight.start.excerpt_id == selection.head().excerpt_id
15341 && highlight.end.excerpt_id == selection.head().excerpt_id
15342 });
15343 buffer_highlights
15344 .next()
15345 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15346 })?
15347 };
15348 if let Some(rename_range) = rename_range {
15349 this.update_in(cx, |this, window, cx| {
15350 let snapshot = cursor_buffer.read(cx).snapshot();
15351 let rename_buffer_range = rename_range.to_offset(&snapshot);
15352 let cursor_offset_in_rename_range =
15353 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15354 let cursor_offset_in_rename_range_end =
15355 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15356
15357 this.take_rename(false, window, cx);
15358 let buffer = this.buffer.read(cx).read(cx);
15359 let cursor_offset = selection.head().to_offset(&buffer);
15360 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15361 let rename_end = rename_start + rename_buffer_range.len();
15362 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15363 let mut old_highlight_id = None;
15364 let old_name: Arc<str> = buffer
15365 .chunks(rename_start..rename_end, true)
15366 .map(|chunk| {
15367 if old_highlight_id.is_none() {
15368 old_highlight_id = chunk.syntax_highlight_id;
15369 }
15370 chunk.text
15371 })
15372 .collect::<String>()
15373 .into();
15374
15375 drop(buffer);
15376
15377 // Position the selection in the rename editor so that it matches the current selection.
15378 this.show_local_selections = false;
15379 let rename_editor = cx.new(|cx| {
15380 let mut editor = Editor::single_line(window, cx);
15381 editor.buffer.update(cx, |buffer, cx| {
15382 buffer.edit([(0..0, old_name.clone())], None, cx)
15383 });
15384 let rename_selection_range = match cursor_offset_in_rename_range
15385 .cmp(&cursor_offset_in_rename_range_end)
15386 {
15387 Ordering::Equal => {
15388 editor.select_all(&SelectAll, window, cx);
15389 return editor;
15390 }
15391 Ordering::Less => {
15392 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15393 }
15394 Ordering::Greater => {
15395 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15396 }
15397 };
15398 if rename_selection_range.end > old_name.len() {
15399 editor.select_all(&SelectAll, window, cx);
15400 } else {
15401 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15402 s.select_ranges([rename_selection_range]);
15403 });
15404 }
15405 editor
15406 });
15407 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15408 if e == &EditorEvent::Focused {
15409 cx.emit(EditorEvent::FocusedIn)
15410 }
15411 })
15412 .detach();
15413
15414 let write_highlights =
15415 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15416 let read_highlights =
15417 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15418 let ranges = write_highlights
15419 .iter()
15420 .flat_map(|(_, ranges)| ranges.iter())
15421 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15422 .cloned()
15423 .collect();
15424
15425 this.highlight_text::<Rename>(
15426 ranges,
15427 HighlightStyle {
15428 fade_out: Some(0.6),
15429 ..Default::default()
15430 },
15431 cx,
15432 );
15433 let rename_focus_handle = rename_editor.focus_handle(cx);
15434 window.focus(&rename_focus_handle);
15435 let block_id = this.insert_blocks(
15436 [BlockProperties {
15437 style: BlockStyle::Flex,
15438 placement: BlockPlacement::Below(range.start),
15439 height: Some(1),
15440 render: Arc::new({
15441 let rename_editor = rename_editor.clone();
15442 move |cx: &mut BlockContext| {
15443 let mut text_style = cx.editor_style.text.clone();
15444 if let Some(highlight_style) = old_highlight_id
15445 .and_then(|h| h.style(&cx.editor_style.syntax))
15446 {
15447 text_style = text_style.highlight(highlight_style);
15448 }
15449 div()
15450 .block_mouse_except_scroll()
15451 .pl(cx.anchor_x)
15452 .child(EditorElement::new(
15453 &rename_editor,
15454 EditorStyle {
15455 background: cx.theme().system().transparent,
15456 local_player: cx.editor_style.local_player,
15457 text: text_style,
15458 scrollbar_width: cx.editor_style.scrollbar_width,
15459 syntax: cx.editor_style.syntax.clone(),
15460 status: cx.editor_style.status.clone(),
15461 inlay_hints_style: HighlightStyle {
15462 font_weight: Some(FontWeight::BOLD),
15463 ..make_inlay_hints_style(cx.app)
15464 },
15465 inline_completion_styles: make_suggestion_styles(
15466 cx.app,
15467 ),
15468 ..EditorStyle::default()
15469 },
15470 ))
15471 .into_any_element()
15472 }
15473 }),
15474 priority: 0,
15475 render_in_minimap: true,
15476 }],
15477 Some(Autoscroll::fit()),
15478 cx,
15479 )[0];
15480 this.pending_rename = Some(RenameState {
15481 range,
15482 old_name,
15483 editor: rename_editor,
15484 block_id,
15485 });
15486 })?;
15487 }
15488
15489 Ok(())
15490 }))
15491 }
15492
15493 pub fn confirm_rename(
15494 &mut self,
15495 _: &ConfirmRename,
15496 window: &mut Window,
15497 cx: &mut Context<Self>,
15498 ) -> Option<Task<Result<()>>> {
15499 let rename = self.take_rename(false, window, cx)?;
15500 let workspace = self.workspace()?.downgrade();
15501 let (buffer, start) = self
15502 .buffer
15503 .read(cx)
15504 .text_anchor_for_position(rename.range.start, cx)?;
15505 let (end_buffer, _) = self
15506 .buffer
15507 .read(cx)
15508 .text_anchor_for_position(rename.range.end, cx)?;
15509 if buffer != end_buffer {
15510 return None;
15511 }
15512
15513 let old_name = rename.old_name;
15514 let new_name = rename.editor.read(cx).text(cx);
15515
15516 let rename = self.semantics_provider.as_ref()?.perform_rename(
15517 &buffer,
15518 start,
15519 new_name.clone(),
15520 cx,
15521 )?;
15522
15523 Some(cx.spawn_in(window, async move |editor, cx| {
15524 let project_transaction = rename.await?;
15525 Self::open_project_transaction(
15526 &editor,
15527 workspace,
15528 project_transaction,
15529 format!("Rename: {} → {}", old_name, new_name),
15530 cx,
15531 )
15532 .await?;
15533
15534 editor.update(cx, |editor, cx| {
15535 editor.refresh_document_highlights(cx);
15536 })?;
15537 Ok(())
15538 }))
15539 }
15540
15541 fn take_rename(
15542 &mut self,
15543 moving_cursor: bool,
15544 window: &mut Window,
15545 cx: &mut Context<Self>,
15546 ) -> Option<RenameState> {
15547 let rename = self.pending_rename.take()?;
15548 if rename.editor.focus_handle(cx).is_focused(window) {
15549 window.focus(&self.focus_handle);
15550 }
15551
15552 self.remove_blocks(
15553 [rename.block_id].into_iter().collect(),
15554 Some(Autoscroll::fit()),
15555 cx,
15556 );
15557 self.clear_highlights::<Rename>(cx);
15558 self.show_local_selections = true;
15559
15560 if moving_cursor {
15561 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15562 editor.selections.newest::<usize>(cx).head()
15563 });
15564
15565 // Update the selection to match the position of the selection inside
15566 // the rename editor.
15567 let snapshot = self.buffer.read(cx).read(cx);
15568 let rename_range = rename.range.to_offset(&snapshot);
15569 let cursor_in_editor = snapshot
15570 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15571 .min(rename_range.end);
15572 drop(snapshot);
15573
15574 self.change_selections(None, window, cx, |s| {
15575 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15576 });
15577 } else {
15578 self.refresh_document_highlights(cx);
15579 }
15580
15581 Some(rename)
15582 }
15583
15584 pub fn pending_rename(&self) -> Option<&RenameState> {
15585 self.pending_rename.as_ref()
15586 }
15587
15588 fn format(
15589 &mut self,
15590 _: &Format,
15591 window: &mut Window,
15592 cx: &mut Context<Self>,
15593 ) -> Option<Task<Result<()>>> {
15594 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15595
15596 let project = match &self.project {
15597 Some(project) => project.clone(),
15598 None => return None,
15599 };
15600
15601 Some(self.perform_format(
15602 project,
15603 FormatTrigger::Manual,
15604 FormatTarget::Buffers,
15605 window,
15606 cx,
15607 ))
15608 }
15609
15610 fn format_selections(
15611 &mut self,
15612 _: &FormatSelections,
15613 window: &mut Window,
15614 cx: &mut Context<Self>,
15615 ) -> Option<Task<Result<()>>> {
15616 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15617
15618 let project = match &self.project {
15619 Some(project) => project.clone(),
15620 None => return None,
15621 };
15622
15623 let ranges = self
15624 .selections
15625 .all_adjusted(cx)
15626 .into_iter()
15627 .map(|selection| selection.range())
15628 .collect_vec();
15629
15630 Some(self.perform_format(
15631 project,
15632 FormatTrigger::Manual,
15633 FormatTarget::Ranges(ranges),
15634 window,
15635 cx,
15636 ))
15637 }
15638
15639 fn perform_format(
15640 &mut self,
15641 project: Entity<Project>,
15642 trigger: FormatTrigger,
15643 target: FormatTarget,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) -> Task<Result<()>> {
15647 let buffer = self.buffer.clone();
15648 let (buffers, target) = match target {
15649 FormatTarget::Buffers => {
15650 let mut buffers = buffer.read(cx).all_buffers();
15651 if trigger == FormatTrigger::Save {
15652 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15653 }
15654 (buffers, LspFormatTarget::Buffers)
15655 }
15656 FormatTarget::Ranges(selection_ranges) => {
15657 let multi_buffer = buffer.read(cx);
15658 let snapshot = multi_buffer.read(cx);
15659 let mut buffers = HashSet::default();
15660 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15661 BTreeMap::new();
15662 for selection_range in selection_ranges {
15663 for (buffer, buffer_range, _) in
15664 snapshot.range_to_buffer_ranges(selection_range)
15665 {
15666 let buffer_id = buffer.remote_id();
15667 let start = buffer.anchor_before(buffer_range.start);
15668 let end = buffer.anchor_after(buffer_range.end);
15669 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15670 buffer_id_to_ranges
15671 .entry(buffer_id)
15672 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15673 .or_insert_with(|| vec![start..end]);
15674 }
15675 }
15676 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15677 }
15678 };
15679
15680 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15681 let selections_prev = transaction_id_prev
15682 .and_then(|transaction_id_prev| {
15683 // default to selections as they were after the last edit, if we have them,
15684 // instead of how they are now.
15685 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15686 // will take you back to where you made the last edit, instead of staying where you scrolled
15687 self.selection_history
15688 .transaction(transaction_id_prev)
15689 .map(|t| t.0.clone())
15690 })
15691 .unwrap_or_else(|| {
15692 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15693 self.selections.disjoint_anchors()
15694 });
15695
15696 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15697 let format = project.update(cx, |project, cx| {
15698 project.format(buffers, target, true, trigger, cx)
15699 });
15700
15701 cx.spawn_in(window, async move |editor, cx| {
15702 let transaction = futures::select_biased! {
15703 transaction = format.log_err().fuse() => transaction,
15704 () = timeout => {
15705 log::warn!("timed out waiting for formatting");
15706 None
15707 }
15708 };
15709
15710 buffer
15711 .update(cx, |buffer, cx| {
15712 if let Some(transaction) = transaction {
15713 if !buffer.is_singleton() {
15714 buffer.push_transaction(&transaction.0, cx);
15715 }
15716 }
15717 cx.notify();
15718 })
15719 .ok();
15720
15721 if let Some(transaction_id_now) =
15722 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15723 {
15724 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15725 if has_new_transaction {
15726 _ = editor.update(cx, |editor, _| {
15727 editor
15728 .selection_history
15729 .insert_transaction(transaction_id_now, selections_prev);
15730 });
15731 }
15732 }
15733
15734 Ok(())
15735 })
15736 }
15737
15738 fn organize_imports(
15739 &mut self,
15740 _: &OrganizeImports,
15741 window: &mut Window,
15742 cx: &mut Context<Self>,
15743 ) -> Option<Task<Result<()>>> {
15744 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15745 let project = match &self.project {
15746 Some(project) => project.clone(),
15747 None => return None,
15748 };
15749 Some(self.perform_code_action_kind(
15750 project,
15751 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15752 window,
15753 cx,
15754 ))
15755 }
15756
15757 fn perform_code_action_kind(
15758 &mut self,
15759 project: Entity<Project>,
15760 kind: CodeActionKind,
15761 window: &mut Window,
15762 cx: &mut Context<Self>,
15763 ) -> Task<Result<()>> {
15764 let buffer = self.buffer.clone();
15765 let buffers = buffer.read(cx).all_buffers();
15766 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15767 let apply_action = project.update(cx, |project, cx| {
15768 project.apply_code_action_kind(buffers, kind, true, cx)
15769 });
15770 cx.spawn_in(window, async move |_, cx| {
15771 let transaction = futures::select_biased! {
15772 () = timeout => {
15773 log::warn!("timed out waiting for executing code action");
15774 None
15775 }
15776 transaction = apply_action.log_err().fuse() => transaction,
15777 };
15778 buffer
15779 .update(cx, |buffer, cx| {
15780 // check if we need this
15781 if let Some(transaction) = transaction {
15782 if !buffer.is_singleton() {
15783 buffer.push_transaction(&transaction.0, cx);
15784 }
15785 }
15786 cx.notify();
15787 })
15788 .ok();
15789 Ok(())
15790 })
15791 }
15792
15793 fn restart_language_server(
15794 &mut self,
15795 _: &RestartLanguageServer,
15796 _: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) {
15799 if let Some(project) = self.project.clone() {
15800 self.buffer.update(cx, |multi_buffer, cx| {
15801 project.update(cx, |project, cx| {
15802 project.restart_language_servers_for_buffers(
15803 multi_buffer.all_buffers().into_iter().collect(),
15804 cx,
15805 );
15806 });
15807 })
15808 }
15809 }
15810
15811 fn stop_language_server(
15812 &mut self,
15813 _: &StopLanguageServer,
15814 _: &mut Window,
15815 cx: &mut Context<Self>,
15816 ) {
15817 if let Some(project) = self.project.clone() {
15818 self.buffer.update(cx, |multi_buffer, cx| {
15819 project.update(cx, |project, cx| {
15820 project.stop_language_servers_for_buffers(
15821 multi_buffer.all_buffers().into_iter().collect(),
15822 cx,
15823 );
15824 cx.emit(project::Event::RefreshInlayHints);
15825 });
15826 });
15827 }
15828 }
15829
15830 fn cancel_language_server_work(
15831 workspace: &mut Workspace,
15832 _: &actions::CancelLanguageServerWork,
15833 _: &mut Window,
15834 cx: &mut Context<Workspace>,
15835 ) {
15836 let project = workspace.project();
15837 let buffers = workspace
15838 .active_item(cx)
15839 .and_then(|item| item.act_as::<Editor>(cx))
15840 .map_or(HashSet::default(), |editor| {
15841 editor.read(cx).buffer.read(cx).all_buffers()
15842 });
15843 project.update(cx, |project, cx| {
15844 project.cancel_language_server_work_for_buffers(buffers, cx);
15845 });
15846 }
15847
15848 fn show_character_palette(
15849 &mut self,
15850 _: &ShowCharacterPalette,
15851 window: &mut Window,
15852 _: &mut Context<Self>,
15853 ) {
15854 window.show_character_palette();
15855 }
15856
15857 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15858 if self.mode.is_minimap() {
15859 return;
15860 }
15861
15862 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15863 let buffer = self.buffer.read(cx).snapshot(cx);
15864 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15865 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15866 let is_valid = buffer
15867 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15868 .any(|entry| {
15869 entry.diagnostic.is_primary
15870 && !entry.range.is_empty()
15871 && entry.range.start == primary_range_start
15872 && entry.diagnostic.message == active_diagnostics.active_message
15873 });
15874
15875 if !is_valid {
15876 self.dismiss_diagnostics(cx);
15877 }
15878 }
15879 }
15880
15881 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15882 match &self.active_diagnostics {
15883 ActiveDiagnostic::Group(group) => Some(group),
15884 _ => None,
15885 }
15886 }
15887
15888 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15889 self.dismiss_diagnostics(cx);
15890 self.active_diagnostics = ActiveDiagnostic::All;
15891 }
15892
15893 fn activate_diagnostics(
15894 &mut self,
15895 buffer_id: BufferId,
15896 diagnostic: DiagnosticEntry<usize>,
15897 window: &mut Window,
15898 cx: &mut Context<Self>,
15899 ) {
15900 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15901 return;
15902 }
15903 self.dismiss_diagnostics(cx);
15904 let snapshot = self.snapshot(window, cx);
15905 let buffer = self.buffer.read(cx).snapshot(cx);
15906 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15907 return;
15908 };
15909
15910 let diagnostic_group = buffer
15911 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15912 .collect::<Vec<_>>();
15913
15914 let blocks =
15915 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15916
15917 let blocks = self.display_map.update(cx, |display_map, cx| {
15918 display_map.insert_blocks(blocks, cx).into_iter().collect()
15919 });
15920 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15921 active_range: buffer.anchor_before(diagnostic.range.start)
15922 ..buffer.anchor_after(diagnostic.range.end),
15923 active_message: diagnostic.diagnostic.message.clone(),
15924 group_id: diagnostic.diagnostic.group_id,
15925 blocks,
15926 });
15927 cx.notify();
15928 }
15929
15930 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15931 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15932 return;
15933 };
15934
15935 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15936 if let ActiveDiagnostic::Group(group) = prev {
15937 self.display_map.update(cx, |display_map, cx| {
15938 display_map.remove_blocks(group.blocks, cx);
15939 });
15940 cx.notify();
15941 }
15942 }
15943
15944 /// Disable inline diagnostics rendering for this editor.
15945 pub fn disable_inline_diagnostics(&mut self) {
15946 self.inline_diagnostics_enabled = false;
15947 self.inline_diagnostics_update = Task::ready(());
15948 self.inline_diagnostics.clear();
15949 }
15950
15951 pub fn diagnostics_enabled(&self) -> bool {
15952 self.mode.is_full()
15953 }
15954
15955 pub fn inline_diagnostics_enabled(&self) -> bool {
15956 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15957 }
15958
15959 pub fn show_inline_diagnostics(&self) -> bool {
15960 self.show_inline_diagnostics
15961 }
15962
15963 pub fn toggle_inline_diagnostics(
15964 &mut self,
15965 _: &ToggleInlineDiagnostics,
15966 window: &mut Window,
15967 cx: &mut Context<Editor>,
15968 ) {
15969 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15970 self.refresh_inline_diagnostics(false, window, cx);
15971 }
15972
15973 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15974 self.diagnostics_max_severity = severity;
15975 self.display_map.update(cx, |display_map, _| {
15976 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15977 });
15978 }
15979
15980 pub fn toggle_diagnostics(
15981 &mut self,
15982 _: &ToggleDiagnostics,
15983 window: &mut Window,
15984 cx: &mut Context<Editor>,
15985 ) {
15986 if !self.diagnostics_enabled() {
15987 return;
15988 }
15989
15990 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15991 EditorSettings::get_global(cx)
15992 .diagnostics_max_severity
15993 .filter(|severity| severity != &DiagnosticSeverity::Off)
15994 .unwrap_or(DiagnosticSeverity::Hint)
15995 } else {
15996 DiagnosticSeverity::Off
15997 };
15998 self.set_max_diagnostics_severity(new_severity, cx);
15999 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16000 self.active_diagnostics = ActiveDiagnostic::None;
16001 self.inline_diagnostics_update = Task::ready(());
16002 self.inline_diagnostics.clear();
16003 } else {
16004 self.refresh_inline_diagnostics(false, window, cx);
16005 }
16006
16007 cx.notify();
16008 }
16009
16010 pub fn toggle_minimap(
16011 &mut self,
16012 _: &ToggleMinimap,
16013 window: &mut Window,
16014 cx: &mut Context<Editor>,
16015 ) {
16016 if self.supports_minimap(cx) {
16017 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16018 }
16019 }
16020
16021 fn refresh_inline_diagnostics(
16022 &mut self,
16023 debounce: bool,
16024 window: &mut Window,
16025 cx: &mut Context<Self>,
16026 ) {
16027 let max_severity = ProjectSettings::get_global(cx)
16028 .diagnostics
16029 .inline
16030 .max_severity
16031 .unwrap_or(self.diagnostics_max_severity);
16032
16033 if !self.inline_diagnostics_enabled()
16034 || !self.show_inline_diagnostics
16035 || max_severity == DiagnosticSeverity::Off
16036 {
16037 self.inline_diagnostics_update = Task::ready(());
16038 self.inline_diagnostics.clear();
16039 return;
16040 }
16041
16042 let debounce_ms = ProjectSettings::get_global(cx)
16043 .diagnostics
16044 .inline
16045 .update_debounce_ms;
16046 let debounce = if debounce && debounce_ms > 0 {
16047 Some(Duration::from_millis(debounce_ms))
16048 } else {
16049 None
16050 };
16051 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16052 if let Some(debounce) = debounce {
16053 cx.background_executor().timer(debounce).await;
16054 }
16055 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16056 editor
16057 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16058 .ok()
16059 }) else {
16060 return;
16061 };
16062
16063 let new_inline_diagnostics = cx
16064 .background_spawn(async move {
16065 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16066 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16067 let message = diagnostic_entry
16068 .diagnostic
16069 .message
16070 .split_once('\n')
16071 .map(|(line, _)| line)
16072 .map(SharedString::new)
16073 .unwrap_or_else(|| {
16074 SharedString::from(diagnostic_entry.diagnostic.message)
16075 });
16076 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16077 let (Ok(i) | Err(i)) = inline_diagnostics
16078 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16079 inline_diagnostics.insert(
16080 i,
16081 (
16082 start_anchor,
16083 InlineDiagnostic {
16084 message,
16085 group_id: diagnostic_entry.diagnostic.group_id,
16086 start: diagnostic_entry.range.start.to_point(&snapshot),
16087 is_primary: diagnostic_entry.diagnostic.is_primary,
16088 severity: diagnostic_entry.diagnostic.severity,
16089 },
16090 ),
16091 );
16092 }
16093 inline_diagnostics
16094 })
16095 .await;
16096
16097 editor
16098 .update(cx, |editor, cx| {
16099 editor.inline_diagnostics = new_inline_diagnostics;
16100 cx.notify();
16101 })
16102 .ok();
16103 });
16104 }
16105
16106 fn pull_diagnostics(
16107 &mut self,
16108 buffer_id: Option<BufferId>,
16109 window: &Window,
16110 cx: &mut Context<Self>,
16111 ) -> Option<()> {
16112 if !self.mode().is_full() {
16113 return None;
16114 }
16115 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16116 .diagnostics
16117 .lsp_pull_diagnostics;
16118 if !pull_diagnostics_settings.enabled {
16119 return None;
16120 }
16121 let project = self.project.as_ref()?.downgrade();
16122 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16123 let mut buffers = self.buffer.read(cx).all_buffers();
16124 if let Some(buffer_id) = buffer_id {
16125 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16126 }
16127
16128 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16129 cx.background_executor().timer(debounce).await;
16130
16131 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16132 buffers
16133 .into_iter()
16134 .flat_map(|buffer| {
16135 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16136 })
16137 .collect::<FuturesUnordered<_>>()
16138 }) else {
16139 return;
16140 };
16141
16142 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16143 match pull_task {
16144 Ok(()) => {
16145 if editor
16146 .update_in(cx, |editor, window, cx| {
16147 editor.update_diagnostics_state(window, cx);
16148 })
16149 .is_err()
16150 {
16151 return;
16152 }
16153 }
16154 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16155 }
16156 }
16157 });
16158
16159 Some(())
16160 }
16161
16162 pub fn set_selections_from_remote(
16163 &mut self,
16164 selections: Vec<Selection<Anchor>>,
16165 pending_selection: Option<Selection<Anchor>>,
16166 window: &mut Window,
16167 cx: &mut Context<Self>,
16168 ) {
16169 let old_cursor_position = self.selections.newest_anchor().head();
16170 self.selections.change_with(cx, |s| {
16171 s.select_anchors(selections);
16172 if let Some(pending_selection) = pending_selection {
16173 s.set_pending(pending_selection, SelectMode::Character);
16174 } else {
16175 s.clear_pending();
16176 }
16177 });
16178 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16179 }
16180
16181 pub fn transact(
16182 &mut self,
16183 window: &mut Window,
16184 cx: &mut Context<Self>,
16185 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16186 ) -> Option<TransactionId> {
16187 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16188 this.start_transaction_at(Instant::now(), window, cx);
16189 update(this, window, cx);
16190 this.end_transaction_at(Instant::now(), cx)
16191 })
16192 }
16193
16194 pub fn start_transaction_at(
16195 &mut self,
16196 now: Instant,
16197 window: &mut Window,
16198 cx: &mut Context<Self>,
16199 ) {
16200 self.end_selection(window, cx);
16201 if let Some(tx_id) = self
16202 .buffer
16203 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16204 {
16205 self.selection_history
16206 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16207 cx.emit(EditorEvent::TransactionBegun {
16208 transaction_id: tx_id,
16209 })
16210 }
16211 }
16212
16213 pub fn end_transaction_at(
16214 &mut self,
16215 now: Instant,
16216 cx: &mut Context<Self>,
16217 ) -> Option<TransactionId> {
16218 if let Some(transaction_id) = self
16219 .buffer
16220 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16221 {
16222 if let Some((_, end_selections)) =
16223 self.selection_history.transaction_mut(transaction_id)
16224 {
16225 *end_selections = Some(self.selections.disjoint_anchors());
16226 } else {
16227 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16228 }
16229
16230 cx.emit(EditorEvent::Edited { transaction_id });
16231 Some(transaction_id)
16232 } else {
16233 None
16234 }
16235 }
16236
16237 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16238 if self.selection_mark_mode {
16239 self.change_selections(None, window, cx, |s| {
16240 s.move_with(|_, sel| {
16241 sel.collapse_to(sel.head(), SelectionGoal::None);
16242 });
16243 })
16244 }
16245 self.selection_mark_mode = true;
16246 cx.notify();
16247 }
16248
16249 pub fn swap_selection_ends(
16250 &mut self,
16251 _: &actions::SwapSelectionEnds,
16252 window: &mut Window,
16253 cx: &mut Context<Self>,
16254 ) {
16255 self.change_selections(None, window, cx, |s| {
16256 s.move_with(|_, sel| {
16257 if sel.start != sel.end {
16258 sel.reversed = !sel.reversed
16259 }
16260 });
16261 });
16262 self.request_autoscroll(Autoscroll::newest(), cx);
16263 cx.notify();
16264 }
16265
16266 pub fn toggle_fold(
16267 &mut self,
16268 _: &actions::ToggleFold,
16269 window: &mut Window,
16270 cx: &mut Context<Self>,
16271 ) {
16272 if self.is_singleton(cx) {
16273 let selection = self.selections.newest::<Point>(cx);
16274
16275 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16276 let range = if selection.is_empty() {
16277 let point = selection.head().to_display_point(&display_map);
16278 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16279 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16280 .to_point(&display_map);
16281 start..end
16282 } else {
16283 selection.range()
16284 };
16285 if display_map.folds_in_range(range).next().is_some() {
16286 self.unfold_lines(&Default::default(), window, cx)
16287 } else {
16288 self.fold(&Default::default(), window, cx)
16289 }
16290 } else {
16291 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16292 let buffer_ids: HashSet<_> = self
16293 .selections
16294 .disjoint_anchor_ranges()
16295 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16296 .collect();
16297
16298 let should_unfold = buffer_ids
16299 .iter()
16300 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16301
16302 for buffer_id in buffer_ids {
16303 if should_unfold {
16304 self.unfold_buffer(buffer_id, cx);
16305 } else {
16306 self.fold_buffer(buffer_id, cx);
16307 }
16308 }
16309 }
16310 }
16311
16312 pub fn toggle_fold_recursive(
16313 &mut self,
16314 _: &actions::ToggleFoldRecursive,
16315 window: &mut Window,
16316 cx: &mut Context<Self>,
16317 ) {
16318 let selection = self.selections.newest::<Point>(cx);
16319
16320 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16321 let range = if selection.is_empty() {
16322 let point = selection.head().to_display_point(&display_map);
16323 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16324 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16325 .to_point(&display_map);
16326 start..end
16327 } else {
16328 selection.range()
16329 };
16330 if display_map.folds_in_range(range).next().is_some() {
16331 self.unfold_recursive(&Default::default(), window, cx)
16332 } else {
16333 self.fold_recursive(&Default::default(), window, cx)
16334 }
16335 }
16336
16337 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16338 if self.is_singleton(cx) {
16339 let mut to_fold = Vec::new();
16340 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16341 let selections = self.selections.all_adjusted(cx);
16342
16343 for selection in selections {
16344 let range = selection.range().sorted();
16345 let buffer_start_row = range.start.row;
16346
16347 if range.start.row != range.end.row {
16348 let mut found = false;
16349 let mut row = range.start.row;
16350 while row <= range.end.row {
16351 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16352 {
16353 found = true;
16354 row = crease.range().end.row + 1;
16355 to_fold.push(crease);
16356 } else {
16357 row += 1
16358 }
16359 }
16360 if found {
16361 continue;
16362 }
16363 }
16364
16365 for row in (0..=range.start.row).rev() {
16366 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16367 if crease.range().end.row >= buffer_start_row {
16368 to_fold.push(crease);
16369 if row <= range.start.row {
16370 break;
16371 }
16372 }
16373 }
16374 }
16375 }
16376
16377 self.fold_creases(to_fold, true, window, cx);
16378 } else {
16379 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16380 let buffer_ids = self
16381 .selections
16382 .disjoint_anchor_ranges()
16383 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16384 .collect::<HashSet<_>>();
16385 for buffer_id in buffer_ids {
16386 self.fold_buffer(buffer_id, cx);
16387 }
16388 }
16389 }
16390
16391 fn fold_at_level(
16392 &mut self,
16393 fold_at: &FoldAtLevel,
16394 window: &mut Window,
16395 cx: &mut Context<Self>,
16396 ) {
16397 if !self.buffer.read(cx).is_singleton() {
16398 return;
16399 }
16400
16401 let fold_at_level = fold_at.0;
16402 let snapshot = self.buffer.read(cx).snapshot(cx);
16403 let mut to_fold = Vec::new();
16404 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16405
16406 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16407 while start_row < end_row {
16408 match self
16409 .snapshot(window, cx)
16410 .crease_for_buffer_row(MultiBufferRow(start_row))
16411 {
16412 Some(crease) => {
16413 let nested_start_row = crease.range().start.row + 1;
16414 let nested_end_row = crease.range().end.row;
16415
16416 if current_level < fold_at_level {
16417 stack.push((nested_start_row, nested_end_row, current_level + 1));
16418 } else if current_level == fold_at_level {
16419 to_fold.push(crease);
16420 }
16421
16422 start_row = nested_end_row + 1;
16423 }
16424 None => start_row += 1,
16425 }
16426 }
16427 }
16428
16429 self.fold_creases(to_fold, true, window, cx);
16430 }
16431
16432 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16433 if self.buffer.read(cx).is_singleton() {
16434 let mut fold_ranges = Vec::new();
16435 let snapshot = self.buffer.read(cx).snapshot(cx);
16436
16437 for row in 0..snapshot.max_row().0 {
16438 if let Some(foldable_range) = self
16439 .snapshot(window, cx)
16440 .crease_for_buffer_row(MultiBufferRow(row))
16441 {
16442 fold_ranges.push(foldable_range);
16443 }
16444 }
16445
16446 self.fold_creases(fold_ranges, true, window, cx);
16447 } else {
16448 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16449 editor
16450 .update_in(cx, |editor, _, cx| {
16451 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16452 editor.fold_buffer(buffer_id, cx);
16453 }
16454 })
16455 .ok();
16456 });
16457 }
16458 }
16459
16460 pub fn fold_function_bodies(
16461 &mut self,
16462 _: &actions::FoldFunctionBodies,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) {
16466 let snapshot = self.buffer.read(cx).snapshot(cx);
16467
16468 let ranges = snapshot
16469 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16470 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16471 .collect::<Vec<_>>();
16472
16473 let creases = ranges
16474 .into_iter()
16475 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16476 .collect();
16477
16478 self.fold_creases(creases, true, window, cx);
16479 }
16480
16481 pub fn fold_recursive(
16482 &mut self,
16483 _: &actions::FoldRecursive,
16484 window: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) {
16487 let mut to_fold = Vec::new();
16488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16489 let selections = self.selections.all_adjusted(cx);
16490
16491 for selection in selections {
16492 let range = selection.range().sorted();
16493 let buffer_start_row = range.start.row;
16494
16495 if range.start.row != range.end.row {
16496 let mut found = false;
16497 for row in range.start.row..=range.end.row {
16498 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16499 found = true;
16500 to_fold.push(crease);
16501 }
16502 }
16503 if found {
16504 continue;
16505 }
16506 }
16507
16508 for row in (0..=range.start.row).rev() {
16509 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16510 if crease.range().end.row >= buffer_start_row {
16511 to_fold.push(crease);
16512 } else {
16513 break;
16514 }
16515 }
16516 }
16517 }
16518
16519 self.fold_creases(to_fold, true, window, cx);
16520 }
16521
16522 pub fn fold_at(
16523 &mut self,
16524 buffer_row: MultiBufferRow,
16525 window: &mut Window,
16526 cx: &mut Context<Self>,
16527 ) {
16528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16529
16530 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16531 let autoscroll = self
16532 .selections
16533 .all::<Point>(cx)
16534 .iter()
16535 .any(|selection| crease.range().overlaps(&selection.range()));
16536
16537 self.fold_creases(vec![crease], autoscroll, window, cx);
16538 }
16539 }
16540
16541 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16542 if self.is_singleton(cx) {
16543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16544 let buffer = &display_map.buffer_snapshot;
16545 let selections = self.selections.all::<Point>(cx);
16546 let ranges = selections
16547 .iter()
16548 .map(|s| {
16549 let range = s.display_range(&display_map).sorted();
16550 let mut start = range.start.to_point(&display_map);
16551 let mut end = range.end.to_point(&display_map);
16552 start.column = 0;
16553 end.column = buffer.line_len(MultiBufferRow(end.row));
16554 start..end
16555 })
16556 .collect::<Vec<_>>();
16557
16558 self.unfold_ranges(&ranges, true, true, cx);
16559 } else {
16560 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16561 let buffer_ids = self
16562 .selections
16563 .disjoint_anchor_ranges()
16564 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16565 .collect::<HashSet<_>>();
16566 for buffer_id in buffer_ids {
16567 self.unfold_buffer(buffer_id, cx);
16568 }
16569 }
16570 }
16571
16572 pub fn unfold_recursive(
16573 &mut self,
16574 _: &UnfoldRecursive,
16575 _window: &mut Window,
16576 cx: &mut Context<Self>,
16577 ) {
16578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16579 let selections = self.selections.all::<Point>(cx);
16580 let ranges = selections
16581 .iter()
16582 .map(|s| {
16583 let mut range = s.display_range(&display_map).sorted();
16584 *range.start.column_mut() = 0;
16585 *range.end.column_mut() = display_map.line_len(range.end.row());
16586 let start = range.start.to_point(&display_map);
16587 let end = range.end.to_point(&display_map);
16588 start..end
16589 })
16590 .collect::<Vec<_>>();
16591
16592 self.unfold_ranges(&ranges, true, true, cx);
16593 }
16594
16595 pub fn unfold_at(
16596 &mut self,
16597 buffer_row: MultiBufferRow,
16598 _window: &mut Window,
16599 cx: &mut Context<Self>,
16600 ) {
16601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16602
16603 let intersection_range = Point::new(buffer_row.0, 0)
16604 ..Point::new(
16605 buffer_row.0,
16606 display_map.buffer_snapshot.line_len(buffer_row),
16607 );
16608
16609 let autoscroll = self
16610 .selections
16611 .all::<Point>(cx)
16612 .iter()
16613 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16614
16615 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16616 }
16617
16618 pub fn unfold_all(
16619 &mut self,
16620 _: &actions::UnfoldAll,
16621 _window: &mut Window,
16622 cx: &mut Context<Self>,
16623 ) {
16624 if self.buffer.read(cx).is_singleton() {
16625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16626 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16627 } else {
16628 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16629 editor
16630 .update(cx, |editor, cx| {
16631 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16632 editor.unfold_buffer(buffer_id, cx);
16633 }
16634 })
16635 .ok();
16636 });
16637 }
16638 }
16639
16640 pub fn fold_selected_ranges(
16641 &mut self,
16642 _: &FoldSelectedRanges,
16643 window: &mut Window,
16644 cx: &mut Context<Self>,
16645 ) {
16646 let selections = self.selections.all_adjusted(cx);
16647 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16648 let ranges = selections
16649 .into_iter()
16650 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16651 .collect::<Vec<_>>();
16652 self.fold_creases(ranges, true, window, cx);
16653 }
16654
16655 pub fn fold_ranges<T: ToOffset + Clone>(
16656 &mut self,
16657 ranges: Vec<Range<T>>,
16658 auto_scroll: bool,
16659 window: &mut Window,
16660 cx: &mut Context<Self>,
16661 ) {
16662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16663 let ranges = ranges
16664 .into_iter()
16665 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16666 .collect::<Vec<_>>();
16667 self.fold_creases(ranges, auto_scroll, window, cx);
16668 }
16669
16670 pub fn fold_creases<T: ToOffset + Clone>(
16671 &mut self,
16672 creases: Vec<Crease<T>>,
16673 auto_scroll: bool,
16674 _window: &mut Window,
16675 cx: &mut Context<Self>,
16676 ) {
16677 if creases.is_empty() {
16678 return;
16679 }
16680
16681 let mut buffers_affected = HashSet::default();
16682 let multi_buffer = self.buffer().read(cx);
16683 for crease in &creases {
16684 if let Some((_, buffer, _)) =
16685 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16686 {
16687 buffers_affected.insert(buffer.read(cx).remote_id());
16688 };
16689 }
16690
16691 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16692
16693 if auto_scroll {
16694 self.request_autoscroll(Autoscroll::fit(), cx);
16695 }
16696
16697 cx.notify();
16698
16699 self.scrollbar_marker_state.dirty = true;
16700 self.folds_did_change(cx);
16701 }
16702
16703 /// Removes any folds whose ranges intersect any of the given ranges.
16704 pub fn unfold_ranges<T: ToOffset + Clone>(
16705 &mut self,
16706 ranges: &[Range<T>],
16707 inclusive: bool,
16708 auto_scroll: bool,
16709 cx: &mut Context<Self>,
16710 ) {
16711 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16712 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16713 });
16714 self.folds_did_change(cx);
16715 }
16716
16717 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16718 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16719 return;
16720 }
16721 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16722 self.display_map.update(cx, |display_map, cx| {
16723 display_map.fold_buffers([buffer_id], cx)
16724 });
16725 cx.emit(EditorEvent::BufferFoldToggled {
16726 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16727 folded: true,
16728 });
16729 cx.notify();
16730 }
16731
16732 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16733 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16734 return;
16735 }
16736 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16737 self.display_map.update(cx, |display_map, cx| {
16738 display_map.unfold_buffers([buffer_id], cx);
16739 });
16740 cx.emit(EditorEvent::BufferFoldToggled {
16741 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16742 folded: false,
16743 });
16744 cx.notify();
16745 }
16746
16747 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16748 self.display_map.read(cx).is_buffer_folded(buffer)
16749 }
16750
16751 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16752 self.display_map.read(cx).folded_buffers()
16753 }
16754
16755 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16756 self.display_map.update(cx, |display_map, cx| {
16757 display_map.disable_header_for_buffer(buffer_id, cx);
16758 });
16759 cx.notify();
16760 }
16761
16762 /// Removes any folds with the given ranges.
16763 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16764 &mut self,
16765 ranges: &[Range<T>],
16766 type_id: TypeId,
16767 auto_scroll: bool,
16768 cx: &mut Context<Self>,
16769 ) {
16770 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16771 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16772 });
16773 self.folds_did_change(cx);
16774 }
16775
16776 fn remove_folds_with<T: ToOffset + Clone>(
16777 &mut self,
16778 ranges: &[Range<T>],
16779 auto_scroll: bool,
16780 cx: &mut Context<Self>,
16781 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16782 ) {
16783 if ranges.is_empty() {
16784 return;
16785 }
16786
16787 let mut buffers_affected = HashSet::default();
16788 let multi_buffer = self.buffer().read(cx);
16789 for range in ranges {
16790 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16791 buffers_affected.insert(buffer.read(cx).remote_id());
16792 };
16793 }
16794
16795 self.display_map.update(cx, update);
16796
16797 if auto_scroll {
16798 self.request_autoscroll(Autoscroll::fit(), cx);
16799 }
16800
16801 cx.notify();
16802 self.scrollbar_marker_state.dirty = true;
16803 self.active_indent_guides_state.dirty = true;
16804 }
16805
16806 pub fn update_fold_widths(
16807 &mut self,
16808 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16809 cx: &mut Context<Self>,
16810 ) -> bool {
16811 self.display_map
16812 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16813 }
16814
16815 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16816 self.display_map.read(cx).fold_placeholder.clone()
16817 }
16818
16819 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16820 self.buffer.update(cx, |buffer, cx| {
16821 buffer.set_all_diff_hunks_expanded(cx);
16822 });
16823 }
16824
16825 pub fn expand_all_diff_hunks(
16826 &mut self,
16827 _: &ExpandAllDiffHunks,
16828 _window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) {
16831 self.buffer.update(cx, |buffer, cx| {
16832 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16833 });
16834 }
16835
16836 pub fn toggle_selected_diff_hunks(
16837 &mut self,
16838 _: &ToggleSelectedDiffHunks,
16839 _window: &mut Window,
16840 cx: &mut Context<Self>,
16841 ) {
16842 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16843 self.toggle_diff_hunks_in_ranges(ranges, cx);
16844 }
16845
16846 pub fn diff_hunks_in_ranges<'a>(
16847 &'a self,
16848 ranges: &'a [Range<Anchor>],
16849 buffer: &'a MultiBufferSnapshot,
16850 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16851 ranges.iter().flat_map(move |range| {
16852 let end_excerpt_id = range.end.excerpt_id;
16853 let range = range.to_point(buffer);
16854 let mut peek_end = range.end;
16855 if range.end.row < buffer.max_row().0 {
16856 peek_end = Point::new(range.end.row + 1, 0);
16857 }
16858 buffer
16859 .diff_hunks_in_range(range.start..peek_end)
16860 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16861 })
16862 }
16863
16864 pub fn has_stageable_diff_hunks_in_ranges(
16865 &self,
16866 ranges: &[Range<Anchor>],
16867 snapshot: &MultiBufferSnapshot,
16868 ) -> bool {
16869 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16870 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16871 }
16872
16873 pub fn toggle_staged_selected_diff_hunks(
16874 &mut self,
16875 _: &::git::ToggleStaged,
16876 _: &mut Window,
16877 cx: &mut Context<Self>,
16878 ) {
16879 let snapshot = self.buffer.read(cx).snapshot(cx);
16880 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16881 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16882 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16883 }
16884
16885 pub fn set_render_diff_hunk_controls(
16886 &mut self,
16887 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16888 cx: &mut Context<Self>,
16889 ) {
16890 self.render_diff_hunk_controls = render_diff_hunk_controls;
16891 cx.notify();
16892 }
16893
16894 pub fn stage_and_next(
16895 &mut self,
16896 _: &::git::StageAndNext,
16897 window: &mut Window,
16898 cx: &mut Context<Self>,
16899 ) {
16900 self.do_stage_or_unstage_and_next(true, window, cx);
16901 }
16902
16903 pub fn unstage_and_next(
16904 &mut self,
16905 _: &::git::UnstageAndNext,
16906 window: &mut Window,
16907 cx: &mut Context<Self>,
16908 ) {
16909 self.do_stage_or_unstage_and_next(false, window, cx);
16910 }
16911
16912 pub fn stage_or_unstage_diff_hunks(
16913 &mut self,
16914 stage: bool,
16915 ranges: Vec<Range<Anchor>>,
16916 cx: &mut Context<Self>,
16917 ) {
16918 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16919 cx.spawn(async move |this, cx| {
16920 task.await?;
16921 this.update(cx, |this, cx| {
16922 let snapshot = this.buffer.read(cx).snapshot(cx);
16923 let chunk_by = this
16924 .diff_hunks_in_ranges(&ranges, &snapshot)
16925 .chunk_by(|hunk| hunk.buffer_id);
16926 for (buffer_id, hunks) in &chunk_by {
16927 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16928 }
16929 })
16930 })
16931 .detach_and_log_err(cx);
16932 }
16933
16934 fn save_buffers_for_ranges_if_needed(
16935 &mut self,
16936 ranges: &[Range<Anchor>],
16937 cx: &mut Context<Editor>,
16938 ) -> Task<Result<()>> {
16939 let multibuffer = self.buffer.read(cx);
16940 let snapshot = multibuffer.read(cx);
16941 let buffer_ids: HashSet<_> = ranges
16942 .iter()
16943 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16944 .collect();
16945 drop(snapshot);
16946
16947 let mut buffers = HashSet::default();
16948 for buffer_id in buffer_ids {
16949 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16950 let buffer = buffer_entity.read(cx);
16951 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16952 {
16953 buffers.insert(buffer_entity);
16954 }
16955 }
16956 }
16957
16958 if let Some(project) = &self.project {
16959 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16960 } else {
16961 Task::ready(Ok(()))
16962 }
16963 }
16964
16965 fn do_stage_or_unstage_and_next(
16966 &mut self,
16967 stage: bool,
16968 window: &mut Window,
16969 cx: &mut Context<Self>,
16970 ) {
16971 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16972
16973 if ranges.iter().any(|range| range.start != range.end) {
16974 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16975 return;
16976 }
16977
16978 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16979 let snapshot = self.snapshot(window, cx);
16980 let position = self.selections.newest::<Point>(cx).head();
16981 let mut row = snapshot
16982 .buffer_snapshot
16983 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16984 .find(|hunk| hunk.row_range.start.0 > position.row)
16985 .map(|hunk| hunk.row_range.start);
16986
16987 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16988 // Outside of the project diff editor, wrap around to the beginning.
16989 if !all_diff_hunks_expanded {
16990 row = row.or_else(|| {
16991 snapshot
16992 .buffer_snapshot
16993 .diff_hunks_in_range(Point::zero()..position)
16994 .find(|hunk| hunk.row_range.end.0 < position.row)
16995 .map(|hunk| hunk.row_range.start)
16996 });
16997 }
16998
16999 if let Some(row) = row {
17000 let destination = Point::new(row.0, 0);
17001 let autoscroll = Autoscroll::center();
17002
17003 self.unfold_ranges(&[destination..destination], false, false, cx);
17004 self.change_selections(Some(autoscroll), window, cx, |s| {
17005 s.select_ranges([destination..destination]);
17006 });
17007 }
17008 }
17009
17010 fn do_stage_or_unstage(
17011 &self,
17012 stage: bool,
17013 buffer_id: BufferId,
17014 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17015 cx: &mut App,
17016 ) -> Option<()> {
17017 let project = self.project.as_ref()?;
17018 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17019 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17020 let buffer_snapshot = buffer.read(cx).snapshot();
17021 let file_exists = buffer_snapshot
17022 .file()
17023 .is_some_and(|file| file.disk_state().exists());
17024 diff.update(cx, |diff, cx| {
17025 diff.stage_or_unstage_hunks(
17026 stage,
17027 &hunks
17028 .map(|hunk| buffer_diff::DiffHunk {
17029 buffer_range: hunk.buffer_range,
17030 diff_base_byte_range: hunk.diff_base_byte_range,
17031 secondary_status: hunk.secondary_status,
17032 range: Point::zero()..Point::zero(), // unused
17033 })
17034 .collect::<Vec<_>>(),
17035 &buffer_snapshot,
17036 file_exists,
17037 cx,
17038 )
17039 });
17040 None
17041 }
17042
17043 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17044 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17045 self.buffer
17046 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17047 }
17048
17049 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17050 self.buffer.update(cx, |buffer, cx| {
17051 let ranges = vec![Anchor::min()..Anchor::max()];
17052 if !buffer.all_diff_hunks_expanded()
17053 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17054 {
17055 buffer.collapse_diff_hunks(ranges, cx);
17056 true
17057 } else {
17058 false
17059 }
17060 })
17061 }
17062
17063 fn toggle_diff_hunks_in_ranges(
17064 &mut self,
17065 ranges: Vec<Range<Anchor>>,
17066 cx: &mut Context<Editor>,
17067 ) {
17068 self.buffer.update(cx, |buffer, cx| {
17069 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17070 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17071 })
17072 }
17073
17074 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17075 self.buffer.update(cx, |buffer, cx| {
17076 let snapshot = buffer.snapshot(cx);
17077 let excerpt_id = range.end.excerpt_id;
17078 let point_range = range.to_point(&snapshot);
17079 let expand = !buffer.single_hunk_is_expanded(range, cx);
17080 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17081 })
17082 }
17083
17084 pub(crate) fn apply_all_diff_hunks(
17085 &mut self,
17086 _: &ApplyAllDiffHunks,
17087 window: &mut Window,
17088 cx: &mut Context<Self>,
17089 ) {
17090 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17091
17092 let buffers = self.buffer.read(cx).all_buffers();
17093 for branch_buffer in buffers {
17094 branch_buffer.update(cx, |branch_buffer, cx| {
17095 branch_buffer.merge_into_base(Vec::new(), cx);
17096 });
17097 }
17098
17099 if let Some(project) = self.project.clone() {
17100 self.save(true, project, window, cx).detach_and_log_err(cx);
17101 }
17102 }
17103
17104 pub(crate) fn apply_selected_diff_hunks(
17105 &mut self,
17106 _: &ApplyDiffHunk,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) {
17110 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17111 let snapshot = self.snapshot(window, cx);
17112 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17113 let mut ranges_by_buffer = HashMap::default();
17114 self.transact(window, cx, |editor, _window, cx| {
17115 for hunk in hunks {
17116 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17117 ranges_by_buffer
17118 .entry(buffer.clone())
17119 .or_insert_with(Vec::new)
17120 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17121 }
17122 }
17123
17124 for (buffer, ranges) in ranges_by_buffer {
17125 buffer.update(cx, |buffer, cx| {
17126 buffer.merge_into_base(ranges, cx);
17127 });
17128 }
17129 });
17130
17131 if let Some(project) = self.project.clone() {
17132 self.save(true, project, window, cx).detach_and_log_err(cx);
17133 }
17134 }
17135
17136 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17137 if hovered != self.gutter_hovered {
17138 self.gutter_hovered = hovered;
17139 cx.notify();
17140 }
17141 }
17142
17143 pub fn insert_blocks(
17144 &mut self,
17145 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17146 autoscroll: Option<Autoscroll>,
17147 cx: &mut Context<Self>,
17148 ) -> Vec<CustomBlockId> {
17149 let blocks = self
17150 .display_map
17151 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17152 if let Some(autoscroll) = autoscroll {
17153 self.request_autoscroll(autoscroll, cx);
17154 }
17155 cx.notify();
17156 blocks
17157 }
17158
17159 pub fn resize_blocks(
17160 &mut self,
17161 heights: HashMap<CustomBlockId, u32>,
17162 autoscroll: Option<Autoscroll>,
17163 cx: &mut Context<Self>,
17164 ) {
17165 self.display_map
17166 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17167 if let Some(autoscroll) = autoscroll {
17168 self.request_autoscroll(autoscroll, cx);
17169 }
17170 cx.notify();
17171 }
17172
17173 pub fn replace_blocks(
17174 &mut self,
17175 renderers: HashMap<CustomBlockId, RenderBlock>,
17176 autoscroll: Option<Autoscroll>,
17177 cx: &mut Context<Self>,
17178 ) {
17179 self.display_map
17180 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17181 if let Some(autoscroll) = autoscroll {
17182 self.request_autoscroll(autoscroll, cx);
17183 }
17184 cx.notify();
17185 }
17186
17187 pub fn remove_blocks(
17188 &mut self,
17189 block_ids: HashSet<CustomBlockId>,
17190 autoscroll: Option<Autoscroll>,
17191 cx: &mut Context<Self>,
17192 ) {
17193 self.display_map.update(cx, |display_map, cx| {
17194 display_map.remove_blocks(block_ids, cx)
17195 });
17196 if let Some(autoscroll) = autoscroll {
17197 self.request_autoscroll(autoscroll, cx);
17198 }
17199 cx.notify();
17200 }
17201
17202 pub fn row_for_block(
17203 &self,
17204 block_id: CustomBlockId,
17205 cx: &mut Context<Self>,
17206 ) -> Option<DisplayRow> {
17207 self.display_map
17208 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17209 }
17210
17211 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17212 self.focused_block = Some(focused_block);
17213 }
17214
17215 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17216 self.focused_block.take()
17217 }
17218
17219 pub fn insert_creases(
17220 &mut self,
17221 creases: impl IntoIterator<Item = Crease<Anchor>>,
17222 cx: &mut Context<Self>,
17223 ) -> Vec<CreaseId> {
17224 self.display_map
17225 .update(cx, |map, cx| map.insert_creases(creases, cx))
17226 }
17227
17228 pub fn remove_creases(
17229 &mut self,
17230 ids: impl IntoIterator<Item = CreaseId>,
17231 cx: &mut Context<Self>,
17232 ) -> Vec<(CreaseId, Range<Anchor>)> {
17233 self.display_map
17234 .update(cx, |map, cx| map.remove_creases(ids, cx))
17235 }
17236
17237 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17238 self.display_map
17239 .update(cx, |map, cx| map.snapshot(cx))
17240 .longest_row()
17241 }
17242
17243 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17244 self.display_map
17245 .update(cx, |map, cx| map.snapshot(cx))
17246 .max_point()
17247 }
17248
17249 pub fn text(&self, cx: &App) -> String {
17250 self.buffer.read(cx).read(cx).text()
17251 }
17252
17253 pub fn is_empty(&self, cx: &App) -> bool {
17254 self.buffer.read(cx).read(cx).is_empty()
17255 }
17256
17257 pub fn text_option(&self, cx: &App) -> Option<String> {
17258 let text = self.text(cx);
17259 let text = text.trim();
17260
17261 if text.is_empty() {
17262 return None;
17263 }
17264
17265 Some(text.to_string())
17266 }
17267
17268 pub fn set_text(
17269 &mut self,
17270 text: impl Into<Arc<str>>,
17271 window: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) {
17274 self.transact(window, cx, |this, _, cx| {
17275 this.buffer
17276 .read(cx)
17277 .as_singleton()
17278 .expect("you can only call set_text on editors for singleton buffers")
17279 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17280 });
17281 }
17282
17283 pub fn display_text(&self, cx: &mut App) -> String {
17284 self.display_map
17285 .update(cx, |map, cx| map.snapshot(cx))
17286 .text()
17287 }
17288
17289 fn create_minimap(
17290 &self,
17291 minimap_settings: MinimapSettings,
17292 window: &mut Window,
17293 cx: &mut Context<Self>,
17294 ) -> Option<Entity<Self>> {
17295 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17296 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17297 }
17298
17299 fn initialize_new_minimap(
17300 &self,
17301 minimap_settings: MinimapSettings,
17302 window: &mut Window,
17303 cx: &mut Context<Self>,
17304 ) -> Entity<Self> {
17305 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17306
17307 let mut minimap = Editor::new_internal(
17308 EditorMode::Minimap {
17309 parent: cx.weak_entity(),
17310 },
17311 self.buffer.clone(),
17312 self.project.clone(),
17313 Some(self.display_map.clone()),
17314 window,
17315 cx,
17316 );
17317 minimap.scroll_manager.clone_state(&self.scroll_manager);
17318 minimap.set_text_style_refinement(TextStyleRefinement {
17319 font_size: Some(MINIMAP_FONT_SIZE),
17320 font_weight: Some(MINIMAP_FONT_WEIGHT),
17321 ..Default::default()
17322 });
17323 minimap.update_minimap_configuration(minimap_settings, cx);
17324 cx.new(|_| minimap)
17325 }
17326
17327 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17328 let current_line_highlight = minimap_settings
17329 .current_line_highlight
17330 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17331 self.set_current_line_highlight(Some(current_line_highlight));
17332 }
17333
17334 pub fn minimap(&self) -> Option<&Entity<Self>> {
17335 self.minimap
17336 .as_ref()
17337 .filter(|_| self.minimap_visibility.visible())
17338 }
17339
17340 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17341 let mut wrap_guides = smallvec![];
17342
17343 if self.show_wrap_guides == Some(false) {
17344 return wrap_guides;
17345 }
17346
17347 let settings = self.buffer.read(cx).language_settings(cx);
17348 if settings.show_wrap_guides {
17349 match self.soft_wrap_mode(cx) {
17350 SoftWrap::Column(soft_wrap) => {
17351 wrap_guides.push((soft_wrap as usize, true));
17352 }
17353 SoftWrap::Bounded(soft_wrap) => {
17354 wrap_guides.push((soft_wrap as usize, true));
17355 }
17356 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17357 }
17358 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17359 }
17360
17361 wrap_guides
17362 }
17363
17364 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17365 let settings = self.buffer.read(cx).language_settings(cx);
17366 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17367 match mode {
17368 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17369 SoftWrap::None
17370 }
17371 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17372 language_settings::SoftWrap::PreferredLineLength => {
17373 SoftWrap::Column(settings.preferred_line_length)
17374 }
17375 language_settings::SoftWrap::Bounded => {
17376 SoftWrap::Bounded(settings.preferred_line_length)
17377 }
17378 }
17379 }
17380
17381 pub fn set_soft_wrap_mode(
17382 &mut self,
17383 mode: language_settings::SoftWrap,
17384
17385 cx: &mut Context<Self>,
17386 ) {
17387 self.soft_wrap_mode_override = Some(mode);
17388 cx.notify();
17389 }
17390
17391 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17392 self.hard_wrap = hard_wrap;
17393 cx.notify();
17394 }
17395
17396 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17397 self.text_style_refinement = Some(style);
17398 }
17399
17400 /// called by the Element so we know what style we were most recently rendered with.
17401 pub(crate) fn set_style(
17402 &mut self,
17403 style: EditorStyle,
17404 window: &mut Window,
17405 cx: &mut Context<Self>,
17406 ) {
17407 // We intentionally do not inform the display map about the minimap style
17408 // so that wrapping is not recalculated and stays consistent for the editor
17409 // and its linked minimap.
17410 if !self.mode.is_minimap() {
17411 let rem_size = window.rem_size();
17412 self.display_map.update(cx, |map, cx| {
17413 map.set_font(
17414 style.text.font(),
17415 style.text.font_size.to_pixels(rem_size),
17416 cx,
17417 )
17418 });
17419 }
17420 self.style = Some(style);
17421 }
17422
17423 pub fn style(&self) -> Option<&EditorStyle> {
17424 self.style.as_ref()
17425 }
17426
17427 // Called by the element. This method is not designed to be called outside of the editor
17428 // element's layout code because it does not notify when rewrapping is computed synchronously.
17429 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17430 self.display_map
17431 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17432 }
17433
17434 pub fn set_soft_wrap(&mut self) {
17435 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17436 }
17437
17438 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17439 if self.soft_wrap_mode_override.is_some() {
17440 self.soft_wrap_mode_override.take();
17441 } else {
17442 let soft_wrap = match self.soft_wrap_mode(cx) {
17443 SoftWrap::GitDiff => return,
17444 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17445 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17446 language_settings::SoftWrap::None
17447 }
17448 };
17449 self.soft_wrap_mode_override = Some(soft_wrap);
17450 }
17451 cx.notify();
17452 }
17453
17454 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17455 let Some(workspace) = self.workspace() else {
17456 return;
17457 };
17458 let fs = workspace.read(cx).app_state().fs.clone();
17459 let current_show = TabBarSettings::get_global(cx).show;
17460 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17461 setting.show = Some(!current_show);
17462 });
17463 }
17464
17465 pub fn toggle_indent_guides(
17466 &mut self,
17467 _: &ToggleIndentGuides,
17468 _: &mut Window,
17469 cx: &mut Context<Self>,
17470 ) {
17471 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17472 self.buffer
17473 .read(cx)
17474 .language_settings(cx)
17475 .indent_guides
17476 .enabled
17477 });
17478 self.show_indent_guides = Some(!currently_enabled);
17479 cx.notify();
17480 }
17481
17482 fn should_show_indent_guides(&self) -> Option<bool> {
17483 self.show_indent_guides
17484 }
17485
17486 pub fn toggle_line_numbers(
17487 &mut self,
17488 _: &ToggleLineNumbers,
17489 _: &mut Window,
17490 cx: &mut Context<Self>,
17491 ) {
17492 let mut editor_settings = EditorSettings::get_global(cx).clone();
17493 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17494 EditorSettings::override_global(editor_settings, cx);
17495 }
17496
17497 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17498 if let Some(show_line_numbers) = self.show_line_numbers {
17499 return show_line_numbers;
17500 }
17501 EditorSettings::get_global(cx).gutter.line_numbers
17502 }
17503
17504 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17505 self.use_relative_line_numbers
17506 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17507 }
17508
17509 pub fn toggle_relative_line_numbers(
17510 &mut self,
17511 _: &ToggleRelativeLineNumbers,
17512 _: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) {
17515 let is_relative = self.should_use_relative_line_numbers(cx);
17516 self.set_relative_line_number(Some(!is_relative), cx)
17517 }
17518
17519 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17520 self.use_relative_line_numbers = is_relative;
17521 cx.notify();
17522 }
17523
17524 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17525 self.show_gutter = show_gutter;
17526 cx.notify();
17527 }
17528
17529 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17530 self.show_scrollbars = ScrollbarAxes {
17531 horizontal: show,
17532 vertical: show,
17533 };
17534 cx.notify();
17535 }
17536
17537 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17538 self.show_scrollbars.vertical = show;
17539 cx.notify();
17540 }
17541
17542 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17543 self.show_scrollbars.horizontal = show;
17544 cx.notify();
17545 }
17546
17547 pub fn set_minimap_visibility(
17548 &mut self,
17549 minimap_visibility: MinimapVisibility,
17550 window: &mut Window,
17551 cx: &mut Context<Self>,
17552 ) {
17553 if self.minimap_visibility != minimap_visibility {
17554 if minimap_visibility.visible() && self.minimap.is_none() {
17555 let minimap_settings = EditorSettings::get_global(cx).minimap;
17556 self.minimap =
17557 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17558 }
17559 self.minimap_visibility = minimap_visibility;
17560 cx.notify();
17561 }
17562 }
17563
17564 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17565 self.set_show_scrollbars(false, cx);
17566 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17567 }
17568
17569 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17570 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17571 }
17572
17573 /// Normally the text in full mode and auto height editors is padded on the
17574 /// left side by roughly half a character width for improved hit testing.
17575 ///
17576 /// Use this method to disable this for cases where this is not wanted (e.g.
17577 /// if you want to align the editor text with some other text above or below)
17578 /// or if you want to add this padding to single-line editors.
17579 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17580 self.offset_content = offset_content;
17581 cx.notify();
17582 }
17583
17584 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17585 self.show_line_numbers = Some(show_line_numbers);
17586 cx.notify();
17587 }
17588
17589 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17590 self.disable_expand_excerpt_buttons = true;
17591 cx.notify();
17592 }
17593
17594 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17595 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17596 cx.notify();
17597 }
17598
17599 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17600 self.show_code_actions = Some(show_code_actions);
17601 cx.notify();
17602 }
17603
17604 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17605 self.show_runnables = Some(show_runnables);
17606 cx.notify();
17607 }
17608
17609 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17610 self.show_breakpoints = Some(show_breakpoints);
17611 cx.notify();
17612 }
17613
17614 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17615 if self.display_map.read(cx).masked != masked {
17616 self.display_map.update(cx, |map, _| map.masked = masked);
17617 }
17618 cx.notify()
17619 }
17620
17621 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17622 self.show_wrap_guides = Some(show_wrap_guides);
17623 cx.notify();
17624 }
17625
17626 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17627 self.show_indent_guides = Some(show_indent_guides);
17628 cx.notify();
17629 }
17630
17631 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17632 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17633 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17634 if let Some(dir) = file.abs_path(cx).parent() {
17635 return Some(dir.to_owned());
17636 }
17637 }
17638
17639 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17640 return Some(project_path.path.to_path_buf());
17641 }
17642 }
17643
17644 None
17645 }
17646
17647 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17648 self.active_excerpt(cx)?
17649 .1
17650 .read(cx)
17651 .file()
17652 .and_then(|f| f.as_local())
17653 }
17654
17655 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17656 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17657 let buffer = buffer.read(cx);
17658 if let Some(project_path) = buffer.project_path(cx) {
17659 let project = self.project.as_ref()?.read(cx);
17660 project.absolute_path(&project_path, cx)
17661 } else {
17662 buffer
17663 .file()
17664 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17665 }
17666 })
17667 }
17668
17669 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17670 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17671 let project_path = buffer.read(cx).project_path(cx)?;
17672 let project = self.project.as_ref()?.read(cx);
17673 let entry = project.entry_for_path(&project_path, cx)?;
17674 let path = entry.path.to_path_buf();
17675 Some(path)
17676 })
17677 }
17678
17679 pub fn reveal_in_finder(
17680 &mut self,
17681 _: &RevealInFileManager,
17682 _window: &mut Window,
17683 cx: &mut Context<Self>,
17684 ) {
17685 if let Some(target) = self.target_file(cx) {
17686 cx.reveal_path(&target.abs_path(cx));
17687 }
17688 }
17689
17690 pub fn copy_path(
17691 &mut self,
17692 _: &zed_actions::workspace::CopyPath,
17693 _window: &mut Window,
17694 cx: &mut Context<Self>,
17695 ) {
17696 if let Some(path) = self.target_file_abs_path(cx) {
17697 if let Some(path) = path.to_str() {
17698 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17699 }
17700 }
17701 }
17702
17703 pub fn copy_relative_path(
17704 &mut self,
17705 _: &zed_actions::workspace::CopyRelativePath,
17706 _window: &mut Window,
17707 cx: &mut Context<Self>,
17708 ) {
17709 if let Some(path) = self.target_file_path(cx) {
17710 if let Some(path) = path.to_str() {
17711 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17712 }
17713 }
17714 }
17715
17716 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17717 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17718 buffer.read(cx).project_path(cx)
17719 } else {
17720 None
17721 }
17722 }
17723
17724 // Returns true if the editor handled a go-to-line request
17725 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17726 maybe!({
17727 let breakpoint_store = self.breakpoint_store.as_ref()?;
17728
17729 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17730 else {
17731 self.clear_row_highlights::<ActiveDebugLine>();
17732 return None;
17733 };
17734
17735 let position = active_stack_frame.position;
17736 let buffer_id = position.buffer_id?;
17737 let snapshot = self
17738 .project
17739 .as_ref()?
17740 .read(cx)
17741 .buffer_for_id(buffer_id, cx)?
17742 .read(cx)
17743 .snapshot();
17744
17745 let mut handled = false;
17746 for (id, ExcerptRange { context, .. }) in
17747 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17748 {
17749 if context.start.cmp(&position, &snapshot).is_ge()
17750 || context.end.cmp(&position, &snapshot).is_lt()
17751 {
17752 continue;
17753 }
17754 let snapshot = self.buffer.read(cx).snapshot(cx);
17755 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17756
17757 handled = true;
17758 self.clear_row_highlights::<ActiveDebugLine>();
17759
17760 self.go_to_line::<ActiveDebugLine>(
17761 multibuffer_anchor,
17762 Some(cx.theme().colors().editor_debugger_active_line_background),
17763 window,
17764 cx,
17765 );
17766
17767 cx.notify();
17768 }
17769
17770 handled.then_some(())
17771 })
17772 .is_some()
17773 }
17774
17775 pub fn copy_file_name_without_extension(
17776 &mut self,
17777 _: &CopyFileNameWithoutExtension,
17778 _: &mut Window,
17779 cx: &mut Context<Self>,
17780 ) {
17781 if let Some(file) = self.target_file(cx) {
17782 if let Some(file_stem) = file.path().file_stem() {
17783 if let Some(name) = file_stem.to_str() {
17784 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17785 }
17786 }
17787 }
17788 }
17789
17790 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17791 if let Some(file) = self.target_file(cx) {
17792 if let Some(file_name) = file.path().file_name() {
17793 if let Some(name) = file_name.to_str() {
17794 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17795 }
17796 }
17797 }
17798 }
17799
17800 pub fn toggle_git_blame(
17801 &mut self,
17802 _: &::git::Blame,
17803 window: &mut Window,
17804 cx: &mut Context<Self>,
17805 ) {
17806 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17807
17808 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17809 self.start_git_blame(true, window, cx);
17810 }
17811
17812 cx.notify();
17813 }
17814
17815 pub fn toggle_git_blame_inline(
17816 &mut self,
17817 _: &ToggleGitBlameInline,
17818 window: &mut Window,
17819 cx: &mut Context<Self>,
17820 ) {
17821 self.toggle_git_blame_inline_internal(true, window, cx);
17822 cx.notify();
17823 }
17824
17825 pub fn open_git_blame_commit(
17826 &mut self,
17827 _: &OpenGitBlameCommit,
17828 window: &mut Window,
17829 cx: &mut Context<Self>,
17830 ) {
17831 self.open_git_blame_commit_internal(window, cx);
17832 }
17833
17834 fn open_git_blame_commit_internal(
17835 &mut self,
17836 window: &mut Window,
17837 cx: &mut Context<Self>,
17838 ) -> Option<()> {
17839 let blame = self.blame.as_ref()?;
17840 let snapshot = self.snapshot(window, cx);
17841 let cursor = self.selections.newest::<Point>(cx).head();
17842 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17843 let blame_entry = blame
17844 .update(cx, |blame, cx| {
17845 blame
17846 .blame_for_rows(
17847 &[RowInfo {
17848 buffer_id: Some(buffer.remote_id()),
17849 buffer_row: Some(point.row),
17850 ..Default::default()
17851 }],
17852 cx,
17853 )
17854 .next()
17855 })
17856 .flatten()?;
17857 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17858 let repo = blame.read(cx).repository(cx)?;
17859 let workspace = self.workspace()?.downgrade();
17860 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17861 None
17862 }
17863
17864 pub fn git_blame_inline_enabled(&self) -> bool {
17865 self.git_blame_inline_enabled
17866 }
17867
17868 pub fn toggle_selection_menu(
17869 &mut self,
17870 _: &ToggleSelectionMenu,
17871 _: &mut Window,
17872 cx: &mut Context<Self>,
17873 ) {
17874 self.show_selection_menu = self
17875 .show_selection_menu
17876 .map(|show_selections_menu| !show_selections_menu)
17877 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17878
17879 cx.notify();
17880 }
17881
17882 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17883 self.show_selection_menu
17884 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17885 }
17886
17887 fn start_git_blame(
17888 &mut self,
17889 user_triggered: bool,
17890 window: &mut Window,
17891 cx: &mut Context<Self>,
17892 ) {
17893 if let Some(project) = self.project.as_ref() {
17894 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17895 return;
17896 };
17897
17898 if buffer.read(cx).file().is_none() {
17899 return;
17900 }
17901
17902 let focused = self.focus_handle(cx).contains_focused(window, cx);
17903
17904 let project = project.clone();
17905 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17906 self.blame_subscription =
17907 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17908 self.blame = Some(blame);
17909 }
17910 }
17911
17912 fn toggle_git_blame_inline_internal(
17913 &mut self,
17914 user_triggered: bool,
17915 window: &mut Window,
17916 cx: &mut Context<Self>,
17917 ) {
17918 if self.git_blame_inline_enabled {
17919 self.git_blame_inline_enabled = false;
17920 self.show_git_blame_inline = false;
17921 self.show_git_blame_inline_delay_task.take();
17922 } else {
17923 self.git_blame_inline_enabled = true;
17924 self.start_git_blame_inline(user_triggered, window, cx);
17925 }
17926
17927 cx.notify();
17928 }
17929
17930 fn start_git_blame_inline(
17931 &mut self,
17932 user_triggered: bool,
17933 window: &mut Window,
17934 cx: &mut Context<Self>,
17935 ) {
17936 self.start_git_blame(user_triggered, window, cx);
17937
17938 if ProjectSettings::get_global(cx)
17939 .git
17940 .inline_blame_delay()
17941 .is_some()
17942 {
17943 self.start_inline_blame_timer(window, cx);
17944 } else {
17945 self.show_git_blame_inline = true
17946 }
17947 }
17948
17949 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17950 self.blame.as_ref()
17951 }
17952
17953 pub fn show_git_blame_gutter(&self) -> bool {
17954 self.show_git_blame_gutter
17955 }
17956
17957 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17958 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17959 }
17960
17961 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17962 self.show_git_blame_inline
17963 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17964 && !self.newest_selection_head_on_empty_line(cx)
17965 && self.has_blame_entries(cx)
17966 }
17967
17968 fn has_blame_entries(&self, cx: &App) -> bool {
17969 self.blame()
17970 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17971 }
17972
17973 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17974 let cursor_anchor = self.selections.newest_anchor().head();
17975
17976 let snapshot = self.buffer.read(cx).snapshot(cx);
17977 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17978
17979 snapshot.line_len(buffer_row) == 0
17980 }
17981
17982 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17983 let buffer_and_selection = maybe!({
17984 let selection = self.selections.newest::<Point>(cx);
17985 let selection_range = selection.range();
17986
17987 let multi_buffer = self.buffer().read(cx);
17988 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17989 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17990
17991 let (buffer, range, _) = if selection.reversed {
17992 buffer_ranges.first()
17993 } else {
17994 buffer_ranges.last()
17995 }?;
17996
17997 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17998 ..text::ToPoint::to_point(&range.end, &buffer).row;
17999 Some((
18000 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18001 selection,
18002 ))
18003 });
18004
18005 let Some((buffer, selection)) = buffer_and_selection else {
18006 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18007 };
18008
18009 let Some(project) = self.project.as_ref() else {
18010 return Task::ready(Err(anyhow!("editor does not have project")));
18011 };
18012
18013 project.update(cx, |project, cx| {
18014 project.get_permalink_to_line(&buffer, selection, cx)
18015 })
18016 }
18017
18018 pub fn copy_permalink_to_line(
18019 &mut self,
18020 _: &CopyPermalinkToLine,
18021 window: &mut Window,
18022 cx: &mut Context<Self>,
18023 ) {
18024 let permalink_task = self.get_permalink_to_line(cx);
18025 let workspace = self.workspace();
18026
18027 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18028 Ok(permalink) => {
18029 cx.update(|_, cx| {
18030 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18031 })
18032 .ok();
18033 }
18034 Err(err) => {
18035 let message = format!("Failed to copy permalink: {err}");
18036
18037 anyhow::Result::<()>::Err(err).log_err();
18038
18039 if let Some(workspace) = workspace {
18040 workspace
18041 .update_in(cx, |workspace, _, cx| {
18042 struct CopyPermalinkToLine;
18043
18044 workspace.show_toast(
18045 Toast::new(
18046 NotificationId::unique::<CopyPermalinkToLine>(),
18047 message,
18048 ),
18049 cx,
18050 )
18051 })
18052 .ok();
18053 }
18054 }
18055 })
18056 .detach();
18057 }
18058
18059 pub fn copy_file_location(
18060 &mut self,
18061 _: &CopyFileLocation,
18062 _: &mut Window,
18063 cx: &mut Context<Self>,
18064 ) {
18065 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18066 if let Some(file) = self.target_file(cx) {
18067 if let Some(path) = file.path().to_str() {
18068 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18069 }
18070 }
18071 }
18072
18073 pub fn open_permalink_to_line(
18074 &mut self,
18075 _: &OpenPermalinkToLine,
18076 window: &mut Window,
18077 cx: &mut Context<Self>,
18078 ) {
18079 let permalink_task = self.get_permalink_to_line(cx);
18080 let workspace = self.workspace();
18081
18082 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18083 Ok(permalink) => {
18084 cx.update(|_, cx| {
18085 cx.open_url(permalink.as_ref());
18086 })
18087 .ok();
18088 }
18089 Err(err) => {
18090 let message = format!("Failed to open permalink: {err}");
18091
18092 anyhow::Result::<()>::Err(err).log_err();
18093
18094 if let Some(workspace) = workspace {
18095 workspace
18096 .update(cx, |workspace, cx| {
18097 struct OpenPermalinkToLine;
18098
18099 workspace.show_toast(
18100 Toast::new(
18101 NotificationId::unique::<OpenPermalinkToLine>(),
18102 message,
18103 ),
18104 cx,
18105 )
18106 })
18107 .ok();
18108 }
18109 }
18110 })
18111 .detach();
18112 }
18113
18114 pub fn insert_uuid_v4(
18115 &mut self,
18116 _: &InsertUuidV4,
18117 window: &mut Window,
18118 cx: &mut Context<Self>,
18119 ) {
18120 self.insert_uuid(UuidVersion::V4, window, cx);
18121 }
18122
18123 pub fn insert_uuid_v7(
18124 &mut self,
18125 _: &InsertUuidV7,
18126 window: &mut Window,
18127 cx: &mut Context<Self>,
18128 ) {
18129 self.insert_uuid(UuidVersion::V7, window, cx);
18130 }
18131
18132 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18133 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18134 self.transact(window, cx, |this, window, cx| {
18135 let edits = this
18136 .selections
18137 .all::<Point>(cx)
18138 .into_iter()
18139 .map(|selection| {
18140 let uuid = match version {
18141 UuidVersion::V4 => uuid::Uuid::new_v4(),
18142 UuidVersion::V7 => uuid::Uuid::now_v7(),
18143 };
18144
18145 (selection.range(), uuid.to_string())
18146 });
18147 this.edit(edits, cx);
18148 this.refresh_inline_completion(true, false, window, cx);
18149 });
18150 }
18151
18152 pub fn open_selections_in_multibuffer(
18153 &mut self,
18154 _: &OpenSelectionsInMultibuffer,
18155 window: &mut Window,
18156 cx: &mut Context<Self>,
18157 ) {
18158 let multibuffer = self.buffer.read(cx);
18159
18160 let Some(buffer) = multibuffer.as_singleton() else {
18161 return;
18162 };
18163
18164 let Some(workspace) = self.workspace() else {
18165 return;
18166 };
18167
18168 let locations = self
18169 .selections
18170 .disjoint_anchors()
18171 .iter()
18172 .map(|range| Location {
18173 buffer: buffer.clone(),
18174 range: range.start.text_anchor..range.end.text_anchor,
18175 })
18176 .collect::<Vec<_>>();
18177
18178 let title = multibuffer.title(cx).to_string();
18179
18180 cx.spawn_in(window, async move |_, cx| {
18181 workspace.update_in(cx, |workspace, window, cx| {
18182 Self::open_locations_in_multibuffer(
18183 workspace,
18184 locations,
18185 format!("Selections for '{title}'"),
18186 false,
18187 MultibufferSelectionMode::All,
18188 window,
18189 cx,
18190 );
18191 })
18192 })
18193 .detach();
18194 }
18195
18196 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18197 /// last highlight added will be used.
18198 ///
18199 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18200 pub fn highlight_rows<T: 'static>(
18201 &mut self,
18202 range: Range<Anchor>,
18203 color: Hsla,
18204 options: RowHighlightOptions,
18205 cx: &mut Context<Self>,
18206 ) {
18207 let snapshot = self.buffer().read(cx).snapshot(cx);
18208 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18209 let ix = row_highlights.binary_search_by(|highlight| {
18210 Ordering::Equal
18211 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18212 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18213 });
18214
18215 if let Err(mut ix) = ix {
18216 let index = post_inc(&mut self.highlight_order);
18217
18218 // If this range intersects with the preceding highlight, then merge it with
18219 // the preceding highlight. Otherwise insert a new highlight.
18220 let mut merged = false;
18221 if ix > 0 {
18222 let prev_highlight = &mut row_highlights[ix - 1];
18223 if prev_highlight
18224 .range
18225 .end
18226 .cmp(&range.start, &snapshot)
18227 .is_ge()
18228 {
18229 ix -= 1;
18230 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18231 prev_highlight.range.end = range.end;
18232 }
18233 merged = true;
18234 prev_highlight.index = index;
18235 prev_highlight.color = color;
18236 prev_highlight.options = options;
18237 }
18238 }
18239
18240 if !merged {
18241 row_highlights.insert(
18242 ix,
18243 RowHighlight {
18244 range: range.clone(),
18245 index,
18246 color,
18247 options,
18248 type_id: TypeId::of::<T>(),
18249 },
18250 );
18251 }
18252
18253 // If any of the following highlights intersect with this one, merge them.
18254 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18255 let highlight = &row_highlights[ix];
18256 if next_highlight
18257 .range
18258 .start
18259 .cmp(&highlight.range.end, &snapshot)
18260 .is_le()
18261 {
18262 if next_highlight
18263 .range
18264 .end
18265 .cmp(&highlight.range.end, &snapshot)
18266 .is_gt()
18267 {
18268 row_highlights[ix].range.end = next_highlight.range.end;
18269 }
18270 row_highlights.remove(ix + 1);
18271 } else {
18272 break;
18273 }
18274 }
18275 }
18276 }
18277
18278 /// Remove any highlighted row ranges of the given type that intersect the
18279 /// given ranges.
18280 pub fn remove_highlighted_rows<T: 'static>(
18281 &mut self,
18282 ranges_to_remove: Vec<Range<Anchor>>,
18283 cx: &mut Context<Self>,
18284 ) {
18285 let snapshot = self.buffer().read(cx).snapshot(cx);
18286 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18287 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18288 row_highlights.retain(|highlight| {
18289 while let Some(range_to_remove) = ranges_to_remove.peek() {
18290 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18291 Ordering::Less | Ordering::Equal => {
18292 ranges_to_remove.next();
18293 }
18294 Ordering::Greater => {
18295 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18296 Ordering::Less | Ordering::Equal => {
18297 return false;
18298 }
18299 Ordering::Greater => break,
18300 }
18301 }
18302 }
18303 }
18304
18305 true
18306 })
18307 }
18308
18309 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18310 pub fn clear_row_highlights<T: 'static>(&mut self) {
18311 self.highlighted_rows.remove(&TypeId::of::<T>());
18312 }
18313
18314 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18315 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18316 self.highlighted_rows
18317 .get(&TypeId::of::<T>())
18318 .map_or(&[] as &[_], |vec| vec.as_slice())
18319 .iter()
18320 .map(|highlight| (highlight.range.clone(), highlight.color))
18321 }
18322
18323 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18324 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18325 /// Allows to ignore certain kinds of highlights.
18326 pub fn highlighted_display_rows(
18327 &self,
18328 window: &mut Window,
18329 cx: &mut App,
18330 ) -> BTreeMap<DisplayRow, LineHighlight> {
18331 let snapshot = self.snapshot(window, cx);
18332 let mut used_highlight_orders = HashMap::default();
18333 self.highlighted_rows
18334 .iter()
18335 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18336 .fold(
18337 BTreeMap::<DisplayRow, LineHighlight>::new(),
18338 |mut unique_rows, highlight| {
18339 let start = highlight.range.start.to_display_point(&snapshot);
18340 let end = highlight.range.end.to_display_point(&snapshot);
18341 let start_row = start.row().0;
18342 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18343 && end.column() == 0
18344 {
18345 end.row().0.saturating_sub(1)
18346 } else {
18347 end.row().0
18348 };
18349 for row in start_row..=end_row {
18350 let used_index =
18351 used_highlight_orders.entry(row).or_insert(highlight.index);
18352 if highlight.index >= *used_index {
18353 *used_index = highlight.index;
18354 unique_rows.insert(
18355 DisplayRow(row),
18356 LineHighlight {
18357 include_gutter: highlight.options.include_gutter,
18358 border: None,
18359 background: highlight.color.into(),
18360 type_id: Some(highlight.type_id),
18361 },
18362 );
18363 }
18364 }
18365 unique_rows
18366 },
18367 )
18368 }
18369
18370 pub fn highlighted_display_row_for_autoscroll(
18371 &self,
18372 snapshot: &DisplaySnapshot,
18373 ) -> Option<DisplayRow> {
18374 self.highlighted_rows
18375 .values()
18376 .flat_map(|highlighted_rows| highlighted_rows.iter())
18377 .filter_map(|highlight| {
18378 if highlight.options.autoscroll {
18379 Some(highlight.range.start.to_display_point(snapshot).row())
18380 } else {
18381 None
18382 }
18383 })
18384 .min()
18385 }
18386
18387 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18388 self.highlight_background::<SearchWithinRange>(
18389 ranges,
18390 |colors| colors.editor_document_highlight_read_background,
18391 cx,
18392 )
18393 }
18394
18395 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18396 self.breadcrumb_header = Some(new_header);
18397 }
18398
18399 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18400 self.clear_background_highlights::<SearchWithinRange>(cx);
18401 }
18402
18403 pub fn highlight_background<T: 'static>(
18404 &mut self,
18405 ranges: &[Range<Anchor>],
18406 color_fetcher: fn(&ThemeColors) -> Hsla,
18407 cx: &mut Context<Self>,
18408 ) {
18409 self.background_highlights
18410 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18411 self.scrollbar_marker_state.dirty = true;
18412 cx.notify();
18413 }
18414
18415 pub fn clear_background_highlights<T: 'static>(
18416 &mut self,
18417 cx: &mut Context<Self>,
18418 ) -> Option<BackgroundHighlight> {
18419 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18420 if !text_highlights.1.is_empty() {
18421 self.scrollbar_marker_state.dirty = true;
18422 cx.notify();
18423 }
18424 Some(text_highlights)
18425 }
18426
18427 pub fn highlight_gutter<T: 'static>(
18428 &mut self,
18429 ranges: impl Into<Vec<Range<Anchor>>>,
18430 color_fetcher: fn(&App) -> Hsla,
18431 cx: &mut Context<Self>,
18432 ) {
18433 self.gutter_highlights
18434 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18435 cx.notify();
18436 }
18437
18438 pub fn clear_gutter_highlights<T: 'static>(
18439 &mut self,
18440 cx: &mut Context<Self>,
18441 ) -> Option<GutterHighlight> {
18442 cx.notify();
18443 self.gutter_highlights.remove(&TypeId::of::<T>())
18444 }
18445
18446 pub fn insert_gutter_highlight<T: 'static>(
18447 &mut self,
18448 range: Range<Anchor>,
18449 color_fetcher: fn(&App) -> Hsla,
18450 cx: &mut Context<Self>,
18451 ) {
18452 let snapshot = self.buffer().read(cx).snapshot(cx);
18453 let mut highlights = self
18454 .gutter_highlights
18455 .remove(&TypeId::of::<T>())
18456 .map(|(_, highlights)| highlights)
18457 .unwrap_or_default();
18458 let ix = highlights.binary_search_by(|highlight| {
18459 Ordering::Equal
18460 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18461 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18462 });
18463 if let Err(ix) = ix {
18464 highlights.insert(ix, range);
18465 }
18466 self.gutter_highlights
18467 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18468 }
18469
18470 pub fn remove_gutter_highlights<T: 'static>(
18471 &mut self,
18472 ranges_to_remove: Vec<Range<Anchor>>,
18473 cx: &mut Context<Self>,
18474 ) {
18475 let snapshot = self.buffer().read(cx).snapshot(cx);
18476 let Some((color_fetcher, mut gutter_highlights)) =
18477 self.gutter_highlights.remove(&TypeId::of::<T>())
18478 else {
18479 return;
18480 };
18481 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18482 gutter_highlights.retain(|highlight| {
18483 while let Some(range_to_remove) = ranges_to_remove.peek() {
18484 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18485 Ordering::Less | Ordering::Equal => {
18486 ranges_to_remove.next();
18487 }
18488 Ordering::Greater => {
18489 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18490 Ordering::Less | Ordering::Equal => {
18491 return false;
18492 }
18493 Ordering::Greater => break,
18494 }
18495 }
18496 }
18497 }
18498
18499 true
18500 });
18501 self.gutter_highlights
18502 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18503 }
18504
18505 #[cfg(feature = "test-support")]
18506 pub fn all_text_background_highlights(
18507 &self,
18508 window: &mut Window,
18509 cx: &mut Context<Self>,
18510 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18511 let snapshot = self.snapshot(window, cx);
18512 let buffer = &snapshot.buffer_snapshot;
18513 let start = buffer.anchor_before(0);
18514 let end = buffer.anchor_after(buffer.len());
18515 let theme = cx.theme().colors();
18516 self.background_highlights_in_range(start..end, &snapshot, theme)
18517 }
18518
18519 #[cfg(feature = "test-support")]
18520 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18521 let snapshot = self.buffer().read(cx).snapshot(cx);
18522
18523 let highlights = self
18524 .background_highlights
18525 .get(&TypeId::of::<items::BufferSearchHighlights>());
18526
18527 if let Some((_color, ranges)) = highlights {
18528 ranges
18529 .iter()
18530 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18531 .collect_vec()
18532 } else {
18533 vec![]
18534 }
18535 }
18536
18537 fn document_highlights_for_position<'a>(
18538 &'a self,
18539 position: Anchor,
18540 buffer: &'a MultiBufferSnapshot,
18541 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18542 let read_highlights = self
18543 .background_highlights
18544 .get(&TypeId::of::<DocumentHighlightRead>())
18545 .map(|h| &h.1);
18546 let write_highlights = self
18547 .background_highlights
18548 .get(&TypeId::of::<DocumentHighlightWrite>())
18549 .map(|h| &h.1);
18550 let left_position = position.bias_left(buffer);
18551 let right_position = position.bias_right(buffer);
18552 read_highlights
18553 .into_iter()
18554 .chain(write_highlights)
18555 .flat_map(move |ranges| {
18556 let start_ix = match ranges.binary_search_by(|probe| {
18557 let cmp = probe.end.cmp(&left_position, buffer);
18558 if cmp.is_ge() {
18559 Ordering::Greater
18560 } else {
18561 Ordering::Less
18562 }
18563 }) {
18564 Ok(i) | Err(i) => i,
18565 };
18566
18567 ranges[start_ix..]
18568 .iter()
18569 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18570 })
18571 }
18572
18573 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18574 self.background_highlights
18575 .get(&TypeId::of::<T>())
18576 .map_or(false, |(_, highlights)| !highlights.is_empty())
18577 }
18578
18579 pub fn background_highlights_in_range(
18580 &self,
18581 search_range: Range<Anchor>,
18582 display_snapshot: &DisplaySnapshot,
18583 theme: &ThemeColors,
18584 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18585 let mut results = Vec::new();
18586 for (color_fetcher, ranges) in self.background_highlights.values() {
18587 let color = color_fetcher(theme);
18588 let start_ix = match ranges.binary_search_by(|probe| {
18589 let cmp = probe
18590 .end
18591 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18592 if cmp.is_gt() {
18593 Ordering::Greater
18594 } else {
18595 Ordering::Less
18596 }
18597 }) {
18598 Ok(i) | Err(i) => i,
18599 };
18600 for range in &ranges[start_ix..] {
18601 if range
18602 .start
18603 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18604 .is_ge()
18605 {
18606 break;
18607 }
18608
18609 let start = range.start.to_display_point(display_snapshot);
18610 let end = range.end.to_display_point(display_snapshot);
18611 results.push((start..end, color))
18612 }
18613 }
18614 results
18615 }
18616
18617 pub fn background_highlight_row_ranges<T: 'static>(
18618 &self,
18619 search_range: Range<Anchor>,
18620 display_snapshot: &DisplaySnapshot,
18621 count: usize,
18622 ) -> Vec<RangeInclusive<DisplayPoint>> {
18623 let mut results = Vec::new();
18624 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18625 return vec![];
18626 };
18627
18628 let start_ix = match ranges.binary_search_by(|probe| {
18629 let cmp = probe
18630 .end
18631 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18632 if cmp.is_gt() {
18633 Ordering::Greater
18634 } else {
18635 Ordering::Less
18636 }
18637 }) {
18638 Ok(i) | Err(i) => i,
18639 };
18640 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18641 if let (Some(start_display), Some(end_display)) = (start, end) {
18642 results.push(
18643 start_display.to_display_point(display_snapshot)
18644 ..=end_display.to_display_point(display_snapshot),
18645 );
18646 }
18647 };
18648 let mut start_row: Option<Point> = None;
18649 let mut end_row: Option<Point> = None;
18650 if ranges.len() > count {
18651 return Vec::new();
18652 }
18653 for range in &ranges[start_ix..] {
18654 if range
18655 .start
18656 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18657 .is_ge()
18658 {
18659 break;
18660 }
18661 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18662 if let Some(current_row) = &end_row {
18663 if end.row == current_row.row {
18664 continue;
18665 }
18666 }
18667 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18668 if start_row.is_none() {
18669 assert_eq!(end_row, None);
18670 start_row = Some(start);
18671 end_row = Some(end);
18672 continue;
18673 }
18674 if let Some(current_end) = end_row.as_mut() {
18675 if start.row > current_end.row + 1 {
18676 push_region(start_row, end_row);
18677 start_row = Some(start);
18678 end_row = Some(end);
18679 } else {
18680 // Merge two hunks.
18681 *current_end = end;
18682 }
18683 } else {
18684 unreachable!();
18685 }
18686 }
18687 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18688 push_region(start_row, end_row);
18689 results
18690 }
18691
18692 pub fn gutter_highlights_in_range(
18693 &self,
18694 search_range: Range<Anchor>,
18695 display_snapshot: &DisplaySnapshot,
18696 cx: &App,
18697 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18698 let mut results = Vec::new();
18699 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18700 let color = color_fetcher(cx);
18701 let start_ix = match ranges.binary_search_by(|probe| {
18702 let cmp = probe
18703 .end
18704 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18705 if cmp.is_gt() {
18706 Ordering::Greater
18707 } else {
18708 Ordering::Less
18709 }
18710 }) {
18711 Ok(i) | Err(i) => i,
18712 };
18713 for range in &ranges[start_ix..] {
18714 if range
18715 .start
18716 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18717 .is_ge()
18718 {
18719 break;
18720 }
18721
18722 let start = range.start.to_display_point(display_snapshot);
18723 let end = range.end.to_display_point(display_snapshot);
18724 results.push((start..end, color))
18725 }
18726 }
18727 results
18728 }
18729
18730 /// Get the text ranges corresponding to the redaction query
18731 pub fn redacted_ranges(
18732 &self,
18733 search_range: Range<Anchor>,
18734 display_snapshot: &DisplaySnapshot,
18735 cx: &App,
18736 ) -> Vec<Range<DisplayPoint>> {
18737 display_snapshot
18738 .buffer_snapshot
18739 .redacted_ranges(search_range, |file| {
18740 if let Some(file) = file {
18741 file.is_private()
18742 && EditorSettings::get(
18743 Some(SettingsLocation {
18744 worktree_id: file.worktree_id(cx),
18745 path: file.path().as_ref(),
18746 }),
18747 cx,
18748 )
18749 .redact_private_values
18750 } else {
18751 false
18752 }
18753 })
18754 .map(|range| {
18755 range.start.to_display_point(display_snapshot)
18756 ..range.end.to_display_point(display_snapshot)
18757 })
18758 .collect()
18759 }
18760
18761 pub fn highlight_text<T: 'static>(
18762 &mut self,
18763 ranges: Vec<Range<Anchor>>,
18764 style: HighlightStyle,
18765 cx: &mut Context<Self>,
18766 ) {
18767 self.display_map.update(cx, |map, _| {
18768 map.highlight_text(TypeId::of::<T>(), ranges, style)
18769 });
18770 cx.notify();
18771 }
18772
18773 pub(crate) fn highlight_inlays<T: 'static>(
18774 &mut self,
18775 highlights: Vec<InlayHighlight>,
18776 style: HighlightStyle,
18777 cx: &mut Context<Self>,
18778 ) {
18779 self.display_map.update(cx, |map, _| {
18780 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18781 });
18782 cx.notify();
18783 }
18784
18785 pub fn text_highlights<'a, T: 'static>(
18786 &'a self,
18787 cx: &'a App,
18788 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18789 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18790 }
18791
18792 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18793 let cleared = self
18794 .display_map
18795 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18796 if cleared {
18797 cx.notify();
18798 }
18799 }
18800
18801 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18802 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18803 && self.focus_handle.is_focused(window)
18804 }
18805
18806 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18807 self.show_cursor_when_unfocused = is_enabled;
18808 cx.notify();
18809 }
18810
18811 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18812 cx.notify();
18813 }
18814
18815 fn on_debug_session_event(
18816 &mut self,
18817 _session: Entity<Session>,
18818 event: &SessionEvent,
18819 cx: &mut Context<Self>,
18820 ) {
18821 match event {
18822 SessionEvent::InvalidateInlineValue => {
18823 self.refresh_inline_values(cx);
18824 }
18825 _ => {}
18826 }
18827 }
18828
18829 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18830 let Some(project) = self.project.clone() else {
18831 return;
18832 };
18833
18834 if !self.inline_value_cache.enabled {
18835 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18836 self.splice_inlays(&inlays, Vec::new(), cx);
18837 return;
18838 }
18839
18840 let current_execution_position = self
18841 .highlighted_rows
18842 .get(&TypeId::of::<ActiveDebugLine>())
18843 .and_then(|lines| lines.last().map(|line| line.range.start));
18844
18845 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18846 let inline_values = editor
18847 .update(cx, |editor, cx| {
18848 let Some(current_execution_position) = current_execution_position else {
18849 return Some(Task::ready(Ok(Vec::new())));
18850 };
18851
18852 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18853 let snapshot = buffer.snapshot(cx);
18854
18855 let excerpt = snapshot.excerpt_containing(
18856 current_execution_position..current_execution_position,
18857 )?;
18858
18859 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18860 })?;
18861
18862 let range =
18863 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18864
18865 project.inline_values(buffer, range, cx)
18866 })
18867 .ok()
18868 .flatten()?
18869 .await
18870 .context("refreshing debugger inlays")
18871 .log_err()?;
18872
18873 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18874
18875 for (buffer_id, inline_value) in inline_values
18876 .into_iter()
18877 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18878 {
18879 buffer_inline_values
18880 .entry(buffer_id)
18881 .or_default()
18882 .push(inline_value);
18883 }
18884
18885 editor
18886 .update(cx, |editor, cx| {
18887 let snapshot = editor.buffer.read(cx).snapshot(cx);
18888 let mut new_inlays = Vec::default();
18889
18890 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18891 let buffer_id = buffer_snapshot.remote_id();
18892 buffer_inline_values
18893 .get(&buffer_id)
18894 .into_iter()
18895 .flatten()
18896 .for_each(|hint| {
18897 let inlay = Inlay::debugger_hint(
18898 post_inc(&mut editor.next_inlay_id),
18899 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18900 hint.text(),
18901 );
18902
18903 new_inlays.push(inlay);
18904 });
18905 }
18906
18907 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18908 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18909
18910 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18911 })
18912 .ok()?;
18913 Some(())
18914 });
18915 }
18916
18917 fn on_buffer_event(
18918 &mut self,
18919 multibuffer: &Entity<MultiBuffer>,
18920 event: &multi_buffer::Event,
18921 window: &mut Window,
18922 cx: &mut Context<Self>,
18923 ) {
18924 match event {
18925 multi_buffer::Event::Edited {
18926 singleton_buffer_edited,
18927 edited_buffer,
18928 } => {
18929 self.scrollbar_marker_state.dirty = true;
18930 self.active_indent_guides_state.dirty = true;
18931 self.refresh_active_diagnostics(cx);
18932 self.refresh_code_actions(window, cx);
18933 self.refresh_selected_text_highlights(true, window, cx);
18934 refresh_matching_bracket_highlights(self, window, cx);
18935 if self.has_active_inline_completion() {
18936 self.update_visible_inline_completion(window, cx);
18937 }
18938 if let Some(project) = self.project.as_ref() {
18939 if let Some(edited_buffer) = edited_buffer {
18940 project.update(cx, |project, cx| {
18941 self.registered_buffers
18942 .entry(edited_buffer.read(cx).remote_id())
18943 .or_insert_with(|| {
18944 project
18945 .register_buffer_with_language_servers(&edited_buffer, cx)
18946 });
18947 });
18948 if edited_buffer.read(cx).file().is_some() {
18949 self.pull_diagnostics(
18950 Some(edited_buffer.read(cx).remote_id()),
18951 window,
18952 cx,
18953 );
18954 }
18955 }
18956 }
18957 cx.emit(EditorEvent::BufferEdited);
18958 cx.emit(SearchEvent::MatchesInvalidated);
18959 if *singleton_buffer_edited {
18960 if let Some(buffer) = edited_buffer {
18961 if buffer.read(cx).file().is_none() {
18962 cx.emit(EditorEvent::TitleChanged);
18963 }
18964 }
18965 if let Some(project) = &self.project {
18966 #[allow(clippy::mutable_key_type)]
18967 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18968 multibuffer
18969 .all_buffers()
18970 .into_iter()
18971 .filter_map(|buffer| {
18972 buffer.update(cx, |buffer, cx| {
18973 let language = buffer.language()?;
18974 let should_discard = project.update(cx, |project, cx| {
18975 project.is_local()
18976 && !project.has_language_servers_for(buffer, cx)
18977 });
18978 should_discard.not().then_some(language.clone())
18979 })
18980 })
18981 .collect::<HashSet<_>>()
18982 });
18983 if !languages_affected.is_empty() {
18984 self.refresh_inlay_hints(
18985 InlayHintRefreshReason::BufferEdited(languages_affected),
18986 cx,
18987 );
18988 }
18989 }
18990 }
18991
18992 let Some(project) = &self.project else { return };
18993 let (telemetry, is_via_ssh) = {
18994 let project = project.read(cx);
18995 let telemetry = project.client().telemetry().clone();
18996 let is_via_ssh = project.is_via_ssh();
18997 (telemetry, is_via_ssh)
18998 };
18999 refresh_linked_ranges(self, window, cx);
19000 telemetry.log_edit_event("editor", is_via_ssh);
19001 }
19002 multi_buffer::Event::ExcerptsAdded {
19003 buffer,
19004 predecessor,
19005 excerpts,
19006 } => {
19007 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19008 let buffer_id = buffer.read(cx).remote_id();
19009 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19010 if let Some(project) = &self.project {
19011 update_uncommitted_diff_for_buffer(
19012 cx.entity(),
19013 project,
19014 [buffer.clone()],
19015 self.buffer.clone(),
19016 cx,
19017 )
19018 .detach();
19019 }
19020 }
19021 cx.emit(EditorEvent::ExcerptsAdded {
19022 buffer: buffer.clone(),
19023 predecessor: *predecessor,
19024 excerpts: excerpts.clone(),
19025 });
19026 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19027 }
19028 multi_buffer::Event::ExcerptsRemoved {
19029 ids,
19030 removed_buffer_ids,
19031 } => {
19032 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19033 let buffer = self.buffer.read(cx);
19034 self.registered_buffers
19035 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19036 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19037 cx.emit(EditorEvent::ExcerptsRemoved {
19038 ids: ids.clone(),
19039 removed_buffer_ids: removed_buffer_ids.clone(),
19040 })
19041 }
19042 multi_buffer::Event::ExcerptsEdited {
19043 excerpt_ids,
19044 buffer_ids,
19045 } => {
19046 self.display_map.update(cx, |map, cx| {
19047 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19048 });
19049 cx.emit(EditorEvent::ExcerptsEdited {
19050 ids: excerpt_ids.clone(),
19051 })
19052 }
19053 multi_buffer::Event::ExcerptsExpanded { ids } => {
19054 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19055 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19056 }
19057 multi_buffer::Event::Reparsed(buffer_id) => {
19058 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19059 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19060
19061 cx.emit(EditorEvent::Reparsed(*buffer_id));
19062 }
19063 multi_buffer::Event::DiffHunksToggled => {
19064 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19065 }
19066 multi_buffer::Event::LanguageChanged(buffer_id) => {
19067 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19068 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19069 cx.emit(EditorEvent::Reparsed(*buffer_id));
19070 cx.notify();
19071 }
19072 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19073 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19074 multi_buffer::Event::FileHandleChanged
19075 | multi_buffer::Event::Reloaded
19076 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19077 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19078 multi_buffer::Event::DiagnosticsUpdated => {
19079 self.update_diagnostics_state(window, cx);
19080 }
19081 _ => {}
19082 };
19083 }
19084
19085 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19086 self.refresh_active_diagnostics(cx);
19087 self.refresh_inline_diagnostics(true, window, cx);
19088 self.scrollbar_marker_state.dirty = true;
19089 cx.notify();
19090 }
19091
19092 pub fn start_temporary_diff_override(&mut self) {
19093 self.load_diff_task.take();
19094 self.temporary_diff_override = true;
19095 }
19096
19097 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19098 self.temporary_diff_override = false;
19099 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19100 self.buffer.update(cx, |buffer, cx| {
19101 buffer.set_all_diff_hunks_collapsed(cx);
19102 });
19103
19104 if let Some(project) = self.project.clone() {
19105 self.load_diff_task = Some(
19106 update_uncommitted_diff_for_buffer(
19107 cx.entity(),
19108 &project,
19109 self.buffer.read(cx).all_buffers(),
19110 self.buffer.clone(),
19111 cx,
19112 )
19113 .shared(),
19114 );
19115 }
19116 }
19117
19118 fn on_display_map_changed(
19119 &mut self,
19120 _: Entity<DisplayMap>,
19121 _: &mut Window,
19122 cx: &mut Context<Self>,
19123 ) {
19124 cx.notify();
19125 }
19126
19127 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19128 let new_severity = if self.diagnostics_enabled() {
19129 EditorSettings::get_global(cx)
19130 .diagnostics_max_severity
19131 .unwrap_or(DiagnosticSeverity::Hint)
19132 } else {
19133 DiagnosticSeverity::Off
19134 };
19135 self.set_max_diagnostics_severity(new_severity, cx);
19136 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19137 self.update_edit_prediction_settings(cx);
19138 self.refresh_inline_completion(true, false, window, cx);
19139 self.refresh_inlay_hints(
19140 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19141 self.selections.newest_anchor().head(),
19142 &self.buffer.read(cx).snapshot(cx),
19143 cx,
19144 )),
19145 cx,
19146 );
19147
19148 let old_cursor_shape = self.cursor_shape;
19149
19150 {
19151 let editor_settings = EditorSettings::get_global(cx);
19152 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19153 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19154 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19155 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19156 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19157 }
19158
19159 if old_cursor_shape != self.cursor_shape {
19160 cx.emit(EditorEvent::CursorShapeChanged);
19161 }
19162
19163 let project_settings = ProjectSettings::get_global(cx);
19164 self.serialize_dirty_buffers =
19165 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19166
19167 if self.mode.is_full() {
19168 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19169 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19170 if self.show_inline_diagnostics != show_inline_diagnostics {
19171 self.show_inline_diagnostics = show_inline_diagnostics;
19172 self.refresh_inline_diagnostics(false, window, cx);
19173 }
19174
19175 if self.git_blame_inline_enabled != inline_blame_enabled {
19176 self.toggle_git_blame_inline_internal(false, window, cx);
19177 }
19178
19179 let minimap_settings = EditorSettings::get_global(cx).minimap;
19180 if self.minimap_visibility != MinimapVisibility::Disabled {
19181 if self.minimap_visibility.settings_visibility()
19182 != minimap_settings.minimap_enabled()
19183 {
19184 self.set_minimap_visibility(
19185 MinimapVisibility::for_mode(self.mode(), cx),
19186 window,
19187 cx,
19188 );
19189 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19190 minimap_entity.update(cx, |minimap_editor, cx| {
19191 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19192 })
19193 }
19194 }
19195 }
19196
19197 cx.notify();
19198 }
19199
19200 pub fn set_searchable(&mut self, searchable: bool) {
19201 self.searchable = searchable;
19202 }
19203
19204 pub fn searchable(&self) -> bool {
19205 self.searchable
19206 }
19207
19208 fn open_proposed_changes_editor(
19209 &mut self,
19210 _: &OpenProposedChangesEditor,
19211 window: &mut Window,
19212 cx: &mut Context<Self>,
19213 ) {
19214 let Some(workspace) = self.workspace() else {
19215 cx.propagate();
19216 return;
19217 };
19218
19219 let selections = self.selections.all::<usize>(cx);
19220 let multi_buffer = self.buffer.read(cx);
19221 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19222 let mut new_selections_by_buffer = HashMap::default();
19223 for selection in selections {
19224 for (buffer, range, _) in
19225 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19226 {
19227 let mut range = range.to_point(buffer);
19228 range.start.column = 0;
19229 range.end.column = buffer.line_len(range.end.row);
19230 new_selections_by_buffer
19231 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19232 .or_insert(Vec::new())
19233 .push(range)
19234 }
19235 }
19236
19237 let proposed_changes_buffers = new_selections_by_buffer
19238 .into_iter()
19239 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19240 .collect::<Vec<_>>();
19241 let proposed_changes_editor = cx.new(|cx| {
19242 ProposedChangesEditor::new(
19243 "Proposed changes",
19244 proposed_changes_buffers,
19245 self.project.clone(),
19246 window,
19247 cx,
19248 )
19249 });
19250
19251 window.defer(cx, move |window, cx| {
19252 workspace.update(cx, |workspace, cx| {
19253 workspace.active_pane().update(cx, |pane, cx| {
19254 pane.add_item(
19255 Box::new(proposed_changes_editor),
19256 true,
19257 true,
19258 None,
19259 window,
19260 cx,
19261 );
19262 });
19263 });
19264 });
19265 }
19266
19267 pub fn open_excerpts_in_split(
19268 &mut self,
19269 _: &OpenExcerptsSplit,
19270 window: &mut Window,
19271 cx: &mut Context<Self>,
19272 ) {
19273 self.open_excerpts_common(None, true, window, cx)
19274 }
19275
19276 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19277 self.open_excerpts_common(None, false, window, cx)
19278 }
19279
19280 fn open_excerpts_common(
19281 &mut self,
19282 jump_data: Option<JumpData>,
19283 split: bool,
19284 window: &mut Window,
19285 cx: &mut Context<Self>,
19286 ) {
19287 let Some(workspace) = self.workspace() else {
19288 cx.propagate();
19289 return;
19290 };
19291
19292 if self.buffer.read(cx).is_singleton() {
19293 cx.propagate();
19294 return;
19295 }
19296
19297 let mut new_selections_by_buffer = HashMap::default();
19298 match &jump_data {
19299 Some(JumpData::MultiBufferPoint {
19300 excerpt_id,
19301 position,
19302 anchor,
19303 line_offset_from_top,
19304 }) => {
19305 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19306 if let Some(buffer) = multi_buffer_snapshot
19307 .buffer_id_for_excerpt(*excerpt_id)
19308 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19309 {
19310 let buffer_snapshot = buffer.read(cx).snapshot();
19311 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19312 language::ToPoint::to_point(anchor, &buffer_snapshot)
19313 } else {
19314 buffer_snapshot.clip_point(*position, Bias::Left)
19315 };
19316 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19317 new_selections_by_buffer.insert(
19318 buffer,
19319 (
19320 vec![jump_to_offset..jump_to_offset],
19321 Some(*line_offset_from_top),
19322 ),
19323 );
19324 }
19325 }
19326 Some(JumpData::MultiBufferRow {
19327 row,
19328 line_offset_from_top,
19329 }) => {
19330 let point = MultiBufferPoint::new(row.0, 0);
19331 if let Some((buffer, buffer_point, _)) =
19332 self.buffer.read(cx).point_to_buffer_point(point, cx)
19333 {
19334 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19335 new_selections_by_buffer
19336 .entry(buffer)
19337 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19338 .0
19339 .push(buffer_offset..buffer_offset)
19340 }
19341 }
19342 None => {
19343 let selections = self.selections.all::<usize>(cx);
19344 let multi_buffer = self.buffer.read(cx);
19345 for selection in selections {
19346 for (snapshot, range, _, anchor) in multi_buffer
19347 .snapshot(cx)
19348 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19349 {
19350 if let Some(anchor) = anchor {
19351 // selection is in a deleted hunk
19352 let Some(buffer_id) = anchor.buffer_id else {
19353 continue;
19354 };
19355 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19356 continue;
19357 };
19358 let offset = text::ToOffset::to_offset(
19359 &anchor.text_anchor,
19360 &buffer_handle.read(cx).snapshot(),
19361 );
19362 let range = offset..offset;
19363 new_selections_by_buffer
19364 .entry(buffer_handle)
19365 .or_insert((Vec::new(), None))
19366 .0
19367 .push(range)
19368 } else {
19369 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19370 else {
19371 continue;
19372 };
19373 new_selections_by_buffer
19374 .entry(buffer_handle)
19375 .or_insert((Vec::new(), None))
19376 .0
19377 .push(range)
19378 }
19379 }
19380 }
19381 }
19382 }
19383
19384 new_selections_by_buffer
19385 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19386
19387 if new_selections_by_buffer.is_empty() {
19388 return;
19389 }
19390
19391 // We defer the pane interaction because we ourselves are a workspace item
19392 // and activating a new item causes the pane to call a method on us reentrantly,
19393 // which panics if we're on the stack.
19394 window.defer(cx, move |window, cx| {
19395 workspace.update(cx, |workspace, cx| {
19396 let pane = if split {
19397 workspace.adjacent_pane(window, cx)
19398 } else {
19399 workspace.active_pane().clone()
19400 };
19401
19402 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19403 let editor = buffer
19404 .read(cx)
19405 .file()
19406 .is_none()
19407 .then(|| {
19408 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19409 // so `workspace.open_project_item` will never find them, always opening a new editor.
19410 // Instead, we try to activate the existing editor in the pane first.
19411 let (editor, pane_item_index) =
19412 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19413 let editor = item.downcast::<Editor>()?;
19414 let singleton_buffer =
19415 editor.read(cx).buffer().read(cx).as_singleton()?;
19416 if singleton_buffer == buffer {
19417 Some((editor, i))
19418 } else {
19419 None
19420 }
19421 })?;
19422 pane.update(cx, |pane, cx| {
19423 pane.activate_item(pane_item_index, true, true, window, cx)
19424 });
19425 Some(editor)
19426 })
19427 .flatten()
19428 .unwrap_or_else(|| {
19429 workspace.open_project_item::<Self>(
19430 pane.clone(),
19431 buffer,
19432 true,
19433 true,
19434 window,
19435 cx,
19436 )
19437 });
19438
19439 editor.update(cx, |editor, cx| {
19440 let autoscroll = match scroll_offset {
19441 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19442 None => Autoscroll::newest(),
19443 };
19444 let nav_history = editor.nav_history.take();
19445 editor.change_selections(Some(autoscroll), window, cx, |s| {
19446 s.select_ranges(ranges);
19447 });
19448 editor.nav_history = nav_history;
19449 });
19450 }
19451 })
19452 });
19453 }
19454
19455 // For now, don't allow opening excerpts in buffers that aren't backed by
19456 // regular project files.
19457 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19458 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19459 }
19460
19461 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19462 let snapshot = self.buffer.read(cx).read(cx);
19463 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19464 Some(
19465 ranges
19466 .iter()
19467 .map(move |range| {
19468 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19469 })
19470 .collect(),
19471 )
19472 }
19473
19474 fn selection_replacement_ranges(
19475 &self,
19476 range: Range<OffsetUtf16>,
19477 cx: &mut App,
19478 ) -> Vec<Range<OffsetUtf16>> {
19479 let selections = self.selections.all::<OffsetUtf16>(cx);
19480 let newest_selection = selections
19481 .iter()
19482 .max_by_key(|selection| selection.id)
19483 .unwrap();
19484 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19485 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19486 let snapshot = self.buffer.read(cx).read(cx);
19487 selections
19488 .into_iter()
19489 .map(|mut selection| {
19490 selection.start.0 =
19491 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19492 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19493 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19494 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19495 })
19496 .collect()
19497 }
19498
19499 fn report_editor_event(
19500 &self,
19501 event_type: &'static str,
19502 file_extension: Option<String>,
19503 cx: &App,
19504 ) {
19505 if cfg!(any(test, feature = "test-support")) {
19506 return;
19507 }
19508
19509 let Some(project) = &self.project else { return };
19510
19511 // If None, we are in a file without an extension
19512 let file = self
19513 .buffer
19514 .read(cx)
19515 .as_singleton()
19516 .and_then(|b| b.read(cx).file());
19517 let file_extension = file_extension.or(file
19518 .as_ref()
19519 .and_then(|file| Path::new(file.file_name(cx)).extension())
19520 .and_then(|e| e.to_str())
19521 .map(|a| a.to_string()));
19522
19523 let vim_mode = vim_enabled(cx);
19524
19525 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19526 let copilot_enabled = edit_predictions_provider
19527 == language::language_settings::EditPredictionProvider::Copilot;
19528 let copilot_enabled_for_language = self
19529 .buffer
19530 .read(cx)
19531 .language_settings(cx)
19532 .show_edit_predictions;
19533
19534 let project = project.read(cx);
19535 telemetry::event!(
19536 event_type,
19537 file_extension,
19538 vim_mode,
19539 copilot_enabled,
19540 copilot_enabled_for_language,
19541 edit_predictions_provider,
19542 is_via_ssh = project.is_via_ssh(),
19543 );
19544 }
19545
19546 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19547 /// with each line being an array of {text, highlight} objects.
19548 fn copy_highlight_json(
19549 &mut self,
19550 _: &CopyHighlightJson,
19551 window: &mut Window,
19552 cx: &mut Context<Self>,
19553 ) {
19554 #[derive(Serialize)]
19555 struct Chunk<'a> {
19556 text: String,
19557 highlight: Option<&'a str>,
19558 }
19559
19560 let snapshot = self.buffer.read(cx).snapshot(cx);
19561 let range = self
19562 .selected_text_range(false, window, cx)
19563 .and_then(|selection| {
19564 if selection.range.is_empty() {
19565 None
19566 } else {
19567 Some(selection.range)
19568 }
19569 })
19570 .unwrap_or_else(|| 0..snapshot.len());
19571
19572 let chunks = snapshot.chunks(range, true);
19573 let mut lines = Vec::new();
19574 let mut line: VecDeque<Chunk> = VecDeque::new();
19575
19576 let Some(style) = self.style.as_ref() else {
19577 return;
19578 };
19579
19580 for chunk in chunks {
19581 let highlight = chunk
19582 .syntax_highlight_id
19583 .and_then(|id| id.name(&style.syntax));
19584 let mut chunk_lines = chunk.text.split('\n').peekable();
19585 while let Some(text) = chunk_lines.next() {
19586 let mut merged_with_last_token = false;
19587 if let Some(last_token) = line.back_mut() {
19588 if last_token.highlight == highlight {
19589 last_token.text.push_str(text);
19590 merged_with_last_token = true;
19591 }
19592 }
19593
19594 if !merged_with_last_token {
19595 line.push_back(Chunk {
19596 text: text.into(),
19597 highlight,
19598 });
19599 }
19600
19601 if chunk_lines.peek().is_some() {
19602 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19603 line.pop_front();
19604 }
19605 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19606 line.pop_back();
19607 }
19608
19609 lines.push(mem::take(&mut line));
19610 }
19611 }
19612 }
19613
19614 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19615 return;
19616 };
19617 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19618 }
19619
19620 pub fn open_context_menu(
19621 &mut self,
19622 _: &OpenContextMenu,
19623 window: &mut Window,
19624 cx: &mut Context<Self>,
19625 ) {
19626 self.request_autoscroll(Autoscroll::newest(), cx);
19627 let position = self.selections.newest_display(cx).start;
19628 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19629 }
19630
19631 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19632 &self.inlay_hint_cache
19633 }
19634
19635 pub fn replay_insert_event(
19636 &mut self,
19637 text: &str,
19638 relative_utf16_range: Option<Range<isize>>,
19639 window: &mut Window,
19640 cx: &mut Context<Self>,
19641 ) {
19642 if !self.input_enabled {
19643 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19644 return;
19645 }
19646 if let Some(relative_utf16_range) = relative_utf16_range {
19647 let selections = self.selections.all::<OffsetUtf16>(cx);
19648 self.change_selections(None, window, cx, |s| {
19649 let new_ranges = selections.into_iter().map(|range| {
19650 let start = OffsetUtf16(
19651 range
19652 .head()
19653 .0
19654 .saturating_add_signed(relative_utf16_range.start),
19655 );
19656 let end = OffsetUtf16(
19657 range
19658 .head()
19659 .0
19660 .saturating_add_signed(relative_utf16_range.end),
19661 );
19662 start..end
19663 });
19664 s.select_ranges(new_ranges);
19665 });
19666 }
19667
19668 self.handle_input(text, window, cx);
19669 }
19670
19671 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19672 let Some(provider) = self.semantics_provider.as_ref() else {
19673 return false;
19674 };
19675
19676 let mut supports = false;
19677 self.buffer().update(cx, |this, cx| {
19678 this.for_each_buffer(|buffer| {
19679 supports |= provider.supports_inlay_hints(buffer, cx);
19680 });
19681 });
19682
19683 supports
19684 }
19685
19686 pub fn is_focused(&self, window: &Window) -> bool {
19687 self.focus_handle.is_focused(window)
19688 }
19689
19690 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19691 cx.emit(EditorEvent::Focused);
19692
19693 if let Some(descendant) = self
19694 .last_focused_descendant
19695 .take()
19696 .and_then(|descendant| descendant.upgrade())
19697 {
19698 window.focus(&descendant);
19699 } else {
19700 if let Some(blame) = self.blame.as_ref() {
19701 blame.update(cx, GitBlame::focus)
19702 }
19703
19704 self.blink_manager.update(cx, BlinkManager::enable);
19705 self.show_cursor_names(window, cx);
19706 self.buffer.update(cx, |buffer, cx| {
19707 buffer.finalize_last_transaction(cx);
19708 if self.leader_id.is_none() {
19709 buffer.set_active_selections(
19710 &self.selections.disjoint_anchors(),
19711 self.selections.line_mode,
19712 self.cursor_shape,
19713 cx,
19714 );
19715 }
19716 });
19717 }
19718 }
19719
19720 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19721 cx.emit(EditorEvent::FocusedIn)
19722 }
19723
19724 fn handle_focus_out(
19725 &mut self,
19726 event: FocusOutEvent,
19727 _window: &mut Window,
19728 cx: &mut Context<Self>,
19729 ) {
19730 if event.blurred != self.focus_handle {
19731 self.last_focused_descendant = Some(event.blurred);
19732 }
19733 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19734 }
19735
19736 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19737 self.blink_manager.update(cx, BlinkManager::disable);
19738 self.buffer
19739 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19740
19741 if let Some(blame) = self.blame.as_ref() {
19742 blame.update(cx, GitBlame::blur)
19743 }
19744 if !self.hover_state.focused(window, cx) {
19745 hide_hover(self, cx);
19746 }
19747 if !self
19748 .context_menu
19749 .borrow()
19750 .as_ref()
19751 .is_some_and(|context_menu| context_menu.focused(window, cx))
19752 {
19753 self.hide_context_menu(window, cx);
19754 }
19755 self.discard_inline_completion(false, cx);
19756 cx.emit(EditorEvent::Blurred);
19757 cx.notify();
19758 }
19759
19760 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19761 let mut pending: String = window
19762 .pending_input_keystrokes()
19763 .into_iter()
19764 .flatten()
19765 .filter_map(|keystroke| {
19766 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19767 keystroke.key_char.clone()
19768 } else {
19769 None
19770 }
19771 })
19772 .collect();
19773
19774 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19775 pending = "".to_string();
19776 }
19777
19778 let existing_pending = self
19779 .text_highlights::<PendingInput>(cx)
19780 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19781 if existing_pending.is_none() && pending.is_empty() {
19782 return;
19783 }
19784 let transaction =
19785 self.transact(window, cx, |this, window, cx| {
19786 let selections = this.selections.all::<usize>(cx);
19787 let edits = selections
19788 .iter()
19789 .map(|selection| (selection.end..selection.end, pending.clone()));
19790 this.edit(edits, cx);
19791 this.change_selections(None, window, cx, |s| {
19792 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19793 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19794 }));
19795 });
19796 if let Some(existing_ranges) = existing_pending {
19797 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19798 this.edit(edits, cx);
19799 }
19800 });
19801
19802 let snapshot = self.snapshot(window, cx);
19803 let ranges = self
19804 .selections
19805 .all::<usize>(cx)
19806 .into_iter()
19807 .map(|selection| {
19808 snapshot.buffer_snapshot.anchor_after(selection.end)
19809 ..snapshot
19810 .buffer_snapshot
19811 .anchor_before(selection.end + pending.len())
19812 })
19813 .collect();
19814
19815 if pending.is_empty() {
19816 self.clear_highlights::<PendingInput>(cx);
19817 } else {
19818 self.highlight_text::<PendingInput>(
19819 ranges,
19820 HighlightStyle {
19821 underline: Some(UnderlineStyle {
19822 thickness: px(1.),
19823 color: None,
19824 wavy: false,
19825 }),
19826 ..Default::default()
19827 },
19828 cx,
19829 );
19830 }
19831
19832 self.ime_transaction = self.ime_transaction.or(transaction);
19833 if let Some(transaction) = self.ime_transaction {
19834 self.buffer.update(cx, |buffer, cx| {
19835 buffer.group_until_transaction(transaction, cx);
19836 });
19837 }
19838
19839 if self.text_highlights::<PendingInput>(cx).is_none() {
19840 self.ime_transaction.take();
19841 }
19842 }
19843
19844 pub fn register_action_renderer(
19845 &mut self,
19846 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19847 ) -> Subscription {
19848 let id = self.next_editor_action_id.post_inc();
19849 self.editor_actions
19850 .borrow_mut()
19851 .insert(id, Box::new(listener));
19852
19853 let editor_actions = self.editor_actions.clone();
19854 Subscription::new(move || {
19855 editor_actions.borrow_mut().remove(&id);
19856 })
19857 }
19858
19859 pub fn register_action<A: Action>(
19860 &mut self,
19861 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19862 ) -> Subscription {
19863 let id = self.next_editor_action_id.post_inc();
19864 let listener = Arc::new(listener);
19865 self.editor_actions.borrow_mut().insert(
19866 id,
19867 Box::new(move |_, window, _| {
19868 let listener = listener.clone();
19869 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19870 let action = action.downcast_ref().unwrap();
19871 if phase == DispatchPhase::Bubble {
19872 listener(action, window, cx)
19873 }
19874 })
19875 }),
19876 );
19877
19878 let editor_actions = self.editor_actions.clone();
19879 Subscription::new(move || {
19880 editor_actions.borrow_mut().remove(&id);
19881 })
19882 }
19883
19884 pub fn file_header_size(&self) -> u32 {
19885 FILE_HEADER_HEIGHT
19886 }
19887
19888 pub fn restore(
19889 &mut self,
19890 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19891 window: &mut Window,
19892 cx: &mut Context<Self>,
19893 ) {
19894 let workspace = self.workspace();
19895 let project = self.project.as_ref();
19896 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19897 let mut tasks = Vec::new();
19898 for (buffer_id, changes) in revert_changes {
19899 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19900 buffer.update(cx, |buffer, cx| {
19901 buffer.edit(
19902 changes
19903 .into_iter()
19904 .map(|(range, text)| (range, text.to_string())),
19905 None,
19906 cx,
19907 );
19908 });
19909
19910 if let Some(project) =
19911 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19912 {
19913 project.update(cx, |project, cx| {
19914 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19915 })
19916 }
19917 }
19918 }
19919 tasks
19920 });
19921 cx.spawn_in(window, async move |_, cx| {
19922 for (buffer, task) in save_tasks {
19923 let result = task.await;
19924 if result.is_err() {
19925 let Some(path) = buffer
19926 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19927 .ok()
19928 else {
19929 continue;
19930 };
19931 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19932 let Some(task) = cx
19933 .update_window_entity(&workspace, |workspace, window, cx| {
19934 workspace
19935 .open_path_preview(path, None, false, false, false, window, cx)
19936 })
19937 .ok()
19938 else {
19939 continue;
19940 };
19941 task.await.log_err();
19942 }
19943 }
19944 }
19945 })
19946 .detach();
19947 self.change_selections(None, window, cx, |selections| selections.refresh());
19948 }
19949
19950 pub fn to_pixel_point(
19951 &self,
19952 source: multi_buffer::Anchor,
19953 editor_snapshot: &EditorSnapshot,
19954 window: &mut Window,
19955 ) -> Option<gpui::Point<Pixels>> {
19956 let source_point = source.to_display_point(editor_snapshot);
19957 self.display_to_pixel_point(source_point, editor_snapshot, window)
19958 }
19959
19960 pub fn display_to_pixel_point(
19961 &self,
19962 source: DisplayPoint,
19963 editor_snapshot: &EditorSnapshot,
19964 window: &mut Window,
19965 ) -> Option<gpui::Point<Pixels>> {
19966 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19967 let text_layout_details = self.text_layout_details(window);
19968 let scroll_top = text_layout_details
19969 .scroll_anchor
19970 .scroll_position(editor_snapshot)
19971 .y;
19972
19973 if source.row().as_f32() < scroll_top.floor() {
19974 return None;
19975 }
19976 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19977 let source_y = line_height * (source.row().as_f32() - scroll_top);
19978 Some(gpui::Point::new(source_x, source_y))
19979 }
19980
19981 pub fn has_visible_completions_menu(&self) -> bool {
19982 !self.edit_prediction_preview_is_active()
19983 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19984 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19985 })
19986 }
19987
19988 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19989 if self.mode.is_minimap() {
19990 return;
19991 }
19992 self.addons
19993 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19994 }
19995
19996 pub fn unregister_addon<T: Addon>(&mut self) {
19997 self.addons.remove(&std::any::TypeId::of::<T>());
19998 }
19999
20000 pub fn addon<T: Addon>(&self) -> Option<&T> {
20001 let type_id = std::any::TypeId::of::<T>();
20002 self.addons
20003 .get(&type_id)
20004 .and_then(|item| item.to_any().downcast_ref::<T>())
20005 }
20006
20007 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20008 let type_id = std::any::TypeId::of::<T>();
20009 self.addons
20010 .get_mut(&type_id)
20011 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20012 }
20013
20014 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20015 let text_layout_details = self.text_layout_details(window);
20016 let style = &text_layout_details.editor_style;
20017 let font_id = window.text_system().resolve_font(&style.text.font());
20018 let font_size = style.text.font_size.to_pixels(window.rem_size());
20019 let line_height = style.text.line_height_in_pixels(window.rem_size());
20020 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20021
20022 gpui::Size::new(em_width, line_height)
20023 }
20024
20025 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20026 self.load_diff_task.clone()
20027 }
20028
20029 fn read_metadata_from_db(
20030 &mut self,
20031 item_id: u64,
20032 workspace_id: WorkspaceId,
20033 window: &mut Window,
20034 cx: &mut Context<Editor>,
20035 ) {
20036 if self.is_singleton(cx)
20037 && !self.mode.is_minimap()
20038 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20039 {
20040 let buffer_snapshot = OnceCell::new();
20041
20042 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20043 if !folds.is_empty() {
20044 let snapshot =
20045 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20046 self.fold_ranges(
20047 folds
20048 .into_iter()
20049 .map(|(start, end)| {
20050 snapshot.clip_offset(start, Bias::Left)
20051 ..snapshot.clip_offset(end, Bias::Right)
20052 })
20053 .collect(),
20054 false,
20055 window,
20056 cx,
20057 );
20058 }
20059 }
20060
20061 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20062 if !selections.is_empty() {
20063 let snapshot =
20064 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20065 // skip adding the initial selection to selection history
20066 self.selection_history.mode = SelectionHistoryMode::Skipping;
20067 self.change_selections(None, window, cx, |s| {
20068 s.select_ranges(selections.into_iter().map(|(start, end)| {
20069 snapshot.clip_offset(start, Bias::Left)
20070 ..snapshot.clip_offset(end, Bias::Right)
20071 }));
20072 });
20073 self.selection_history.mode = SelectionHistoryMode::Normal;
20074 }
20075 };
20076 }
20077
20078 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20079 }
20080}
20081
20082fn vim_enabled(cx: &App) -> bool {
20083 cx.global::<SettingsStore>()
20084 .raw_user_settings()
20085 .get("vim_mode")
20086 == Some(&serde_json::Value::Bool(true))
20087}
20088
20089fn process_completion_for_edit(
20090 completion: &Completion,
20091 intent: CompletionIntent,
20092 buffer: &Entity<Buffer>,
20093 cursor_position: &text::Anchor,
20094 cx: &mut Context<Editor>,
20095) -> CompletionEdit {
20096 let buffer = buffer.read(cx);
20097 let buffer_snapshot = buffer.snapshot();
20098 let (snippet, new_text) = if completion.is_snippet() {
20099 // Workaround for typescript language server issues so that methods don't expand within
20100 // strings and functions with type expressions. The previous point is used because the query
20101 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20102 let mut snippet_source = completion.new_text.clone();
20103 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20104 previous_point.column = previous_point.column.saturating_sub(1);
20105 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20106 if scope.prefers_label_for_snippet_in_completion() {
20107 if let Some(label) = completion.label() {
20108 if matches!(
20109 completion.kind(),
20110 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20111 ) {
20112 snippet_source = label;
20113 }
20114 }
20115 }
20116 }
20117 match Snippet::parse(&snippet_source).log_err() {
20118 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20119 None => (None, completion.new_text.clone()),
20120 }
20121 } else {
20122 (None, completion.new_text.clone())
20123 };
20124
20125 let mut range_to_replace = {
20126 let replace_range = &completion.replace_range;
20127 if let CompletionSource::Lsp {
20128 insert_range: Some(insert_range),
20129 ..
20130 } = &completion.source
20131 {
20132 debug_assert_eq!(
20133 insert_range.start, replace_range.start,
20134 "insert_range and replace_range should start at the same position"
20135 );
20136 debug_assert!(
20137 insert_range
20138 .start
20139 .cmp(&cursor_position, &buffer_snapshot)
20140 .is_le(),
20141 "insert_range should start before or at cursor position"
20142 );
20143 debug_assert!(
20144 replace_range
20145 .start
20146 .cmp(&cursor_position, &buffer_snapshot)
20147 .is_le(),
20148 "replace_range should start before or at cursor position"
20149 );
20150 debug_assert!(
20151 insert_range
20152 .end
20153 .cmp(&cursor_position, &buffer_snapshot)
20154 .is_le(),
20155 "insert_range should end before or at cursor position"
20156 );
20157
20158 let should_replace = match intent {
20159 CompletionIntent::CompleteWithInsert => false,
20160 CompletionIntent::CompleteWithReplace => true,
20161 CompletionIntent::Complete | CompletionIntent::Compose => {
20162 let insert_mode =
20163 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20164 .completions
20165 .lsp_insert_mode;
20166 match insert_mode {
20167 LspInsertMode::Insert => false,
20168 LspInsertMode::Replace => true,
20169 LspInsertMode::ReplaceSubsequence => {
20170 let mut text_to_replace = buffer.chars_for_range(
20171 buffer.anchor_before(replace_range.start)
20172 ..buffer.anchor_after(replace_range.end),
20173 );
20174 let mut current_needle = text_to_replace.next();
20175 for haystack_ch in completion.label.text.chars() {
20176 if let Some(needle_ch) = current_needle {
20177 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20178 current_needle = text_to_replace.next();
20179 }
20180 }
20181 }
20182 current_needle.is_none()
20183 }
20184 LspInsertMode::ReplaceSuffix => {
20185 if replace_range
20186 .end
20187 .cmp(&cursor_position, &buffer_snapshot)
20188 .is_gt()
20189 {
20190 let range_after_cursor = *cursor_position..replace_range.end;
20191 let text_after_cursor = buffer
20192 .text_for_range(
20193 buffer.anchor_before(range_after_cursor.start)
20194 ..buffer.anchor_after(range_after_cursor.end),
20195 )
20196 .collect::<String>()
20197 .to_ascii_lowercase();
20198 completion
20199 .label
20200 .text
20201 .to_ascii_lowercase()
20202 .ends_with(&text_after_cursor)
20203 } else {
20204 true
20205 }
20206 }
20207 }
20208 }
20209 };
20210
20211 if should_replace {
20212 replace_range.clone()
20213 } else {
20214 insert_range.clone()
20215 }
20216 } else {
20217 replace_range.clone()
20218 }
20219 };
20220
20221 if range_to_replace
20222 .end
20223 .cmp(&cursor_position, &buffer_snapshot)
20224 .is_lt()
20225 {
20226 range_to_replace.end = *cursor_position;
20227 }
20228
20229 CompletionEdit {
20230 new_text,
20231 replace_range: range_to_replace.to_offset(&buffer),
20232 snippet,
20233 }
20234}
20235
20236struct CompletionEdit {
20237 new_text: String,
20238 replace_range: Range<usize>,
20239 snippet: Option<Snippet>,
20240}
20241
20242fn insert_extra_newline_brackets(
20243 buffer: &MultiBufferSnapshot,
20244 range: Range<usize>,
20245 language: &language::LanguageScope,
20246) -> bool {
20247 let leading_whitespace_len = buffer
20248 .reversed_chars_at(range.start)
20249 .take_while(|c| c.is_whitespace() && *c != '\n')
20250 .map(|c| c.len_utf8())
20251 .sum::<usize>();
20252 let trailing_whitespace_len = buffer
20253 .chars_at(range.end)
20254 .take_while(|c| c.is_whitespace() && *c != '\n')
20255 .map(|c| c.len_utf8())
20256 .sum::<usize>();
20257 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20258
20259 language.brackets().any(|(pair, enabled)| {
20260 let pair_start = pair.start.trim_end();
20261 let pair_end = pair.end.trim_start();
20262
20263 enabled
20264 && pair.newline
20265 && buffer.contains_str_at(range.end, pair_end)
20266 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20267 })
20268}
20269
20270fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20271 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20272 [(buffer, range, _)] => (*buffer, range.clone()),
20273 _ => return false,
20274 };
20275 let pair = {
20276 let mut result: Option<BracketMatch> = None;
20277
20278 for pair in buffer
20279 .all_bracket_ranges(range.clone())
20280 .filter(move |pair| {
20281 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20282 })
20283 {
20284 let len = pair.close_range.end - pair.open_range.start;
20285
20286 if let Some(existing) = &result {
20287 let existing_len = existing.close_range.end - existing.open_range.start;
20288 if len > existing_len {
20289 continue;
20290 }
20291 }
20292
20293 result = Some(pair);
20294 }
20295
20296 result
20297 };
20298 let Some(pair) = pair else {
20299 return false;
20300 };
20301 pair.newline_only
20302 && buffer
20303 .chars_for_range(pair.open_range.end..range.start)
20304 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20305 .all(|c| c.is_whitespace() && c != '\n')
20306}
20307
20308fn update_uncommitted_diff_for_buffer(
20309 editor: Entity<Editor>,
20310 project: &Entity<Project>,
20311 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20312 buffer: Entity<MultiBuffer>,
20313 cx: &mut App,
20314) -> Task<()> {
20315 let mut tasks = Vec::new();
20316 project.update(cx, |project, cx| {
20317 for buffer in buffers {
20318 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20319 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20320 }
20321 }
20322 });
20323 cx.spawn(async move |cx| {
20324 let diffs = future::join_all(tasks).await;
20325 if editor
20326 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20327 .unwrap_or(false)
20328 {
20329 return;
20330 }
20331
20332 buffer
20333 .update(cx, |buffer, cx| {
20334 for diff in diffs.into_iter().flatten() {
20335 buffer.add_diff(diff, cx);
20336 }
20337 })
20338 .ok();
20339 })
20340}
20341
20342fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20343 let tab_size = tab_size.get() as usize;
20344 let mut width = offset;
20345
20346 for ch in text.chars() {
20347 width += if ch == '\t' {
20348 tab_size - (width % tab_size)
20349 } else {
20350 1
20351 };
20352 }
20353
20354 width - offset
20355}
20356
20357#[cfg(test)]
20358mod tests {
20359 use super::*;
20360
20361 #[test]
20362 fn test_string_size_with_expanded_tabs() {
20363 let nz = |val| NonZeroU32::new(val).unwrap();
20364 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20365 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20366 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20367 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20368 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20369 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20370 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20371 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20372 }
20373}
20374
20375/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20376struct WordBreakingTokenizer<'a> {
20377 input: &'a str,
20378}
20379
20380impl<'a> WordBreakingTokenizer<'a> {
20381 fn new(input: &'a str) -> Self {
20382 Self { input }
20383 }
20384}
20385
20386fn is_char_ideographic(ch: char) -> bool {
20387 use unicode_script::Script::*;
20388 use unicode_script::UnicodeScript;
20389 matches!(ch.script(), Han | Tangut | Yi)
20390}
20391
20392fn is_grapheme_ideographic(text: &str) -> bool {
20393 text.chars().any(is_char_ideographic)
20394}
20395
20396fn is_grapheme_whitespace(text: &str) -> bool {
20397 text.chars().any(|x| x.is_whitespace())
20398}
20399
20400fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20401 text.chars().next().map_or(false, |ch| {
20402 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20403 })
20404}
20405
20406#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20407enum WordBreakToken<'a> {
20408 Word { token: &'a str, grapheme_len: usize },
20409 InlineWhitespace { token: &'a str, grapheme_len: usize },
20410 Newline,
20411}
20412
20413impl<'a> Iterator for WordBreakingTokenizer<'a> {
20414 /// Yields a span, the count of graphemes in the token, and whether it was
20415 /// whitespace. Note that it also breaks at word boundaries.
20416 type Item = WordBreakToken<'a>;
20417
20418 fn next(&mut self) -> Option<Self::Item> {
20419 use unicode_segmentation::UnicodeSegmentation;
20420 if self.input.is_empty() {
20421 return None;
20422 }
20423
20424 let mut iter = self.input.graphemes(true).peekable();
20425 let mut offset = 0;
20426 let mut grapheme_len = 0;
20427 if let Some(first_grapheme) = iter.next() {
20428 let is_newline = first_grapheme == "\n";
20429 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20430 offset += first_grapheme.len();
20431 grapheme_len += 1;
20432 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20433 if let Some(grapheme) = iter.peek().copied() {
20434 if should_stay_with_preceding_ideograph(grapheme) {
20435 offset += grapheme.len();
20436 grapheme_len += 1;
20437 }
20438 }
20439 } else {
20440 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20441 let mut next_word_bound = words.peek().copied();
20442 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20443 next_word_bound = words.next();
20444 }
20445 while let Some(grapheme) = iter.peek().copied() {
20446 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20447 break;
20448 };
20449 if is_grapheme_whitespace(grapheme) != is_whitespace
20450 || (grapheme == "\n") != is_newline
20451 {
20452 break;
20453 };
20454 offset += grapheme.len();
20455 grapheme_len += 1;
20456 iter.next();
20457 }
20458 }
20459 let token = &self.input[..offset];
20460 self.input = &self.input[offset..];
20461 if token == "\n" {
20462 Some(WordBreakToken::Newline)
20463 } else if is_whitespace {
20464 Some(WordBreakToken::InlineWhitespace {
20465 token,
20466 grapheme_len,
20467 })
20468 } else {
20469 Some(WordBreakToken::Word {
20470 token,
20471 grapheme_len,
20472 })
20473 }
20474 } else {
20475 None
20476 }
20477 }
20478}
20479
20480#[test]
20481fn test_word_breaking_tokenizer() {
20482 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20483 ("", &[]),
20484 (" ", &[whitespace(" ", 2)]),
20485 ("Ʒ", &[word("Ʒ", 1)]),
20486 ("Ǽ", &[word("Ǽ", 1)]),
20487 ("⋑", &[word("⋑", 1)]),
20488 ("⋑⋑", &[word("⋑⋑", 2)]),
20489 (
20490 "原理,进而",
20491 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20492 ),
20493 (
20494 "hello world",
20495 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20496 ),
20497 (
20498 "hello, world",
20499 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20500 ),
20501 (
20502 " hello world",
20503 &[
20504 whitespace(" ", 2),
20505 word("hello", 5),
20506 whitespace(" ", 1),
20507 word("world", 5),
20508 ],
20509 ),
20510 (
20511 "这是什么 \n 钢笔",
20512 &[
20513 word("这", 1),
20514 word("是", 1),
20515 word("什", 1),
20516 word("么", 1),
20517 whitespace(" ", 1),
20518 newline(),
20519 whitespace(" ", 1),
20520 word("钢", 1),
20521 word("笔", 1),
20522 ],
20523 ),
20524 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20525 ];
20526
20527 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20528 WordBreakToken::Word {
20529 token,
20530 grapheme_len,
20531 }
20532 }
20533
20534 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20535 WordBreakToken::InlineWhitespace {
20536 token,
20537 grapheme_len,
20538 }
20539 }
20540
20541 fn newline() -> WordBreakToken<'static> {
20542 WordBreakToken::Newline
20543 }
20544
20545 for (input, result) in tests {
20546 assert_eq!(
20547 WordBreakingTokenizer::new(input)
20548 .collect::<Vec<_>>()
20549 .as_slice(),
20550 *result,
20551 );
20552 }
20553}
20554
20555fn wrap_with_prefix(
20556 line_prefix: String,
20557 unwrapped_text: String,
20558 wrap_column: usize,
20559 tab_size: NonZeroU32,
20560 preserve_existing_whitespace: bool,
20561) -> String {
20562 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20563 let mut wrapped_text = String::new();
20564 let mut current_line = line_prefix.clone();
20565
20566 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20567 let mut current_line_len = line_prefix_len;
20568 let mut in_whitespace = false;
20569 for token in tokenizer {
20570 let have_preceding_whitespace = in_whitespace;
20571 match token {
20572 WordBreakToken::Word {
20573 token,
20574 grapheme_len,
20575 } => {
20576 in_whitespace = false;
20577 if current_line_len + grapheme_len > wrap_column
20578 && current_line_len != line_prefix_len
20579 {
20580 wrapped_text.push_str(current_line.trim_end());
20581 wrapped_text.push('\n');
20582 current_line.truncate(line_prefix.len());
20583 current_line_len = line_prefix_len;
20584 }
20585 current_line.push_str(token);
20586 current_line_len += grapheme_len;
20587 }
20588 WordBreakToken::InlineWhitespace {
20589 mut token,
20590 mut grapheme_len,
20591 } => {
20592 in_whitespace = true;
20593 if have_preceding_whitespace && !preserve_existing_whitespace {
20594 continue;
20595 }
20596 if !preserve_existing_whitespace {
20597 token = " ";
20598 grapheme_len = 1;
20599 }
20600 if current_line_len + grapheme_len > wrap_column {
20601 wrapped_text.push_str(current_line.trim_end());
20602 wrapped_text.push('\n');
20603 current_line.truncate(line_prefix.len());
20604 current_line_len = line_prefix_len;
20605 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20606 current_line.push_str(token);
20607 current_line_len += grapheme_len;
20608 }
20609 }
20610 WordBreakToken::Newline => {
20611 in_whitespace = true;
20612 if preserve_existing_whitespace {
20613 wrapped_text.push_str(current_line.trim_end());
20614 wrapped_text.push('\n');
20615 current_line.truncate(line_prefix.len());
20616 current_line_len = line_prefix_len;
20617 } else if have_preceding_whitespace {
20618 continue;
20619 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20620 {
20621 wrapped_text.push_str(current_line.trim_end());
20622 wrapped_text.push('\n');
20623 current_line.truncate(line_prefix.len());
20624 current_line_len = line_prefix_len;
20625 } else if current_line_len != line_prefix_len {
20626 current_line.push(' ');
20627 current_line_len += 1;
20628 }
20629 }
20630 }
20631 }
20632
20633 if !current_line.is_empty() {
20634 wrapped_text.push_str(¤t_line);
20635 }
20636 wrapped_text
20637}
20638
20639#[test]
20640fn test_wrap_with_prefix() {
20641 assert_eq!(
20642 wrap_with_prefix(
20643 "# ".to_string(),
20644 "abcdefg".to_string(),
20645 4,
20646 NonZeroU32::new(4).unwrap(),
20647 false,
20648 ),
20649 "# abcdefg"
20650 );
20651 assert_eq!(
20652 wrap_with_prefix(
20653 "".to_string(),
20654 "\thello world".to_string(),
20655 8,
20656 NonZeroU32::new(4).unwrap(),
20657 false,
20658 ),
20659 "hello\nworld"
20660 );
20661 assert_eq!(
20662 wrap_with_prefix(
20663 "// ".to_string(),
20664 "xx \nyy zz aa bb cc".to_string(),
20665 12,
20666 NonZeroU32::new(4).unwrap(),
20667 false,
20668 ),
20669 "// xx yy zz\n// aa bb cc"
20670 );
20671 assert_eq!(
20672 wrap_with_prefix(
20673 String::new(),
20674 "这是什么 \n 钢笔".to_string(),
20675 3,
20676 NonZeroU32::new(4).unwrap(),
20677 false,
20678 ),
20679 "这是什\n么 钢\n笔"
20680 );
20681}
20682
20683pub trait CollaborationHub {
20684 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20685 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20686 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20687}
20688
20689impl CollaborationHub for Entity<Project> {
20690 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20691 self.read(cx).collaborators()
20692 }
20693
20694 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20695 self.read(cx).user_store().read(cx).participant_indices()
20696 }
20697
20698 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20699 let this = self.read(cx);
20700 let user_ids = this.collaborators().values().map(|c| c.user_id);
20701 this.user_store().read(cx).participant_names(user_ids, cx)
20702 }
20703}
20704
20705pub trait SemanticsProvider {
20706 fn hover(
20707 &self,
20708 buffer: &Entity<Buffer>,
20709 position: text::Anchor,
20710 cx: &mut App,
20711 ) -> Option<Task<Vec<project::Hover>>>;
20712
20713 fn inline_values(
20714 &self,
20715 buffer_handle: Entity<Buffer>,
20716 range: Range<text::Anchor>,
20717 cx: &mut App,
20718 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20719
20720 fn inlay_hints(
20721 &self,
20722 buffer_handle: Entity<Buffer>,
20723 range: Range<text::Anchor>,
20724 cx: &mut App,
20725 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20726
20727 fn resolve_inlay_hint(
20728 &self,
20729 hint: InlayHint,
20730 buffer_handle: Entity<Buffer>,
20731 server_id: LanguageServerId,
20732 cx: &mut App,
20733 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20734
20735 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20736
20737 fn document_highlights(
20738 &self,
20739 buffer: &Entity<Buffer>,
20740 position: text::Anchor,
20741 cx: &mut App,
20742 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20743
20744 fn definitions(
20745 &self,
20746 buffer: &Entity<Buffer>,
20747 position: text::Anchor,
20748 kind: GotoDefinitionKind,
20749 cx: &mut App,
20750 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20751
20752 fn range_for_rename(
20753 &self,
20754 buffer: &Entity<Buffer>,
20755 position: text::Anchor,
20756 cx: &mut App,
20757 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20758
20759 fn perform_rename(
20760 &self,
20761 buffer: &Entity<Buffer>,
20762 position: text::Anchor,
20763 new_name: String,
20764 cx: &mut App,
20765 ) -> Option<Task<Result<ProjectTransaction>>>;
20766
20767 fn pull_diagnostics_for_buffer(
20768 &self,
20769 buffer: Entity<Buffer>,
20770 cx: &mut App,
20771 ) -> Task<anyhow::Result<()>>;
20772}
20773
20774pub trait CompletionProvider {
20775 fn completions(
20776 &self,
20777 excerpt_id: ExcerptId,
20778 buffer: &Entity<Buffer>,
20779 buffer_position: text::Anchor,
20780 trigger: CompletionContext,
20781 window: &mut Window,
20782 cx: &mut Context<Editor>,
20783 ) -> Task<Result<Vec<CompletionResponse>>>;
20784
20785 fn resolve_completions(
20786 &self,
20787 _buffer: Entity<Buffer>,
20788 _completion_indices: Vec<usize>,
20789 _completions: Rc<RefCell<Box<[Completion]>>>,
20790 _cx: &mut Context<Editor>,
20791 ) -> Task<Result<bool>> {
20792 Task::ready(Ok(false))
20793 }
20794
20795 fn apply_additional_edits_for_completion(
20796 &self,
20797 _buffer: Entity<Buffer>,
20798 _completions: Rc<RefCell<Box<[Completion]>>>,
20799 _completion_index: usize,
20800 _push_to_history: bool,
20801 _cx: &mut Context<Editor>,
20802 ) -> Task<Result<Option<language::Transaction>>> {
20803 Task::ready(Ok(None))
20804 }
20805
20806 fn is_completion_trigger(
20807 &self,
20808 buffer: &Entity<Buffer>,
20809 position: language::Anchor,
20810 text: &str,
20811 trigger_in_words: bool,
20812 menu_is_open: bool,
20813 cx: &mut Context<Editor>,
20814 ) -> bool;
20815
20816 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20817
20818 fn sort_completions(&self) -> bool {
20819 true
20820 }
20821
20822 fn filter_completions(&self) -> bool {
20823 true
20824 }
20825}
20826
20827pub trait CodeActionProvider {
20828 fn id(&self) -> Arc<str>;
20829
20830 fn code_actions(
20831 &self,
20832 buffer: &Entity<Buffer>,
20833 range: Range<text::Anchor>,
20834 window: &mut Window,
20835 cx: &mut App,
20836 ) -> Task<Result<Vec<CodeAction>>>;
20837
20838 fn apply_code_action(
20839 &self,
20840 buffer_handle: Entity<Buffer>,
20841 action: CodeAction,
20842 excerpt_id: ExcerptId,
20843 push_to_history: bool,
20844 window: &mut Window,
20845 cx: &mut App,
20846 ) -> Task<Result<ProjectTransaction>>;
20847}
20848
20849impl CodeActionProvider for Entity<Project> {
20850 fn id(&self) -> Arc<str> {
20851 "project".into()
20852 }
20853
20854 fn code_actions(
20855 &self,
20856 buffer: &Entity<Buffer>,
20857 range: Range<text::Anchor>,
20858 _window: &mut Window,
20859 cx: &mut App,
20860 ) -> Task<Result<Vec<CodeAction>>> {
20861 self.update(cx, |project, cx| {
20862 let code_lens = project.code_lens(buffer, range.clone(), cx);
20863 let code_actions = project.code_actions(buffer, range, None, cx);
20864 cx.background_spawn(async move {
20865 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20866 Ok(code_lens
20867 .context("code lens fetch")?
20868 .into_iter()
20869 .chain(code_actions.context("code action fetch")?)
20870 .collect())
20871 })
20872 })
20873 }
20874
20875 fn apply_code_action(
20876 &self,
20877 buffer_handle: Entity<Buffer>,
20878 action: CodeAction,
20879 _excerpt_id: ExcerptId,
20880 push_to_history: bool,
20881 _window: &mut Window,
20882 cx: &mut App,
20883 ) -> Task<Result<ProjectTransaction>> {
20884 self.update(cx, |project, cx| {
20885 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20886 })
20887 }
20888}
20889
20890fn snippet_completions(
20891 project: &Project,
20892 buffer: &Entity<Buffer>,
20893 buffer_position: text::Anchor,
20894 cx: &mut App,
20895) -> Task<Result<CompletionResponse>> {
20896 let languages = buffer.read(cx).languages_at(buffer_position);
20897 let snippet_store = project.snippets().read(cx);
20898
20899 let scopes: Vec<_> = languages
20900 .iter()
20901 .filter_map(|language| {
20902 let language_name = language.lsp_id();
20903 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20904
20905 if snippets.is_empty() {
20906 None
20907 } else {
20908 Some((language.default_scope(), snippets))
20909 }
20910 })
20911 .collect();
20912
20913 if scopes.is_empty() {
20914 return Task::ready(Ok(CompletionResponse {
20915 completions: vec![],
20916 is_incomplete: false,
20917 }));
20918 }
20919
20920 let snapshot = buffer.read(cx).text_snapshot();
20921 let chars: String = snapshot
20922 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20923 .collect();
20924 let executor = cx.background_executor().clone();
20925
20926 cx.background_spawn(async move {
20927 let mut is_incomplete = false;
20928 let mut completions: Vec<Completion> = Vec::new();
20929 for (scope, snippets) in scopes.into_iter() {
20930 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20931 let mut last_word = chars
20932 .chars()
20933 .take_while(|c| classifier.is_word(*c))
20934 .collect::<String>();
20935 last_word = last_word.chars().rev().collect();
20936
20937 if last_word.is_empty() {
20938 return Ok(CompletionResponse {
20939 completions: vec![],
20940 is_incomplete: true,
20941 });
20942 }
20943
20944 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20945 let to_lsp = |point: &text::Anchor| {
20946 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20947 point_to_lsp(end)
20948 };
20949 let lsp_end = to_lsp(&buffer_position);
20950
20951 let candidates = snippets
20952 .iter()
20953 .enumerate()
20954 .flat_map(|(ix, snippet)| {
20955 snippet
20956 .prefix
20957 .iter()
20958 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20959 })
20960 .collect::<Vec<StringMatchCandidate>>();
20961
20962 const MAX_RESULTS: usize = 100;
20963 let mut matches = fuzzy::match_strings(
20964 &candidates,
20965 &last_word,
20966 last_word.chars().any(|c| c.is_uppercase()),
20967 MAX_RESULTS,
20968 &Default::default(),
20969 executor.clone(),
20970 )
20971 .await;
20972
20973 if matches.len() >= MAX_RESULTS {
20974 is_incomplete = true;
20975 }
20976
20977 // Remove all candidates where the query's start does not match the start of any word in the candidate
20978 if let Some(query_start) = last_word.chars().next() {
20979 matches.retain(|string_match| {
20980 split_words(&string_match.string).any(|word| {
20981 // Check that the first codepoint of the word as lowercase matches the first
20982 // codepoint of the query as lowercase
20983 word.chars()
20984 .flat_map(|codepoint| codepoint.to_lowercase())
20985 .zip(query_start.to_lowercase())
20986 .all(|(word_cp, query_cp)| word_cp == query_cp)
20987 })
20988 });
20989 }
20990
20991 let matched_strings = matches
20992 .into_iter()
20993 .map(|m| m.string)
20994 .collect::<HashSet<_>>();
20995
20996 completions.extend(snippets.iter().filter_map(|snippet| {
20997 let matching_prefix = snippet
20998 .prefix
20999 .iter()
21000 .find(|prefix| matched_strings.contains(*prefix))?;
21001 let start = as_offset - last_word.len();
21002 let start = snapshot.anchor_before(start);
21003 let range = start..buffer_position;
21004 let lsp_start = to_lsp(&start);
21005 let lsp_range = lsp::Range {
21006 start: lsp_start,
21007 end: lsp_end,
21008 };
21009 Some(Completion {
21010 replace_range: range,
21011 new_text: snippet.body.clone(),
21012 source: CompletionSource::Lsp {
21013 insert_range: None,
21014 server_id: LanguageServerId(usize::MAX),
21015 resolved: true,
21016 lsp_completion: Box::new(lsp::CompletionItem {
21017 label: snippet.prefix.first().unwrap().clone(),
21018 kind: Some(CompletionItemKind::SNIPPET),
21019 label_details: snippet.description.as_ref().map(|description| {
21020 lsp::CompletionItemLabelDetails {
21021 detail: Some(description.clone()),
21022 description: None,
21023 }
21024 }),
21025 insert_text_format: Some(InsertTextFormat::SNIPPET),
21026 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21027 lsp::InsertReplaceEdit {
21028 new_text: snippet.body.clone(),
21029 insert: lsp_range,
21030 replace: lsp_range,
21031 },
21032 )),
21033 filter_text: Some(snippet.body.clone()),
21034 sort_text: Some(char::MAX.to_string()),
21035 ..lsp::CompletionItem::default()
21036 }),
21037 lsp_defaults: None,
21038 },
21039 label: CodeLabel {
21040 text: matching_prefix.clone(),
21041 runs: Vec::new(),
21042 filter_range: 0..matching_prefix.len(),
21043 },
21044 icon_path: None,
21045 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21046 single_line: snippet.name.clone().into(),
21047 plain_text: snippet
21048 .description
21049 .clone()
21050 .map(|description| description.into()),
21051 }),
21052 insert_text_mode: None,
21053 confirm: None,
21054 })
21055 }))
21056 }
21057
21058 Ok(CompletionResponse {
21059 completions,
21060 is_incomplete,
21061 })
21062 })
21063}
21064
21065impl CompletionProvider for Entity<Project> {
21066 fn completions(
21067 &self,
21068 _excerpt_id: ExcerptId,
21069 buffer: &Entity<Buffer>,
21070 buffer_position: text::Anchor,
21071 options: CompletionContext,
21072 _window: &mut Window,
21073 cx: &mut Context<Editor>,
21074 ) -> Task<Result<Vec<CompletionResponse>>> {
21075 self.update(cx, |project, cx| {
21076 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21077 let project_completions = project.completions(buffer, buffer_position, options, cx);
21078 cx.background_spawn(async move {
21079 let mut responses = project_completions.await?;
21080 let snippets = snippets.await?;
21081 if !snippets.completions.is_empty() {
21082 responses.push(snippets);
21083 }
21084 Ok(responses)
21085 })
21086 })
21087 }
21088
21089 fn resolve_completions(
21090 &self,
21091 buffer: Entity<Buffer>,
21092 completion_indices: Vec<usize>,
21093 completions: Rc<RefCell<Box<[Completion]>>>,
21094 cx: &mut Context<Editor>,
21095 ) -> Task<Result<bool>> {
21096 self.update(cx, |project, cx| {
21097 project.lsp_store().update(cx, |lsp_store, cx| {
21098 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21099 })
21100 })
21101 }
21102
21103 fn apply_additional_edits_for_completion(
21104 &self,
21105 buffer: Entity<Buffer>,
21106 completions: Rc<RefCell<Box<[Completion]>>>,
21107 completion_index: usize,
21108 push_to_history: bool,
21109 cx: &mut Context<Editor>,
21110 ) -> Task<Result<Option<language::Transaction>>> {
21111 self.update(cx, |project, cx| {
21112 project.lsp_store().update(cx, |lsp_store, cx| {
21113 lsp_store.apply_additional_edits_for_completion(
21114 buffer,
21115 completions,
21116 completion_index,
21117 push_to_history,
21118 cx,
21119 )
21120 })
21121 })
21122 }
21123
21124 fn is_completion_trigger(
21125 &self,
21126 buffer: &Entity<Buffer>,
21127 position: language::Anchor,
21128 text: &str,
21129 trigger_in_words: bool,
21130 menu_is_open: bool,
21131 cx: &mut Context<Editor>,
21132 ) -> bool {
21133 let mut chars = text.chars();
21134 let char = if let Some(char) = chars.next() {
21135 char
21136 } else {
21137 return false;
21138 };
21139 if chars.next().is_some() {
21140 return false;
21141 }
21142
21143 let buffer = buffer.read(cx);
21144 let snapshot = buffer.snapshot();
21145 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21146 return false;
21147 }
21148 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21149 if trigger_in_words && classifier.is_word(char) {
21150 return true;
21151 }
21152
21153 buffer.completion_triggers().contains(text)
21154 }
21155}
21156
21157impl SemanticsProvider for Entity<Project> {
21158 fn hover(
21159 &self,
21160 buffer: &Entity<Buffer>,
21161 position: text::Anchor,
21162 cx: &mut App,
21163 ) -> Option<Task<Vec<project::Hover>>> {
21164 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21165 }
21166
21167 fn document_highlights(
21168 &self,
21169 buffer: &Entity<Buffer>,
21170 position: text::Anchor,
21171 cx: &mut App,
21172 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21173 Some(self.update(cx, |project, cx| {
21174 project.document_highlights(buffer, position, cx)
21175 }))
21176 }
21177
21178 fn definitions(
21179 &self,
21180 buffer: &Entity<Buffer>,
21181 position: text::Anchor,
21182 kind: GotoDefinitionKind,
21183 cx: &mut App,
21184 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21185 Some(self.update(cx, |project, cx| match kind {
21186 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21187 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21188 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21189 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21190 }))
21191 }
21192
21193 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21194 // TODO: make this work for remote projects
21195 self.update(cx, |project, cx| {
21196 if project
21197 .active_debug_session(cx)
21198 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21199 {
21200 return true;
21201 }
21202
21203 buffer.update(cx, |buffer, cx| {
21204 project.any_language_server_supports_inlay_hints(buffer, cx)
21205 })
21206 })
21207 }
21208
21209 fn inline_values(
21210 &self,
21211 buffer_handle: Entity<Buffer>,
21212
21213 range: Range<text::Anchor>,
21214 cx: &mut App,
21215 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21216 self.update(cx, |project, cx| {
21217 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21218
21219 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21220 })
21221 }
21222
21223 fn inlay_hints(
21224 &self,
21225 buffer_handle: Entity<Buffer>,
21226 range: Range<text::Anchor>,
21227 cx: &mut App,
21228 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21229 Some(self.update(cx, |project, cx| {
21230 project.inlay_hints(buffer_handle, range, cx)
21231 }))
21232 }
21233
21234 fn resolve_inlay_hint(
21235 &self,
21236 hint: InlayHint,
21237 buffer_handle: Entity<Buffer>,
21238 server_id: LanguageServerId,
21239 cx: &mut App,
21240 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21241 Some(self.update(cx, |project, cx| {
21242 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21243 }))
21244 }
21245
21246 fn range_for_rename(
21247 &self,
21248 buffer: &Entity<Buffer>,
21249 position: text::Anchor,
21250 cx: &mut App,
21251 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21252 Some(self.update(cx, |project, cx| {
21253 let buffer = buffer.clone();
21254 let task = project.prepare_rename(buffer.clone(), position, cx);
21255 cx.spawn(async move |_, cx| {
21256 Ok(match task.await? {
21257 PrepareRenameResponse::Success(range) => Some(range),
21258 PrepareRenameResponse::InvalidPosition => None,
21259 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21260 // Fallback on using TreeSitter info to determine identifier range
21261 buffer.read_with(cx, |buffer, _| {
21262 let snapshot = buffer.snapshot();
21263 let (range, kind) = snapshot.surrounding_word(position);
21264 if kind != Some(CharKind::Word) {
21265 return None;
21266 }
21267 Some(
21268 snapshot.anchor_before(range.start)
21269 ..snapshot.anchor_after(range.end),
21270 )
21271 })?
21272 }
21273 })
21274 })
21275 }))
21276 }
21277
21278 fn perform_rename(
21279 &self,
21280 buffer: &Entity<Buffer>,
21281 position: text::Anchor,
21282 new_name: String,
21283 cx: &mut App,
21284 ) -> Option<Task<Result<ProjectTransaction>>> {
21285 Some(self.update(cx, |project, cx| {
21286 project.perform_rename(buffer.clone(), position, new_name, cx)
21287 }))
21288 }
21289
21290 fn pull_diagnostics_for_buffer(
21291 &self,
21292 buffer: Entity<Buffer>,
21293 cx: &mut App,
21294 ) -> Task<anyhow::Result<()>> {
21295 let diagnostics = self.update(cx, |project, cx| {
21296 project
21297 .lsp_store()
21298 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21299 });
21300 let project = self.clone();
21301 cx.spawn(async move |cx| {
21302 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21303 project.update(cx, |project, cx| {
21304 project.lsp_store().update(cx, |lsp_store, cx| {
21305 for diagnostics_set in diagnostics {
21306 let LspPullDiagnostics::Response {
21307 server_id,
21308 uri,
21309 diagnostics,
21310 } = diagnostics_set
21311 else {
21312 continue;
21313 };
21314
21315 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21316 let disk_based_sources = adapter
21317 .as_ref()
21318 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21319 .unwrap_or(&[]);
21320 match diagnostics {
21321 PulledDiagnostics::Unchanged { result_id } => {
21322 lsp_store
21323 .merge_diagnostics(
21324 server_id,
21325 lsp::PublishDiagnosticsParams {
21326 uri: uri.clone(),
21327 diagnostics: Vec::new(),
21328 version: None,
21329 },
21330 Some(result_id),
21331 DiagnosticSourceKind::Pulled,
21332 disk_based_sources,
21333 |_, _| true,
21334 cx,
21335 )
21336 .log_err();
21337 }
21338 PulledDiagnostics::Changed {
21339 diagnostics,
21340 result_id,
21341 } => {
21342 lsp_store
21343 .merge_diagnostics(
21344 server_id,
21345 lsp::PublishDiagnosticsParams {
21346 uri: uri.clone(),
21347 diagnostics,
21348 version: None,
21349 },
21350 result_id,
21351 DiagnosticSourceKind::Pulled,
21352 disk_based_sources,
21353 |old_diagnostic, _| match old_diagnostic.source_kind {
21354 DiagnosticSourceKind::Pulled => false,
21355 DiagnosticSourceKind::Other
21356 | DiagnosticSourceKind::Pushed => true,
21357 },
21358 cx,
21359 )
21360 .log_err();
21361 }
21362 }
21363 }
21364 })
21365 })
21366 })
21367 }
21368}
21369
21370fn inlay_hint_settings(
21371 location: Anchor,
21372 snapshot: &MultiBufferSnapshot,
21373 cx: &mut Context<Editor>,
21374) -> InlayHintSettings {
21375 let file = snapshot.file_at(location);
21376 let language = snapshot.language_at(location).map(|l| l.name());
21377 language_settings(language, file, cx).inlay_hints
21378}
21379
21380fn consume_contiguous_rows(
21381 contiguous_row_selections: &mut Vec<Selection<Point>>,
21382 selection: &Selection<Point>,
21383 display_map: &DisplaySnapshot,
21384 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21385) -> (MultiBufferRow, MultiBufferRow) {
21386 contiguous_row_selections.push(selection.clone());
21387 let start_row = MultiBufferRow(selection.start.row);
21388 let mut end_row = ending_row(selection, display_map);
21389
21390 while let Some(next_selection) = selections.peek() {
21391 if next_selection.start.row <= end_row.0 {
21392 end_row = ending_row(next_selection, display_map);
21393 contiguous_row_selections.push(selections.next().unwrap().clone());
21394 } else {
21395 break;
21396 }
21397 }
21398 (start_row, end_row)
21399}
21400
21401fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21402 if next_selection.end.column > 0 || next_selection.is_empty() {
21403 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21404 } else {
21405 MultiBufferRow(next_selection.end.row)
21406 }
21407}
21408
21409impl EditorSnapshot {
21410 pub fn remote_selections_in_range<'a>(
21411 &'a self,
21412 range: &'a Range<Anchor>,
21413 collaboration_hub: &dyn CollaborationHub,
21414 cx: &'a App,
21415 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21416 let participant_names = collaboration_hub.user_names(cx);
21417 let participant_indices = collaboration_hub.user_participant_indices(cx);
21418 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21419 let collaborators_by_replica_id = collaborators_by_peer_id
21420 .values()
21421 .map(|collaborator| (collaborator.replica_id, collaborator))
21422 .collect::<HashMap<_, _>>();
21423 self.buffer_snapshot
21424 .selections_in_range(range, false)
21425 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21426 if replica_id == AGENT_REPLICA_ID {
21427 Some(RemoteSelection {
21428 replica_id,
21429 selection,
21430 cursor_shape,
21431 line_mode,
21432 collaborator_id: CollaboratorId::Agent,
21433 user_name: Some("Agent".into()),
21434 color: cx.theme().players().agent(),
21435 })
21436 } else {
21437 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21438 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21439 let user_name = participant_names.get(&collaborator.user_id).cloned();
21440 Some(RemoteSelection {
21441 replica_id,
21442 selection,
21443 cursor_shape,
21444 line_mode,
21445 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21446 user_name,
21447 color: if let Some(index) = participant_index {
21448 cx.theme().players().color_for_participant(index.0)
21449 } else {
21450 cx.theme().players().absent()
21451 },
21452 })
21453 }
21454 })
21455 }
21456
21457 pub fn hunks_for_ranges(
21458 &self,
21459 ranges: impl IntoIterator<Item = Range<Point>>,
21460 ) -> Vec<MultiBufferDiffHunk> {
21461 let mut hunks = Vec::new();
21462 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21463 HashMap::default();
21464 for query_range in ranges {
21465 let query_rows =
21466 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21467 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21468 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21469 ) {
21470 // Include deleted hunks that are adjacent to the query range, because
21471 // otherwise they would be missed.
21472 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21473 if hunk.status().is_deleted() {
21474 intersects_range |= hunk.row_range.start == query_rows.end;
21475 intersects_range |= hunk.row_range.end == query_rows.start;
21476 }
21477 if intersects_range {
21478 if !processed_buffer_rows
21479 .entry(hunk.buffer_id)
21480 .or_default()
21481 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21482 {
21483 continue;
21484 }
21485 hunks.push(hunk);
21486 }
21487 }
21488 }
21489
21490 hunks
21491 }
21492
21493 fn display_diff_hunks_for_rows<'a>(
21494 &'a self,
21495 display_rows: Range<DisplayRow>,
21496 folded_buffers: &'a HashSet<BufferId>,
21497 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21498 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21499 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21500
21501 self.buffer_snapshot
21502 .diff_hunks_in_range(buffer_start..buffer_end)
21503 .filter_map(|hunk| {
21504 if folded_buffers.contains(&hunk.buffer_id) {
21505 return None;
21506 }
21507
21508 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21509 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21510
21511 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21512 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21513
21514 let display_hunk = if hunk_display_start.column() != 0 {
21515 DisplayDiffHunk::Folded {
21516 display_row: hunk_display_start.row(),
21517 }
21518 } else {
21519 let mut end_row = hunk_display_end.row();
21520 if hunk_display_end.column() > 0 {
21521 end_row.0 += 1;
21522 }
21523 let is_created_file = hunk.is_created_file();
21524 DisplayDiffHunk::Unfolded {
21525 status: hunk.status(),
21526 diff_base_byte_range: hunk.diff_base_byte_range,
21527 display_row_range: hunk_display_start.row()..end_row,
21528 multi_buffer_range: Anchor::range_in_buffer(
21529 hunk.excerpt_id,
21530 hunk.buffer_id,
21531 hunk.buffer_range,
21532 ),
21533 is_created_file,
21534 }
21535 };
21536
21537 Some(display_hunk)
21538 })
21539 }
21540
21541 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21542 self.display_snapshot.buffer_snapshot.language_at(position)
21543 }
21544
21545 pub fn is_focused(&self) -> bool {
21546 self.is_focused
21547 }
21548
21549 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21550 self.placeholder_text.as_ref()
21551 }
21552
21553 pub fn scroll_position(&self) -> gpui::Point<f32> {
21554 self.scroll_anchor.scroll_position(&self.display_snapshot)
21555 }
21556
21557 fn gutter_dimensions(
21558 &self,
21559 font_id: FontId,
21560 font_size: Pixels,
21561 max_line_number_width: Pixels,
21562 cx: &App,
21563 ) -> Option<GutterDimensions> {
21564 if !self.show_gutter {
21565 return None;
21566 }
21567
21568 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21569 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21570
21571 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21572 matches!(
21573 ProjectSettings::get_global(cx).git.git_gutter,
21574 Some(GitGutterSetting::TrackedFiles)
21575 )
21576 });
21577 let gutter_settings = EditorSettings::get_global(cx).gutter;
21578 let show_line_numbers = self
21579 .show_line_numbers
21580 .unwrap_or(gutter_settings.line_numbers);
21581 let line_gutter_width = if show_line_numbers {
21582 // Avoid flicker-like gutter resizes when the line number gains another digit by
21583 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21584 let min_width_for_number_on_gutter =
21585 ch_advance * gutter_settings.min_line_number_digits as f32;
21586 max_line_number_width.max(min_width_for_number_on_gutter)
21587 } else {
21588 0.0.into()
21589 };
21590
21591 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21592 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21593
21594 let git_blame_entries_width =
21595 self.git_blame_gutter_max_author_length
21596 .map(|max_author_length| {
21597 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21598 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21599
21600 /// The number of characters to dedicate to gaps and margins.
21601 const SPACING_WIDTH: usize = 4;
21602
21603 let max_char_count = max_author_length.min(renderer.max_author_length())
21604 + ::git::SHORT_SHA_LENGTH
21605 + MAX_RELATIVE_TIMESTAMP.len()
21606 + SPACING_WIDTH;
21607
21608 ch_advance * max_char_count
21609 });
21610
21611 let is_singleton = self.buffer_snapshot.is_singleton();
21612
21613 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21614 left_padding += if !is_singleton {
21615 ch_width * 4.0
21616 } else if show_runnables || show_breakpoints {
21617 ch_width * 3.0
21618 } else if show_git_gutter && show_line_numbers {
21619 ch_width * 2.0
21620 } else if show_git_gutter || show_line_numbers {
21621 ch_width
21622 } else {
21623 px(0.)
21624 };
21625
21626 let shows_folds = is_singleton && gutter_settings.folds;
21627
21628 let right_padding = if shows_folds && show_line_numbers {
21629 ch_width * 4.0
21630 } else if shows_folds || (!is_singleton && show_line_numbers) {
21631 ch_width * 3.0
21632 } else if show_line_numbers {
21633 ch_width
21634 } else {
21635 px(0.)
21636 };
21637
21638 Some(GutterDimensions {
21639 left_padding,
21640 right_padding,
21641 width: line_gutter_width + left_padding + right_padding,
21642 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21643 git_blame_entries_width,
21644 })
21645 }
21646
21647 pub fn render_crease_toggle(
21648 &self,
21649 buffer_row: MultiBufferRow,
21650 row_contains_cursor: bool,
21651 editor: Entity<Editor>,
21652 window: &mut Window,
21653 cx: &mut App,
21654 ) -> Option<AnyElement> {
21655 let folded = self.is_line_folded(buffer_row);
21656 let mut is_foldable = false;
21657
21658 if let Some(crease) = self
21659 .crease_snapshot
21660 .query_row(buffer_row, &self.buffer_snapshot)
21661 {
21662 is_foldable = true;
21663 match crease {
21664 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21665 if let Some(render_toggle) = render_toggle {
21666 let toggle_callback =
21667 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21668 if folded {
21669 editor.update(cx, |editor, cx| {
21670 editor.fold_at(buffer_row, window, cx)
21671 });
21672 } else {
21673 editor.update(cx, |editor, cx| {
21674 editor.unfold_at(buffer_row, window, cx)
21675 });
21676 }
21677 });
21678 return Some((render_toggle)(
21679 buffer_row,
21680 folded,
21681 toggle_callback,
21682 window,
21683 cx,
21684 ));
21685 }
21686 }
21687 }
21688 }
21689
21690 is_foldable |= self.starts_indent(buffer_row);
21691
21692 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21693 Some(
21694 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21695 .toggle_state(folded)
21696 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21697 if folded {
21698 this.unfold_at(buffer_row, window, cx);
21699 } else {
21700 this.fold_at(buffer_row, window, cx);
21701 }
21702 }))
21703 .into_any_element(),
21704 )
21705 } else {
21706 None
21707 }
21708 }
21709
21710 pub fn render_crease_trailer(
21711 &self,
21712 buffer_row: MultiBufferRow,
21713 window: &mut Window,
21714 cx: &mut App,
21715 ) -> Option<AnyElement> {
21716 let folded = self.is_line_folded(buffer_row);
21717 if let Crease::Inline { render_trailer, .. } = self
21718 .crease_snapshot
21719 .query_row(buffer_row, &self.buffer_snapshot)?
21720 {
21721 let render_trailer = render_trailer.as_ref()?;
21722 Some(render_trailer(buffer_row, folded, window, cx))
21723 } else {
21724 None
21725 }
21726 }
21727}
21728
21729impl Deref for EditorSnapshot {
21730 type Target = DisplaySnapshot;
21731
21732 fn deref(&self) -> &Self::Target {
21733 &self.display_snapshot
21734 }
21735}
21736
21737#[derive(Clone, Debug, PartialEq, Eq)]
21738pub enum EditorEvent {
21739 InputIgnored {
21740 text: Arc<str>,
21741 },
21742 InputHandled {
21743 utf16_range_to_replace: Option<Range<isize>>,
21744 text: Arc<str>,
21745 },
21746 ExcerptsAdded {
21747 buffer: Entity<Buffer>,
21748 predecessor: ExcerptId,
21749 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21750 },
21751 ExcerptsRemoved {
21752 ids: Vec<ExcerptId>,
21753 removed_buffer_ids: Vec<BufferId>,
21754 },
21755 BufferFoldToggled {
21756 ids: Vec<ExcerptId>,
21757 folded: bool,
21758 },
21759 ExcerptsEdited {
21760 ids: Vec<ExcerptId>,
21761 },
21762 ExcerptsExpanded {
21763 ids: Vec<ExcerptId>,
21764 },
21765 BufferEdited,
21766 Edited {
21767 transaction_id: clock::Lamport,
21768 },
21769 Reparsed(BufferId),
21770 Focused,
21771 FocusedIn,
21772 Blurred,
21773 DirtyChanged,
21774 Saved,
21775 TitleChanged,
21776 DiffBaseChanged,
21777 SelectionsChanged {
21778 local: bool,
21779 },
21780 ScrollPositionChanged {
21781 local: bool,
21782 autoscroll: bool,
21783 },
21784 Closed,
21785 TransactionUndone {
21786 transaction_id: clock::Lamport,
21787 },
21788 TransactionBegun {
21789 transaction_id: clock::Lamport,
21790 },
21791 Reloaded,
21792 CursorShapeChanged,
21793 PushedToNavHistory {
21794 anchor: Anchor,
21795 is_deactivate: bool,
21796 },
21797}
21798
21799impl EventEmitter<EditorEvent> for Editor {}
21800
21801impl Focusable for Editor {
21802 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21803 self.focus_handle.clone()
21804 }
21805}
21806
21807impl Render for Editor {
21808 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21809 let settings = ThemeSettings::get_global(cx);
21810
21811 let mut text_style = match self.mode {
21812 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21813 color: cx.theme().colors().editor_foreground,
21814 font_family: settings.ui_font.family.clone(),
21815 font_features: settings.ui_font.features.clone(),
21816 font_fallbacks: settings.ui_font.fallbacks.clone(),
21817 font_size: rems(0.875).into(),
21818 font_weight: settings.ui_font.weight,
21819 line_height: relative(settings.buffer_line_height.value()),
21820 ..Default::default()
21821 },
21822 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21823 color: cx.theme().colors().editor_foreground,
21824 font_family: settings.buffer_font.family.clone(),
21825 font_features: settings.buffer_font.features.clone(),
21826 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21827 font_size: settings.buffer_font_size(cx).into(),
21828 font_weight: settings.buffer_font.weight,
21829 line_height: relative(settings.buffer_line_height.value()),
21830 ..Default::default()
21831 },
21832 };
21833 if let Some(text_style_refinement) = &self.text_style_refinement {
21834 text_style.refine(text_style_refinement)
21835 }
21836
21837 let background = match self.mode {
21838 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21839 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21840 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21841 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21842 };
21843
21844 EditorElement::new(
21845 &cx.entity(),
21846 EditorStyle {
21847 background,
21848 local_player: cx.theme().players().local(),
21849 text: text_style,
21850 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21851 syntax: cx.theme().syntax().clone(),
21852 status: cx.theme().status().clone(),
21853 inlay_hints_style: make_inlay_hints_style(cx),
21854 inline_completion_styles: make_suggestion_styles(cx),
21855 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21856 show_underlines: !self.mode.is_minimap(),
21857 },
21858 )
21859 }
21860}
21861
21862impl EntityInputHandler for Editor {
21863 fn text_for_range(
21864 &mut self,
21865 range_utf16: Range<usize>,
21866 adjusted_range: &mut Option<Range<usize>>,
21867 _: &mut Window,
21868 cx: &mut Context<Self>,
21869 ) -> Option<String> {
21870 let snapshot = self.buffer.read(cx).read(cx);
21871 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21872 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21873 if (start.0..end.0) != range_utf16 {
21874 adjusted_range.replace(start.0..end.0);
21875 }
21876 Some(snapshot.text_for_range(start..end).collect())
21877 }
21878
21879 fn selected_text_range(
21880 &mut self,
21881 ignore_disabled_input: bool,
21882 _: &mut Window,
21883 cx: &mut Context<Self>,
21884 ) -> Option<UTF16Selection> {
21885 // Prevent the IME menu from appearing when holding down an alphabetic key
21886 // while input is disabled.
21887 if !ignore_disabled_input && !self.input_enabled {
21888 return None;
21889 }
21890
21891 let selection = self.selections.newest::<OffsetUtf16>(cx);
21892 let range = selection.range();
21893
21894 Some(UTF16Selection {
21895 range: range.start.0..range.end.0,
21896 reversed: selection.reversed,
21897 })
21898 }
21899
21900 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21901 let snapshot = self.buffer.read(cx).read(cx);
21902 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21903 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21904 }
21905
21906 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21907 self.clear_highlights::<InputComposition>(cx);
21908 self.ime_transaction.take();
21909 }
21910
21911 fn replace_text_in_range(
21912 &mut self,
21913 range_utf16: Option<Range<usize>>,
21914 text: &str,
21915 window: &mut Window,
21916 cx: &mut Context<Self>,
21917 ) {
21918 if !self.input_enabled {
21919 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21920 return;
21921 }
21922
21923 self.transact(window, cx, |this, window, cx| {
21924 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21925 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21926 Some(this.selection_replacement_ranges(range_utf16, cx))
21927 } else {
21928 this.marked_text_ranges(cx)
21929 };
21930
21931 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21932 let newest_selection_id = this.selections.newest_anchor().id;
21933 this.selections
21934 .all::<OffsetUtf16>(cx)
21935 .iter()
21936 .zip(ranges_to_replace.iter())
21937 .find_map(|(selection, range)| {
21938 if selection.id == newest_selection_id {
21939 Some(
21940 (range.start.0 as isize - selection.head().0 as isize)
21941 ..(range.end.0 as isize - selection.head().0 as isize),
21942 )
21943 } else {
21944 None
21945 }
21946 })
21947 });
21948
21949 cx.emit(EditorEvent::InputHandled {
21950 utf16_range_to_replace: range_to_replace,
21951 text: text.into(),
21952 });
21953
21954 if let Some(new_selected_ranges) = new_selected_ranges {
21955 this.change_selections(None, window, cx, |selections| {
21956 selections.select_ranges(new_selected_ranges)
21957 });
21958 this.backspace(&Default::default(), window, cx);
21959 }
21960
21961 this.handle_input(text, window, cx);
21962 });
21963
21964 if let Some(transaction) = self.ime_transaction {
21965 self.buffer.update(cx, |buffer, cx| {
21966 buffer.group_until_transaction(transaction, cx);
21967 });
21968 }
21969
21970 self.unmark_text(window, cx);
21971 }
21972
21973 fn replace_and_mark_text_in_range(
21974 &mut self,
21975 range_utf16: Option<Range<usize>>,
21976 text: &str,
21977 new_selected_range_utf16: Option<Range<usize>>,
21978 window: &mut Window,
21979 cx: &mut Context<Self>,
21980 ) {
21981 if !self.input_enabled {
21982 return;
21983 }
21984
21985 let transaction = self.transact(window, cx, |this, window, cx| {
21986 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21987 let snapshot = this.buffer.read(cx).read(cx);
21988 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21989 for marked_range in &mut marked_ranges {
21990 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21991 marked_range.start.0 += relative_range_utf16.start;
21992 marked_range.start =
21993 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21994 marked_range.end =
21995 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21996 }
21997 }
21998 Some(marked_ranges)
21999 } else if let Some(range_utf16) = range_utf16 {
22000 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22001 Some(this.selection_replacement_ranges(range_utf16, cx))
22002 } else {
22003 None
22004 };
22005
22006 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22007 let newest_selection_id = this.selections.newest_anchor().id;
22008 this.selections
22009 .all::<OffsetUtf16>(cx)
22010 .iter()
22011 .zip(ranges_to_replace.iter())
22012 .find_map(|(selection, range)| {
22013 if selection.id == newest_selection_id {
22014 Some(
22015 (range.start.0 as isize - selection.head().0 as isize)
22016 ..(range.end.0 as isize - selection.head().0 as isize),
22017 )
22018 } else {
22019 None
22020 }
22021 })
22022 });
22023
22024 cx.emit(EditorEvent::InputHandled {
22025 utf16_range_to_replace: range_to_replace,
22026 text: text.into(),
22027 });
22028
22029 if let Some(ranges) = ranges_to_replace {
22030 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22031 }
22032
22033 let marked_ranges = {
22034 let snapshot = this.buffer.read(cx).read(cx);
22035 this.selections
22036 .disjoint_anchors()
22037 .iter()
22038 .map(|selection| {
22039 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22040 })
22041 .collect::<Vec<_>>()
22042 };
22043
22044 if text.is_empty() {
22045 this.unmark_text(window, cx);
22046 } else {
22047 this.highlight_text::<InputComposition>(
22048 marked_ranges.clone(),
22049 HighlightStyle {
22050 underline: Some(UnderlineStyle {
22051 thickness: px(1.),
22052 color: None,
22053 wavy: false,
22054 }),
22055 ..Default::default()
22056 },
22057 cx,
22058 );
22059 }
22060
22061 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22062 let use_autoclose = this.use_autoclose;
22063 let use_auto_surround = this.use_auto_surround;
22064 this.set_use_autoclose(false);
22065 this.set_use_auto_surround(false);
22066 this.handle_input(text, window, cx);
22067 this.set_use_autoclose(use_autoclose);
22068 this.set_use_auto_surround(use_auto_surround);
22069
22070 if let Some(new_selected_range) = new_selected_range_utf16 {
22071 let snapshot = this.buffer.read(cx).read(cx);
22072 let new_selected_ranges = marked_ranges
22073 .into_iter()
22074 .map(|marked_range| {
22075 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22076 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22077 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22078 snapshot.clip_offset_utf16(new_start, Bias::Left)
22079 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22080 })
22081 .collect::<Vec<_>>();
22082
22083 drop(snapshot);
22084 this.change_selections(None, window, cx, |selections| {
22085 selections.select_ranges(new_selected_ranges)
22086 });
22087 }
22088 });
22089
22090 self.ime_transaction = self.ime_transaction.or(transaction);
22091 if let Some(transaction) = self.ime_transaction {
22092 self.buffer.update(cx, |buffer, cx| {
22093 buffer.group_until_transaction(transaction, cx);
22094 });
22095 }
22096
22097 if self.text_highlights::<InputComposition>(cx).is_none() {
22098 self.ime_transaction.take();
22099 }
22100 }
22101
22102 fn bounds_for_range(
22103 &mut self,
22104 range_utf16: Range<usize>,
22105 element_bounds: gpui::Bounds<Pixels>,
22106 window: &mut Window,
22107 cx: &mut Context<Self>,
22108 ) -> Option<gpui::Bounds<Pixels>> {
22109 let text_layout_details = self.text_layout_details(window);
22110 let gpui::Size {
22111 width: em_width,
22112 height: line_height,
22113 } = self.character_size(window);
22114
22115 let snapshot = self.snapshot(window, cx);
22116 let scroll_position = snapshot.scroll_position();
22117 let scroll_left = scroll_position.x * em_width;
22118
22119 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22120 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22121 + self.gutter_dimensions.width
22122 + self.gutter_dimensions.margin;
22123 let y = line_height * (start.row().as_f32() - scroll_position.y);
22124
22125 Some(Bounds {
22126 origin: element_bounds.origin + point(x, y),
22127 size: size(em_width, line_height),
22128 })
22129 }
22130
22131 fn character_index_for_point(
22132 &mut self,
22133 point: gpui::Point<Pixels>,
22134 _window: &mut Window,
22135 _cx: &mut Context<Self>,
22136 ) -> Option<usize> {
22137 let position_map = self.last_position_map.as_ref()?;
22138 if !position_map.text_hitbox.contains(&point) {
22139 return None;
22140 }
22141 let display_point = position_map.point_for_position(point).previous_valid;
22142 let anchor = position_map
22143 .snapshot
22144 .display_point_to_anchor(display_point, Bias::Left);
22145 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22146 Some(utf16_offset.0)
22147 }
22148}
22149
22150trait SelectionExt {
22151 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22152 fn spanned_rows(
22153 &self,
22154 include_end_if_at_line_start: bool,
22155 map: &DisplaySnapshot,
22156 ) -> Range<MultiBufferRow>;
22157}
22158
22159impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22160 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22161 let start = self
22162 .start
22163 .to_point(&map.buffer_snapshot)
22164 .to_display_point(map);
22165 let end = self
22166 .end
22167 .to_point(&map.buffer_snapshot)
22168 .to_display_point(map);
22169 if self.reversed {
22170 end..start
22171 } else {
22172 start..end
22173 }
22174 }
22175
22176 fn spanned_rows(
22177 &self,
22178 include_end_if_at_line_start: bool,
22179 map: &DisplaySnapshot,
22180 ) -> Range<MultiBufferRow> {
22181 let start = self.start.to_point(&map.buffer_snapshot);
22182 let mut end = self.end.to_point(&map.buffer_snapshot);
22183 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22184 end.row -= 1;
22185 }
22186
22187 let buffer_start = map.prev_line_boundary(start).0;
22188 let buffer_end = map.next_line_boundary(end).0;
22189 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22190 }
22191}
22192
22193impl<T: InvalidationRegion> InvalidationStack<T> {
22194 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22195 where
22196 S: Clone + ToOffset,
22197 {
22198 while let Some(region) = self.last() {
22199 let all_selections_inside_invalidation_ranges =
22200 if selections.len() == region.ranges().len() {
22201 selections
22202 .iter()
22203 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22204 .all(|(selection, invalidation_range)| {
22205 let head = selection.head().to_offset(buffer);
22206 invalidation_range.start <= head && invalidation_range.end >= head
22207 })
22208 } else {
22209 false
22210 };
22211
22212 if all_selections_inside_invalidation_ranges {
22213 break;
22214 } else {
22215 self.pop();
22216 }
22217 }
22218 }
22219}
22220
22221impl<T> Default for InvalidationStack<T> {
22222 fn default() -> Self {
22223 Self(Default::default())
22224 }
22225}
22226
22227impl<T> Deref for InvalidationStack<T> {
22228 type Target = Vec<T>;
22229
22230 fn deref(&self) -> &Self::Target {
22231 &self.0
22232 }
22233}
22234
22235impl<T> DerefMut for InvalidationStack<T> {
22236 fn deref_mut(&mut self) -> &mut Self::Target {
22237 &mut self.0
22238 }
22239}
22240
22241impl InvalidationRegion for SnippetState {
22242 fn ranges(&self) -> &[Range<Anchor>] {
22243 &self.ranges[self.active_index]
22244 }
22245}
22246
22247fn inline_completion_edit_text(
22248 current_snapshot: &BufferSnapshot,
22249 edits: &[(Range<Anchor>, String)],
22250 edit_preview: &EditPreview,
22251 include_deletions: bool,
22252 cx: &App,
22253) -> HighlightedText {
22254 let edits = edits
22255 .iter()
22256 .map(|(anchor, text)| {
22257 (
22258 anchor.start.text_anchor..anchor.end.text_anchor,
22259 text.clone(),
22260 )
22261 })
22262 .collect::<Vec<_>>();
22263
22264 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22265}
22266
22267pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22268 match severity {
22269 lsp::DiagnosticSeverity::ERROR => colors.error,
22270 lsp::DiagnosticSeverity::WARNING => colors.warning,
22271 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22272 lsp::DiagnosticSeverity::HINT => colors.info,
22273 _ => colors.ignored,
22274 }
22275}
22276
22277pub fn styled_runs_for_code_label<'a>(
22278 label: &'a CodeLabel,
22279 syntax_theme: &'a theme::SyntaxTheme,
22280) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22281 let fade_out = HighlightStyle {
22282 fade_out: Some(0.35),
22283 ..Default::default()
22284 };
22285
22286 let mut prev_end = label.filter_range.end;
22287 label
22288 .runs
22289 .iter()
22290 .enumerate()
22291 .flat_map(move |(ix, (range, highlight_id))| {
22292 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22293 style
22294 } else {
22295 return Default::default();
22296 };
22297 let mut muted_style = style;
22298 muted_style.highlight(fade_out);
22299
22300 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22301 if range.start >= label.filter_range.end {
22302 if range.start > prev_end {
22303 runs.push((prev_end..range.start, fade_out));
22304 }
22305 runs.push((range.clone(), muted_style));
22306 } else if range.end <= label.filter_range.end {
22307 runs.push((range.clone(), style));
22308 } else {
22309 runs.push((range.start..label.filter_range.end, style));
22310 runs.push((label.filter_range.end..range.end, muted_style));
22311 }
22312 prev_end = cmp::max(prev_end, range.end);
22313
22314 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22315 runs.push((prev_end..label.text.len(), fade_out));
22316 }
22317
22318 runs
22319 })
22320}
22321
22322pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22323 let mut prev_index = 0;
22324 let mut prev_codepoint: Option<char> = None;
22325 text.char_indices()
22326 .chain([(text.len(), '\0')])
22327 .filter_map(move |(index, codepoint)| {
22328 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22329 let is_boundary = index == text.len()
22330 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22331 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22332 if is_boundary {
22333 let chunk = &text[prev_index..index];
22334 prev_index = index;
22335 Some(chunk)
22336 } else {
22337 None
22338 }
22339 })
22340}
22341
22342pub trait RangeToAnchorExt: Sized {
22343 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22344
22345 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22346 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22347 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22348 }
22349}
22350
22351impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22352 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22353 let start_offset = self.start.to_offset(snapshot);
22354 let end_offset = self.end.to_offset(snapshot);
22355 if start_offset == end_offset {
22356 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22357 } else {
22358 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22359 }
22360 }
22361}
22362
22363pub trait RowExt {
22364 fn as_f32(&self) -> f32;
22365
22366 fn next_row(&self) -> Self;
22367
22368 fn previous_row(&self) -> Self;
22369
22370 fn minus(&self, other: Self) -> u32;
22371}
22372
22373impl RowExt for DisplayRow {
22374 fn as_f32(&self) -> f32 {
22375 self.0 as f32
22376 }
22377
22378 fn next_row(&self) -> Self {
22379 Self(self.0 + 1)
22380 }
22381
22382 fn previous_row(&self) -> Self {
22383 Self(self.0.saturating_sub(1))
22384 }
22385
22386 fn minus(&self, other: Self) -> u32 {
22387 self.0 - other.0
22388 }
22389}
22390
22391impl RowExt for MultiBufferRow {
22392 fn as_f32(&self) -> f32 {
22393 self.0 as f32
22394 }
22395
22396 fn next_row(&self) -> Self {
22397 Self(self.0 + 1)
22398 }
22399
22400 fn previous_row(&self) -> Self {
22401 Self(self.0.saturating_sub(1))
22402 }
22403
22404 fn minus(&self, other: Self) -> u32 {
22405 self.0 - other.0
22406 }
22407}
22408
22409trait RowRangeExt {
22410 type Row;
22411
22412 fn len(&self) -> usize;
22413
22414 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22415}
22416
22417impl RowRangeExt for Range<MultiBufferRow> {
22418 type Row = MultiBufferRow;
22419
22420 fn len(&self) -> usize {
22421 (self.end.0 - self.start.0) as usize
22422 }
22423
22424 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22425 (self.start.0..self.end.0).map(MultiBufferRow)
22426 }
22427}
22428
22429impl RowRangeExt for Range<DisplayRow> {
22430 type Row = DisplayRow;
22431
22432 fn len(&self) -> usize {
22433 (self.end.0 - self.start.0) as usize
22434 }
22435
22436 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22437 (self.start.0..self.end.0).map(DisplayRow)
22438 }
22439}
22440
22441/// If select range has more than one line, we
22442/// just point the cursor to range.start.
22443fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22444 if range.start.row == range.end.row {
22445 range
22446 } else {
22447 range.start..range.start
22448 }
22449}
22450pub struct KillRing(ClipboardItem);
22451impl Global for KillRing {}
22452
22453const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22454
22455enum BreakpointPromptEditAction {
22456 Log,
22457 Condition,
22458 HitCondition,
22459}
22460
22461struct BreakpointPromptEditor {
22462 pub(crate) prompt: Entity<Editor>,
22463 editor: WeakEntity<Editor>,
22464 breakpoint_anchor: Anchor,
22465 breakpoint: Breakpoint,
22466 edit_action: BreakpointPromptEditAction,
22467 block_ids: HashSet<CustomBlockId>,
22468 editor_margins: Arc<Mutex<EditorMargins>>,
22469 _subscriptions: Vec<Subscription>,
22470}
22471
22472impl BreakpointPromptEditor {
22473 const MAX_LINES: u8 = 4;
22474
22475 fn new(
22476 editor: WeakEntity<Editor>,
22477 breakpoint_anchor: Anchor,
22478 breakpoint: Breakpoint,
22479 edit_action: BreakpointPromptEditAction,
22480 window: &mut Window,
22481 cx: &mut Context<Self>,
22482 ) -> Self {
22483 let base_text = match edit_action {
22484 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22485 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22486 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22487 }
22488 .map(|msg| msg.to_string())
22489 .unwrap_or_default();
22490
22491 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22492 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22493
22494 let prompt = cx.new(|cx| {
22495 let mut prompt = Editor::new(
22496 EditorMode::AutoHeight {
22497 max_lines: Self::MAX_LINES as usize,
22498 },
22499 buffer,
22500 None,
22501 window,
22502 cx,
22503 );
22504 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22505 prompt.set_show_cursor_when_unfocused(false, cx);
22506 prompt.set_placeholder_text(
22507 match edit_action {
22508 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22509 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22510 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22511 },
22512 cx,
22513 );
22514
22515 prompt
22516 });
22517
22518 Self {
22519 prompt,
22520 editor,
22521 breakpoint_anchor,
22522 breakpoint,
22523 edit_action,
22524 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22525 block_ids: Default::default(),
22526 _subscriptions: vec![],
22527 }
22528 }
22529
22530 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22531 self.block_ids.extend(block_ids)
22532 }
22533
22534 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22535 if let Some(editor) = self.editor.upgrade() {
22536 let message = self
22537 .prompt
22538 .read(cx)
22539 .buffer
22540 .read(cx)
22541 .as_singleton()
22542 .expect("A multi buffer in breakpoint prompt isn't possible")
22543 .read(cx)
22544 .as_rope()
22545 .to_string();
22546
22547 editor.update(cx, |editor, cx| {
22548 editor.edit_breakpoint_at_anchor(
22549 self.breakpoint_anchor,
22550 self.breakpoint.clone(),
22551 match self.edit_action {
22552 BreakpointPromptEditAction::Log => {
22553 BreakpointEditAction::EditLogMessage(message.into())
22554 }
22555 BreakpointPromptEditAction::Condition => {
22556 BreakpointEditAction::EditCondition(message.into())
22557 }
22558 BreakpointPromptEditAction::HitCondition => {
22559 BreakpointEditAction::EditHitCondition(message.into())
22560 }
22561 },
22562 cx,
22563 );
22564
22565 editor.remove_blocks(self.block_ids.clone(), None, cx);
22566 cx.focus_self(window);
22567 });
22568 }
22569 }
22570
22571 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22572 self.editor
22573 .update(cx, |editor, cx| {
22574 editor.remove_blocks(self.block_ids.clone(), None, cx);
22575 window.focus(&editor.focus_handle);
22576 })
22577 .log_err();
22578 }
22579
22580 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22581 let settings = ThemeSettings::get_global(cx);
22582 let text_style = TextStyle {
22583 color: if self.prompt.read(cx).read_only(cx) {
22584 cx.theme().colors().text_disabled
22585 } else {
22586 cx.theme().colors().text
22587 },
22588 font_family: settings.buffer_font.family.clone(),
22589 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22590 font_size: settings.buffer_font_size(cx).into(),
22591 font_weight: settings.buffer_font.weight,
22592 line_height: relative(settings.buffer_line_height.value()),
22593 ..Default::default()
22594 };
22595 EditorElement::new(
22596 &self.prompt,
22597 EditorStyle {
22598 background: cx.theme().colors().editor_background,
22599 local_player: cx.theme().players().local(),
22600 text: text_style,
22601 ..Default::default()
22602 },
22603 )
22604 }
22605}
22606
22607impl Render for BreakpointPromptEditor {
22608 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22609 let editor_margins = *self.editor_margins.lock();
22610 let gutter_dimensions = editor_margins.gutter;
22611 h_flex()
22612 .key_context("Editor")
22613 .bg(cx.theme().colors().editor_background)
22614 .border_y_1()
22615 .border_color(cx.theme().status().info_border)
22616 .size_full()
22617 .py(window.line_height() / 2.5)
22618 .on_action(cx.listener(Self::confirm))
22619 .on_action(cx.listener(Self::cancel))
22620 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22621 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22622 }
22623}
22624
22625impl Focusable for BreakpointPromptEditor {
22626 fn focus_handle(&self, cx: &App) -> FocusHandle {
22627 self.prompt.focus_handle(cx)
22628 }
22629}
22630
22631fn all_edits_insertions_or_deletions(
22632 edits: &Vec<(Range<Anchor>, String)>,
22633 snapshot: &MultiBufferSnapshot,
22634) -> bool {
22635 let mut all_insertions = true;
22636 let mut all_deletions = true;
22637
22638 for (range, new_text) in edits.iter() {
22639 let range_is_empty = range.to_offset(&snapshot).is_empty();
22640 let text_is_empty = new_text.is_empty();
22641
22642 if range_is_empty != text_is_empty {
22643 if range_is_empty {
22644 all_deletions = false;
22645 } else {
22646 all_insertions = false;
22647 }
22648 } else {
22649 return false;
22650 }
22651
22652 if !all_insertions && !all_deletions {
22653 return false;
22654 }
22655 }
22656 all_insertions || all_deletions
22657}
22658
22659struct MissingEditPredictionKeybindingTooltip;
22660
22661impl Render for MissingEditPredictionKeybindingTooltip {
22662 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22663 ui::tooltip_container(window, cx, |container, _, cx| {
22664 container
22665 .flex_shrink_0()
22666 .max_w_80()
22667 .min_h(rems_from_px(124.))
22668 .justify_between()
22669 .child(
22670 v_flex()
22671 .flex_1()
22672 .text_ui_sm(cx)
22673 .child(Label::new("Conflict with Accept Keybinding"))
22674 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22675 )
22676 .child(
22677 h_flex()
22678 .pb_1()
22679 .gap_1()
22680 .items_end()
22681 .w_full()
22682 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22683 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22684 }))
22685 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22686 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22687 })),
22688 )
22689 })
22690 }
22691}
22692
22693#[derive(Debug, Clone, Copy, PartialEq)]
22694pub struct LineHighlight {
22695 pub background: Background,
22696 pub border: Option<gpui::Hsla>,
22697 pub include_gutter: bool,
22698 pub type_id: Option<TypeId>,
22699}
22700
22701fn render_diff_hunk_controls(
22702 row: u32,
22703 status: &DiffHunkStatus,
22704 hunk_range: Range<Anchor>,
22705 is_created_file: bool,
22706 line_height: Pixels,
22707 editor: &Entity<Editor>,
22708 _window: &mut Window,
22709 cx: &mut App,
22710) -> AnyElement {
22711 h_flex()
22712 .h(line_height)
22713 .mr_1()
22714 .gap_1()
22715 .px_0p5()
22716 .pb_1()
22717 .border_x_1()
22718 .border_b_1()
22719 .border_color(cx.theme().colors().border_variant)
22720 .rounded_b_lg()
22721 .bg(cx.theme().colors().editor_background)
22722 .gap_1()
22723 .block_mouse_except_scroll()
22724 .shadow_md()
22725 .child(if status.has_secondary_hunk() {
22726 Button::new(("stage", row as u64), "Stage")
22727 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22728 .tooltip({
22729 let focus_handle = editor.focus_handle(cx);
22730 move |window, cx| {
22731 Tooltip::for_action_in(
22732 "Stage Hunk",
22733 &::git::ToggleStaged,
22734 &focus_handle,
22735 window,
22736 cx,
22737 )
22738 }
22739 })
22740 .on_click({
22741 let editor = editor.clone();
22742 move |_event, _window, cx| {
22743 editor.update(cx, |editor, cx| {
22744 editor.stage_or_unstage_diff_hunks(
22745 true,
22746 vec![hunk_range.start..hunk_range.start],
22747 cx,
22748 );
22749 });
22750 }
22751 })
22752 } else {
22753 Button::new(("unstage", row as u64), "Unstage")
22754 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22755 .tooltip({
22756 let focus_handle = editor.focus_handle(cx);
22757 move |window, cx| {
22758 Tooltip::for_action_in(
22759 "Unstage Hunk",
22760 &::git::ToggleStaged,
22761 &focus_handle,
22762 window,
22763 cx,
22764 )
22765 }
22766 })
22767 .on_click({
22768 let editor = editor.clone();
22769 move |_event, _window, cx| {
22770 editor.update(cx, |editor, cx| {
22771 editor.stage_or_unstage_diff_hunks(
22772 false,
22773 vec![hunk_range.start..hunk_range.start],
22774 cx,
22775 );
22776 });
22777 }
22778 })
22779 })
22780 .child(
22781 Button::new(("restore", row as u64), "Restore")
22782 .tooltip({
22783 let focus_handle = editor.focus_handle(cx);
22784 move |window, cx| {
22785 Tooltip::for_action_in(
22786 "Restore Hunk",
22787 &::git::Restore,
22788 &focus_handle,
22789 window,
22790 cx,
22791 )
22792 }
22793 })
22794 .on_click({
22795 let editor = editor.clone();
22796 move |_event, window, cx| {
22797 editor.update(cx, |editor, cx| {
22798 let snapshot = editor.snapshot(window, cx);
22799 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22800 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22801 });
22802 }
22803 })
22804 .disabled(is_created_file),
22805 )
22806 .when(
22807 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22808 |el| {
22809 el.child(
22810 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22811 .shape(IconButtonShape::Square)
22812 .icon_size(IconSize::Small)
22813 // .disabled(!has_multiple_hunks)
22814 .tooltip({
22815 let focus_handle = editor.focus_handle(cx);
22816 move |window, cx| {
22817 Tooltip::for_action_in(
22818 "Next Hunk",
22819 &GoToHunk,
22820 &focus_handle,
22821 window,
22822 cx,
22823 )
22824 }
22825 })
22826 .on_click({
22827 let editor = editor.clone();
22828 move |_event, window, cx| {
22829 editor.update(cx, |editor, cx| {
22830 let snapshot = editor.snapshot(window, cx);
22831 let position =
22832 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22833 editor.go_to_hunk_before_or_after_position(
22834 &snapshot,
22835 position,
22836 Direction::Next,
22837 window,
22838 cx,
22839 );
22840 editor.expand_selected_diff_hunks(cx);
22841 });
22842 }
22843 }),
22844 )
22845 .child(
22846 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22847 .shape(IconButtonShape::Square)
22848 .icon_size(IconSize::Small)
22849 // .disabled(!has_multiple_hunks)
22850 .tooltip({
22851 let focus_handle = editor.focus_handle(cx);
22852 move |window, cx| {
22853 Tooltip::for_action_in(
22854 "Previous Hunk",
22855 &GoToPreviousHunk,
22856 &focus_handle,
22857 window,
22858 cx,
22859 )
22860 }
22861 })
22862 .on_click({
22863 let editor = editor.clone();
22864 move |_event, window, cx| {
22865 editor.update(cx, |editor, cx| {
22866 let snapshot = editor.snapshot(window, cx);
22867 let point =
22868 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22869 editor.go_to_hunk_before_or_after_position(
22870 &snapshot,
22871 point,
22872 Direction::Prev,
22873 window,
22874 cx,
22875 );
22876 editor.expand_selected_diff_hunks(cx);
22877 });
22878 }
22879 }),
22880 )
22881 },
22882 )
22883 .into_any_element()
22884}