1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 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
1225#[derive(Debug)]
1226pub struct SelectionEffects {
1227 nav_history: bool,
1228 completions: bool,
1229 scroll: Option<Autoscroll>,
1230}
1231
1232impl Default for SelectionEffects {
1233 fn default() -> Self {
1234 Self {
1235 nav_history: true,
1236 completions: true,
1237 scroll: Some(Autoscroll::fit()),
1238 }
1239 }
1240}
1241impl SelectionEffects {
1242 pub fn scroll(scroll: Autoscroll) -> Self {
1243 Self {
1244 scroll: Some(scroll),
1245 ..Default::default()
1246 }
1247 }
1248
1249 pub fn no_scroll() -> Self {
1250 Self {
1251 scroll: None,
1252 ..Default::default()
1253 }
1254 }
1255
1256 pub fn completions(self, completions: bool) -> Self {
1257 Self {
1258 completions,
1259 ..self
1260 }
1261 }
1262
1263 pub fn nav_history(self, nav_history: bool) -> Self {
1264 Self {
1265 nav_history,
1266 ..self
1267 }
1268 }
1269}
1270
1271struct DeferredSelectionEffectsState {
1272 changed: bool,
1273 effects: SelectionEffects,
1274 old_cursor_position: Anchor,
1275 history_entry: SelectionHistoryEntry,
1276}
1277
1278#[derive(Default)]
1279struct SelectionHistory {
1280 #[allow(clippy::type_complexity)]
1281 selections_by_transaction:
1282 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1283 mode: SelectionHistoryMode,
1284 undo_stack: VecDeque<SelectionHistoryEntry>,
1285 redo_stack: VecDeque<SelectionHistoryEntry>,
1286}
1287
1288impl SelectionHistory {
1289 #[track_caller]
1290 fn insert_transaction(
1291 &mut self,
1292 transaction_id: TransactionId,
1293 selections: Arc<[Selection<Anchor>]>,
1294 ) {
1295 if selections.is_empty() {
1296 log::error!(
1297 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1298 std::panic::Location::caller()
1299 );
1300 return;
1301 }
1302 self.selections_by_transaction
1303 .insert(transaction_id, (selections, None));
1304 }
1305
1306 #[allow(clippy::type_complexity)]
1307 fn transaction(
1308 &self,
1309 transaction_id: TransactionId,
1310 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1311 self.selections_by_transaction.get(&transaction_id)
1312 }
1313
1314 #[allow(clippy::type_complexity)]
1315 fn transaction_mut(
1316 &mut self,
1317 transaction_id: TransactionId,
1318 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1319 self.selections_by_transaction.get_mut(&transaction_id)
1320 }
1321
1322 fn push(&mut self, entry: SelectionHistoryEntry) {
1323 if !entry.selections.is_empty() {
1324 match self.mode {
1325 SelectionHistoryMode::Normal => {
1326 self.push_undo(entry);
1327 self.redo_stack.clear();
1328 }
1329 SelectionHistoryMode::Undoing => self.push_redo(entry),
1330 SelectionHistoryMode::Redoing => self.push_undo(entry),
1331 SelectionHistoryMode::Skipping => {}
1332 }
1333 }
1334 }
1335
1336 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1337 if self
1338 .undo_stack
1339 .back()
1340 .map_or(true, |e| e.selections != entry.selections)
1341 {
1342 self.undo_stack.push_back(entry);
1343 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1344 self.undo_stack.pop_front();
1345 }
1346 }
1347 }
1348
1349 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1350 if self
1351 .redo_stack
1352 .back()
1353 .map_or(true, |e| e.selections != entry.selections)
1354 {
1355 self.redo_stack.push_back(entry);
1356 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1357 self.redo_stack.pop_front();
1358 }
1359 }
1360 }
1361}
1362
1363#[derive(Clone, Copy)]
1364pub struct RowHighlightOptions {
1365 pub autoscroll: bool,
1366 pub include_gutter: bool,
1367}
1368
1369impl Default for RowHighlightOptions {
1370 fn default() -> Self {
1371 Self {
1372 autoscroll: Default::default(),
1373 include_gutter: true,
1374 }
1375 }
1376}
1377
1378struct RowHighlight {
1379 index: usize,
1380 range: Range<Anchor>,
1381 color: Hsla,
1382 options: RowHighlightOptions,
1383 type_id: TypeId,
1384}
1385
1386#[derive(Clone, Debug)]
1387struct AddSelectionsState {
1388 groups: Vec<AddSelectionsGroup>,
1389}
1390
1391#[derive(Clone, Debug)]
1392struct AddSelectionsGroup {
1393 above: bool,
1394 stack: Vec<usize>,
1395}
1396
1397#[derive(Clone)]
1398struct SelectNextState {
1399 query: AhoCorasick,
1400 wordwise: bool,
1401 done: bool,
1402}
1403
1404impl std::fmt::Debug for SelectNextState {
1405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1406 f.debug_struct(std::any::type_name::<Self>())
1407 .field("wordwise", &self.wordwise)
1408 .field("done", &self.done)
1409 .finish()
1410 }
1411}
1412
1413#[derive(Debug)]
1414struct AutocloseRegion {
1415 selection_id: usize,
1416 range: Range<Anchor>,
1417 pair: BracketPair,
1418}
1419
1420#[derive(Debug)]
1421struct SnippetState {
1422 ranges: Vec<Vec<Range<Anchor>>>,
1423 active_index: usize,
1424 choices: Vec<Option<Vec<String>>>,
1425}
1426
1427#[doc(hidden)]
1428pub struct RenameState {
1429 pub range: Range<Anchor>,
1430 pub old_name: Arc<str>,
1431 pub editor: Entity<Editor>,
1432 block_id: CustomBlockId,
1433}
1434
1435struct InvalidationStack<T>(Vec<T>);
1436
1437struct RegisteredInlineCompletionProvider {
1438 provider: Arc<dyn InlineCompletionProviderHandle>,
1439 _subscription: Subscription,
1440}
1441
1442#[derive(Debug, PartialEq, Eq)]
1443pub struct ActiveDiagnosticGroup {
1444 pub active_range: Range<Anchor>,
1445 pub active_message: String,
1446 pub group_id: usize,
1447 pub blocks: HashSet<CustomBlockId>,
1448}
1449
1450#[derive(Debug, PartialEq, Eq)]
1451
1452pub(crate) enum ActiveDiagnostic {
1453 None,
1454 All,
1455 Group(ActiveDiagnosticGroup),
1456}
1457
1458#[derive(Serialize, Deserialize, Clone, Debug)]
1459pub struct ClipboardSelection {
1460 /// The number of bytes in this selection.
1461 pub len: usize,
1462 /// Whether this was a full-line selection.
1463 pub is_entire_line: bool,
1464 /// The indentation of the first line when this content was originally copied.
1465 pub first_line_indent: u32,
1466}
1467
1468// selections, scroll behavior, was newest selection reversed
1469type SelectSyntaxNodeHistoryState = (
1470 Box<[Selection<usize>]>,
1471 SelectSyntaxNodeScrollBehavior,
1472 bool,
1473);
1474
1475#[derive(Default)]
1476struct SelectSyntaxNodeHistory {
1477 stack: Vec<SelectSyntaxNodeHistoryState>,
1478 // disable temporarily to allow changing selections without losing the stack
1479 pub disable_clearing: bool,
1480}
1481
1482impl SelectSyntaxNodeHistory {
1483 pub fn try_clear(&mut self) {
1484 if !self.disable_clearing {
1485 self.stack.clear();
1486 }
1487 }
1488
1489 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1490 self.stack.push(selection);
1491 }
1492
1493 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1494 self.stack.pop()
1495 }
1496}
1497
1498enum SelectSyntaxNodeScrollBehavior {
1499 CursorTop,
1500 FitSelection,
1501 CursorBottom,
1502}
1503
1504#[derive(Debug)]
1505pub(crate) struct NavigationData {
1506 cursor_anchor: Anchor,
1507 cursor_position: Point,
1508 scroll_anchor: ScrollAnchor,
1509 scroll_top_row: u32,
1510}
1511
1512#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1513pub enum GotoDefinitionKind {
1514 Symbol,
1515 Declaration,
1516 Type,
1517 Implementation,
1518}
1519
1520#[derive(Debug, Clone)]
1521enum InlayHintRefreshReason {
1522 ModifiersChanged(bool),
1523 Toggle(bool),
1524 SettingsChange(InlayHintSettings),
1525 NewLinesShown,
1526 BufferEdited(HashSet<Arc<Language>>),
1527 RefreshRequested,
1528 ExcerptsRemoved(Vec<ExcerptId>),
1529}
1530
1531impl InlayHintRefreshReason {
1532 fn description(&self) -> &'static str {
1533 match self {
1534 Self::ModifiersChanged(_) => "modifiers changed",
1535 Self::Toggle(_) => "toggle",
1536 Self::SettingsChange(_) => "settings change",
1537 Self::NewLinesShown => "new lines shown",
1538 Self::BufferEdited(_) => "buffer edited",
1539 Self::RefreshRequested => "refresh requested",
1540 Self::ExcerptsRemoved(_) => "excerpts removed",
1541 }
1542 }
1543}
1544
1545pub enum FormatTarget {
1546 Buffers(HashSet<Entity<Buffer>>),
1547 Ranges(Vec<Range<MultiBufferPoint>>),
1548}
1549
1550pub(crate) struct FocusedBlock {
1551 id: BlockId,
1552 focus_handle: WeakFocusHandle,
1553}
1554
1555#[derive(Clone)]
1556enum JumpData {
1557 MultiBufferRow {
1558 row: MultiBufferRow,
1559 line_offset_from_top: u32,
1560 },
1561 MultiBufferPoint {
1562 excerpt_id: ExcerptId,
1563 position: Point,
1564 anchor: text::Anchor,
1565 line_offset_from_top: u32,
1566 },
1567}
1568
1569pub enum MultibufferSelectionMode {
1570 First,
1571 All,
1572}
1573
1574#[derive(Clone, Copy, Debug, Default)]
1575pub struct RewrapOptions {
1576 pub override_language_settings: bool,
1577 pub preserve_existing_whitespace: bool,
1578}
1579
1580impl Editor {
1581 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1582 let buffer = cx.new(|cx| Buffer::local("", cx));
1583 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1584 Self::new(
1585 EditorMode::SingleLine { auto_width: false },
1586 buffer,
1587 None,
1588 window,
1589 cx,
1590 )
1591 }
1592
1593 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1594 let buffer = cx.new(|cx| Buffer::local("", cx));
1595 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1596 Self::new(EditorMode::full(), buffer, None, window, cx)
1597 }
1598
1599 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1600 let buffer = cx.new(|cx| Buffer::local("", cx));
1601 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1602 Self::new(
1603 EditorMode::SingleLine { auto_width: true },
1604 buffer,
1605 None,
1606 window,
1607 cx,
1608 )
1609 }
1610
1611 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1612 let buffer = cx.new(|cx| Buffer::local("", cx));
1613 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1614 Self::new(
1615 EditorMode::AutoHeight { max_lines },
1616 buffer,
1617 None,
1618 window,
1619 cx,
1620 )
1621 }
1622
1623 pub fn for_buffer(
1624 buffer: Entity<Buffer>,
1625 project: Option<Entity<Project>>,
1626 window: &mut Window,
1627 cx: &mut Context<Self>,
1628 ) -> Self {
1629 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1630 Self::new(EditorMode::full(), buffer, project, window, cx)
1631 }
1632
1633 pub fn for_multibuffer(
1634 buffer: Entity<MultiBuffer>,
1635 project: Option<Entity<Project>>,
1636 window: &mut Window,
1637 cx: &mut Context<Self>,
1638 ) -> Self {
1639 Self::new(EditorMode::full(), buffer, project, window, cx)
1640 }
1641
1642 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1643 let mut clone = Self::new(
1644 self.mode.clone(),
1645 self.buffer.clone(),
1646 self.project.clone(),
1647 window,
1648 cx,
1649 );
1650 self.display_map.update(cx, |display_map, cx| {
1651 let snapshot = display_map.snapshot(cx);
1652 clone.display_map.update(cx, |display_map, cx| {
1653 display_map.set_state(&snapshot, cx);
1654 });
1655 });
1656 clone.folds_did_change(cx);
1657 clone.selections.clone_state(&self.selections);
1658 clone.scroll_manager.clone_state(&self.scroll_manager);
1659 clone.searchable = self.searchable;
1660 clone.read_only = self.read_only;
1661 clone
1662 }
1663
1664 pub fn new(
1665 mode: EditorMode,
1666 buffer: Entity<MultiBuffer>,
1667 project: Option<Entity<Project>>,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) -> Self {
1671 Editor::new_internal(mode, buffer, project, None, window, cx)
1672 }
1673
1674 fn new_internal(
1675 mode: EditorMode,
1676 buffer: Entity<MultiBuffer>,
1677 project: Option<Entity<Project>>,
1678 display_map: Option<Entity<DisplayMap>>,
1679 window: &mut Window,
1680 cx: &mut Context<Self>,
1681 ) -> Self {
1682 debug_assert!(
1683 display_map.is_none() || mode.is_minimap(),
1684 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1685 );
1686
1687 let full_mode = mode.is_full();
1688 let diagnostics_max_severity = if full_mode {
1689 EditorSettings::get_global(cx)
1690 .diagnostics_max_severity
1691 .unwrap_or(DiagnosticSeverity::Hint)
1692 } else {
1693 DiagnosticSeverity::Off
1694 };
1695 let style = window.text_style();
1696 let font_size = style.font_size.to_pixels(window.rem_size());
1697 let editor = cx.entity().downgrade();
1698 let fold_placeholder = FoldPlaceholder {
1699 constrain_width: true,
1700 render: Arc::new(move |fold_id, fold_range, cx| {
1701 let editor = editor.clone();
1702 div()
1703 .id(fold_id)
1704 .bg(cx.theme().colors().ghost_element_background)
1705 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1706 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1707 .rounded_xs()
1708 .size_full()
1709 .cursor_pointer()
1710 .child("⋯")
1711 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1712 .on_click(move |_, _window, cx| {
1713 editor
1714 .update(cx, |editor, cx| {
1715 editor.unfold_ranges(
1716 &[fold_range.start..fold_range.end],
1717 true,
1718 false,
1719 cx,
1720 );
1721 cx.stop_propagation();
1722 })
1723 .ok();
1724 })
1725 .into_any()
1726 }),
1727 merge_adjacent: true,
1728 ..FoldPlaceholder::default()
1729 };
1730 let display_map = display_map.unwrap_or_else(|| {
1731 cx.new(|cx| {
1732 DisplayMap::new(
1733 buffer.clone(),
1734 style.font(),
1735 font_size,
1736 None,
1737 FILE_HEADER_HEIGHT,
1738 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1739 fold_placeholder,
1740 diagnostics_max_severity,
1741 cx,
1742 )
1743 })
1744 });
1745
1746 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1747
1748 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1749
1750 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1751 .then(|| language_settings::SoftWrap::None);
1752
1753 let mut project_subscriptions = Vec::new();
1754 if mode.is_full() {
1755 if let Some(project) = project.as_ref() {
1756 project_subscriptions.push(cx.subscribe_in(
1757 project,
1758 window,
1759 |editor, _, event, window, cx| match event {
1760 project::Event::RefreshCodeLens => {
1761 // we always query lens with actions, without storing them, always refreshing them
1762 }
1763 project::Event::RefreshInlayHints => {
1764 editor
1765 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1766 }
1767 project::Event::LanguageServerAdded(..)
1768 | project::Event::LanguageServerRemoved(..) => {
1769 if editor.tasks_update_task.is_none() {
1770 editor.tasks_update_task =
1771 Some(editor.refresh_runnables(window, cx));
1772 }
1773 editor.pull_diagnostics(None, window, cx);
1774 }
1775 project::Event::SnippetEdit(id, snippet_edits) => {
1776 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1777 let focus_handle = editor.focus_handle(cx);
1778 if focus_handle.is_focused(window) {
1779 let snapshot = buffer.read(cx).snapshot();
1780 for (range, snippet) in snippet_edits {
1781 let editor_range =
1782 language::range_from_lsp(*range).to_offset(&snapshot);
1783 editor
1784 .insert_snippet(
1785 &[editor_range],
1786 snippet.clone(),
1787 window,
1788 cx,
1789 )
1790 .ok();
1791 }
1792 }
1793 }
1794 }
1795 _ => {}
1796 },
1797 ));
1798 if let Some(task_inventory) = project
1799 .read(cx)
1800 .task_store()
1801 .read(cx)
1802 .task_inventory()
1803 .cloned()
1804 {
1805 project_subscriptions.push(cx.observe_in(
1806 &task_inventory,
1807 window,
1808 |editor, _, window, cx| {
1809 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1810 },
1811 ));
1812 };
1813
1814 project_subscriptions.push(cx.subscribe_in(
1815 &project.read(cx).breakpoint_store(),
1816 window,
1817 |editor, _, event, window, cx| match event {
1818 BreakpointStoreEvent::ClearDebugLines => {
1819 editor.clear_row_highlights::<ActiveDebugLine>();
1820 editor.refresh_inline_values(cx);
1821 }
1822 BreakpointStoreEvent::SetDebugLine => {
1823 if editor.go_to_active_debug_line(window, cx) {
1824 cx.stop_propagation();
1825 }
1826
1827 editor.refresh_inline_values(cx);
1828 }
1829 _ => {}
1830 },
1831 ));
1832 }
1833 }
1834
1835 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1836
1837 let inlay_hint_settings =
1838 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1839 let focus_handle = cx.focus_handle();
1840 cx.on_focus(&focus_handle, window, Self::handle_focus)
1841 .detach();
1842 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1843 .detach();
1844 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1845 .detach();
1846 cx.on_blur(&focus_handle, window, Self::handle_blur)
1847 .detach();
1848 cx.observe_pending_input(window, Self::observe_pending_input)
1849 .detach();
1850
1851 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1852 Some(false)
1853 } else {
1854 None
1855 };
1856
1857 let breakpoint_store = match (&mode, project.as_ref()) {
1858 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1859 _ => None,
1860 };
1861
1862 let mut code_action_providers = Vec::new();
1863 let mut load_uncommitted_diff = None;
1864 if let Some(project) = project.clone() {
1865 load_uncommitted_diff = Some(
1866 update_uncommitted_diff_for_buffer(
1867 cx.entity(),
1868 &project,
1869 buffer.read(cx).all_buffers(),
1870 buffer.clone(),
1871 cx,
1872 )
1873 .shared(),
1874 );
1875 code_action_providers.push(Rc::new(project) as Rc<_>);
1876 }
1877
1878 let mut editor = Self {
1879 focus_handle,
1880 show_cursor_when_unfocused: false,
1881 last_focused_descendant: None,
1882 buffer: buffer.clone(),
1883 display_map: display_map.clone(),
1884 selections,
1885 scroll_manager: ScrollManager::new(cx),
1886 columnar_selection_tail: None,
1887 columnar_display_point: None,
1888 add_selections_state: None,
1889 select_next_state: None,
1890 select_prev_state: None,
1891 selection_history: SelectionHistory::default(),
1892 defer_selection_effects: false,
1893 deferred_selection_effects_state: None,
1894 autoclose_regions: Vec::new(),
1895 snippet_stack: InvalidationStack::default(),
1896 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1897 ime_transaction: None,
1898 active_diagnostics: ActiveDiagnostic::None,
1899 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1900 inline_diagnostics_update: Task::ready(()),
1901 inline_diagnostics: Vec::new(),
1902 soft_wrap_mode_override,
1903 diagnostics_max_severity,
1904 hard_wrap: None,
1905 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1906 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1907 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1908 project,
1909 blink_manager: blink_manager.clone(),
1910 show_local_selections: true,
1911 show_scrollbars: ScrollbarAxes {
1912 horizontal: full_mode,
1913 vertical: full_mode,
1914 },
1915 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1916 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1917 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1918 show_gutter: mode.is_full(),
1919 show_line_numbers: None,
1920 use_relative_line_numbers: None,
1921 disable_expand_excerpt_buttons: false,
1922 show_git_diff_gutter: None,
1923 show_code_actions: None,
1924 show_runnables: None,
1925 show_breakpoints: None,
1926 show_wrap_guides: None,
1927 show_indent_guides,
1928 placeholder_text: None,
1929 highlight_order: 0,
1930 highlighted_rows: HashMap::default(),
1931 background_highlights: TreeMap::default(),
1932 gutter_highlights: TreeMap::default(),
1933 scrollbar_marker_state: ScrollbarMarkerState::default(),
1934 active_indent_guides_state: ActiveIndentGuidesState::default(),
1935 nav_history: None,
1936 context_menu: RefCell::new(None),
1937 context_menu_options: None,
1938 mouse_context_menu: None,
1939 completion_tasks: Vec::new(),
1940 inline_blame_popover: None,
1941 signature_help_state: SignatureHelpState::default(),
1942 auto_signature_help: None,
1943 find_all_references_task_sources: Vec::new(),
1944 next_completion_id: 0,
1945 next_inlay_id: 0,
1946 code_action_providers,
1947 available_code_actions: None,
1948 code_actions_task: None,
1949 quick_selection_highlight_task: None,
1950 debounced_selection_highlight_task: None,
1951 document_highlights_task: None,
1952 linked_editing_range_task: None,
1953 pending_rename: None,
1954 searchable: true,
1955 cursor_shape: EditorSettings::get_global(cx)
1956 .cursor_shape
1957 .unwrap_or_default(),
1958 current_line_highlight: None,
1959 autoindent_mode: Some(AutoindentMode::EachLine),
1960 collapse_matches: false,
1961 workspace: None,
1962 input_enabled: true,
1963 use_modal_editing: mode.is_full(),
1964 read_only: mode.is_minimap(),
1965 use_autoclose: true,
1966 use_auto_surround: true,
1967 auto_replace_emoji_shortcode: false,
1968 jsx_tag_auto_close_enabled_in_any_buffer: false,
1969 leader_id: None,
1970 remote_id: None,
1971 hover_state: HoverState::default(),
1972 pending_mouse_down: None,
1973 hovered_link_state: None,
1974 edit_prediction_provider: None,
1975 active_inline_completion: None,
1976 stale_inline_completion_in_menu: None,
1977 edit_prediction_preview: EditPredictionPreview::Inactive {
1978 released_too_fast: false,
1979 },
1980 inline_diagnostics_enabled: mode.is_full(),
1981 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1982 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1983
1984 gutter_hovered: false,
1985 pixel_position_of_newest_cursor: None,
1986 last_bounds: None,
1987 last_position_map: None,
1988 expect_bounds_change: None,
1989 gutter_dimensions: GutterDimensions::default(),
1990 style: None,
1991 show_cursor_names: false,
1992 hovered_cursors: HashMap::default(),
1993 next_editor_action_id: EditorActionId::default(),
1994 editor_actions: Rc::default(),
1995 inline_completions_hidden_for_vim_mode: false,
1996 show_inline_completions_override: None,
1997 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1998 edit_prediction_settings: EditPredictionSettings::Disabled,
1999 edit_prediction_indent_conflict: false,
2000 edit_prediction_requires_modifier_in_indent_conflict: true,
2001 custom_context_menu: None,
2002 show_git_blame_gutter: false,
2003 show_git_blame_inline: false,
2004 show_selection_menu: None,
2005 show_git_blame_inline_delay_task: None,
2006 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2007 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2008 serialize_dirty_buffers: !mode.is_minimap()
2009 && ProjectSettings::get_global(cx)
2010 .session
2011 .restore_unsaved_buffers,
2012 blame: None,
2013 blame_subscription: None,
2014 tasks: BTreeMap::default(),
2015
2016 breakpoint_store,
2017 gutter_breakpoint_indicator: (None, None),
2018 _subscriptions: vec![
2019 cx.observe(&buffer, Self::on_buffer_changed),
2020 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2021 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2022 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2023 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2024 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2025 cx.observe_window_activation(window, |editor, window, cx| {
2026 let active = window.is_window_active();
2027 editor.blink_manager.update(cx, |blink_manager, cx| {
2028 if active {
2029 blink_manager.enable(cx);
2030 } else {
2031 blink_manager.disable(cx);
2032 }
2033 });
2034 if active {
2035 editor.show_mouse_cursor();
2036 }
2037 }),
2038 ],
2039 tasks_update_task: None,
2040 pull_diagnostics_task: Task::ready(()),
2041 linked_edit_ranges: Default::default(),
2042 in_project_search: false,
2043 previous_search_ranges: None,
2044 breadcrumb_header: None,
2045 focused_block: None,
2046 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2047 addons: HashMap::default(),
2048 registered_buffers: HashMap::default(),
2049 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2050 selection_mark_mode: false,
2051 toggle_fold_multiple_buffers: Task::ready(()),
2052 serialize_selections: Task::ready(()),
2053 serialize_folds: Task::ready(()),
2054 text_style_refinement: None,
2055 load_diff_task: load_uncommitted_diff,
2056 temporary_diff_override: false,
2057 mouse_cursor_hidden: false,
2058 minimap: None,
2059 hide_mouse_mode: EditorSettings::get_global(cx)
2060 .hide_mouse
2061 .unwrap_or_default(),
2062 change_list: ChangeList::new(),
2063 mode,
2064 selection_drag_state: SelectionDragState::None,
2065 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2066 };
2067 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2068 editor
2069 ._subscriptions
2070 .push(cx.observe(breakpoints, |_, _, cx| {
2071 cx.notify();
2072 }));
2073 }
2074 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2075 editor._subscriptions.extend(project_subscriptions);
2076
2077 editor._subscriptions.push(cx.subscribe_in(
2078 &cx.entity(),
2079 window,
2080 |editor, _, e: &EditorEvent, window, cx| match e {
2081 EditorEvent::ScrollPositionChanged { local, .. } => {
2082 if *local {
2083 let new_anchor = editor.scroll_manager.anchor();
2084 let snapshot = editor.snapshot(window, cx);
2085 editor.update_restoration_data(cx, move |data| {
2086 data.scroll_position = (
2087 new_anchor.top_row(&snapshot.buffer_snapshot),
2088 new_anchor.offset,
2089 );
2090 });
2091 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2092 editor.inline_blame_popover.take();
2093 }
2094 }
2095 EditorEvent::Edited { .. } => {
2096 if !vim_enabled(cx) {
2097 let (map, selections) = editor.selections.all_adjusted_display(cx);
2098 let pop_state = editor
2099 .change_list
2100 .last()
2101 .map(|previous| {
2102 previous.len() == selections.len()
2103 && previous.iter().enumerate().all(|(ix, p)| {
2104 p.to_display_point(&map).row()
2105 == selections[ix].head().row()
2106 })
2107 })
2108 .unwrap_or(false);
2109 let new_positions = selections
2110 .into_iter()
2111 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2112 .collect();
2113 editor
2114 .change_list
2115 .push_to_change_list(pop_state, new_positions);
2116 }
2117 }
2118 _ => (),
2119 },
2120 ));
2121
2122 if let Some(dap_store) = editor
2123 .project
2124 .as_ref()
2125 .map(|project| project.read(cx).dap_store())
2126 {
2127 let weak_editor = cx.weak_entity();
2128
2129 editor
2130 ._subscriptions
2131 .push(
2132 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2133 let session_entity = cx.entity();
2134 weak_editor
2135 .update(cx, |editor, cx| {
2136 editor._subscriptions.push(
2137 cx.subscribe(&session_entity, Self::on_debug_session_event),
2138 );
2139 })
2140 .ok();
2141 }),
2142 );
2143
2144 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2145 editor
2146 ._subscriptions
2147 .push(cx.subscribe(&session, Self::on_debug_session_event));
2148 }
2149 }
2150
2151 // skip adding the initial selection to selection history
2152 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2153 editor.end_selection(window, cx);
2154 editor.selection_history.mode = SelectionHistoryMode::Normal;
2155
2156 editor.scroll_manager.show_scrollbars(window, cx);
2157 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2158
2159 if full_mode {
2160 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2161 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2162
2163 if editor.git_blame_inline_enabled {
2164 editor.start_git_blame_inline(false, window, cx);
2165 }
2166
2167 editor.go_to_active_debug_line(window, cx);
2168
2169 if let Some(buffer) = buffer.read(cx).as_singleton() {
2170 if let Some(project) = editor.project.as_ref() {
2171 let handle = project.update(cx, |project, cx| {
2172 project.register_buffer_with_language_servers(&buffer, cx)
2173 });
2174 editor
2175 .registered_buffers
2176 .insert(buffer.read(cx).remote_id(), handle);
2177 }
2178 }
2179
2180 editor.minimap =
2181 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2182 editor.pull_diagnostics(None, window, cx);
2183 }
2184
2185 editor.report_editor_event("Editor Opened", None, cx);
2186 editor
2187 }
2188
2189 pub fn deploy_mouse_context_menu(
2190 &mut self,
2191 position: gpui::Point<Pixels>,
2192 context_menu: Entity<ContextMenu>,
2193 window: &mut Window,
2194 cx: &mut Context<Self>,
2195 ) {
2196 self.mouse_context_menu = Some(MouseContextMenu::new(
2197 self,
2198 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2199 context_menu,
2200 window,
2201 cx,
2202 ));
2203 }
2204
2205 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2206 self.mouse_context_menu
2207 .as_ref()
2208 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2209 }
2210
2211 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2212 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2213 }
2214
2215 fn key_context_internal(
2216 &self,
2217 has_active_edit_prediction: bool,
2218 window: &Window,
2219 cx: &App,
2220 ) -> KeyContext {
2221 let mut key_context = KeyContext::new_with_defaults();
2222 key_context.add("Editor");
2223 let mode = match self.mode {
2224 EditorMode::SingleLine { .. } => "single_line",
2225 EditorMode::AutoHeight { .. } => "auto_height",
2226 EditorMode::Minimap { .. } => "minimap",
2227 EditorMode::Full { .. } => "full",
2228 };
2229
2230 if EditorSettings::jupyter_enabled(cx) {
2231 key_context.add("jupyter");
2232 }
2233
2234 key_context.set("mode", mode);
2235 if self.pending_rename.is_some() {
2236 key_context.add("renaming");
2237 }
2238
2239 match self.context_menu.borrow().as_ref() {
2240 Some(CodeContextMenu::Completions(_)) => {
2241 key_context.add("menu");
2242 key_context.add("showing_completions");
2243 }
2244 Some(CodeContextMenu::CodeActions(_)) => {
2245 key_context.add("menu");
2246 key_context.add("showing_code_actions")
2247 }
2248 None => {}
2249 }
2250
2251 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2252 if !self.focus_handle(cx).contains_focused(window, cx)
2253 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2254 {
2255 for addon in self.addons.values() {
2256 addon.extend_key_context(&mut key_context, cx)
2257 }
2258 }
2259
2260 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2261 if let Some(extension) = singleton_buffer
2262 .read(cx)
2263 .file()
2264 .and_then(|file| file.path().extension()?.to_str())
2265 {
2266 key_context.set("extension", extension.to_string());
2267 }
2268 } else {
2269 key_context.add("multibuffer");
2270 }
2271
2272 if has_active_edit_prediction {
2273 if self.edit_prediction_in_conflict() {
2274 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2275 } else {
2276 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2277 key_context.add("copilot_suggestion");
2278 }
2279 }
2280
2281 if self.selection_mark_mode {
2282 key_context.add("selection_mode");
2283 }
2284
2285 key_context
2286 }
2287
2288 fn show_mouse_cursor(&mut self) {
2289 self.mouse_cursor_hidden = false;
2290 }
2291
2292 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2293 self.mouse_cursor_hidden = match origin {
2294 HideMouseCursorOrigin::TypingAction => {
2295 matches!(
2296 self.hide_mouse_mode,
2297 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2298 )
2299 }
2300 HideMouseCursorOrigin::MovementAction => {
2301 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2302 }
2303 };
2304 }
2305
2306 pub fn edit_prediction_in_conflict(&self) -> bool {
2307 if !self.show_edit_predictions_in_menu() {
2308 return false;
2309 }
2310
2311 let showing_completions = self
2312 .context_menu
2313 .borrow()
2314 .as_ref()
2315 .map_or(false, |context| {
2316 matches!(context, CodeContextMenu::Completions(_))
2317 });
2318
2319 showing_completions
2320 || self.edit_prediction_requires_modifier()
2321 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2322 // bindings to insert tab characters.
2323 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2324 }
2325
2326 pub fn accept_edit_prediction_keybind(
2327 &self,
2328 accept_partial: bool,
2329 window: &Window,
2330 cx: &App,
2331 ) -> AcceptEditPredictionBinding {
2332 let key_context = self.key_context_internal(true, window, cx);
2333 let in_conflict = self.edit_prediction_in_conflict();
2334
2335 let bindings = if accept_partial {
2336 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2337 } else {
2338 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2339 };
2340
2341 // TODO: if the binding contains multiple keystrokes, display all of them, not
2342 // just the first one.
2343 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2344 !in_conflict
2345 || binding
2346 .keystrokes()
2347 .first()
2348 .map_or(false, |keystroke| keystroke.modifiers.modified())
2349 }))
2350 }
2351
2352 pub fn new_file(
2353 workspace: &mut Workspace,
2354 _: &workspace::NewFile,
2355 window: &mut Window,
2356 cx: &mut Context<Workspace>,
2357 ) {
2358 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2359 "Failed to create buffer",
2360 window,
2361 cx,
2362 |e, _, _| match e.error_code() {
2363 ErrorCode::RemoteUpgradeRequired => Some(format!(
2364 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2365 e.error_tag("required").unwrap_or("the latest version")
2366 )),
2367 _ => None,
2368 },
2369 );
2370 }
2371
2372 pub fn new_in_workspace(
2373 workspace: &mut Workspace,
2374 window: &mut Window,
2375 cx: &mut Context<Workspace>,
2376 ) -> Task<Result<Entity<Editor>>> {
2377 let project = workspace.project().clone();
2378 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2379
2380 cx.spawn_in(window, async move |workspace, cx| {
2381 let buffer = create.await?;
2382 workspace.update_in(cx, |workspace, window, cx| {
2383 let editor =
2384 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2385 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2386 editor
2387 })
2388 })
2389 }
2390
2391 fn new_file_vertical(
2392 workspace: &mut Workspace,
2393 _: &workspace::NewFileSplitVertical,
2394 window: &mut Window,
2395 cx: &mut Context<Workspace>,
2396 ) {
2397 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2398 }
2399
2400 fn new_file_horizontal(
2401 workspace: &mut Workspace,
2402 _: &workspace::NewFileSplitHorizontal,
2403 window: &mut Window,
2404 cx: &mut Context<Workspace>,
2405 ) {
2406 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2407 }
2408
2409 fn new_file_in_direction(
2410 workspace: &mut Workspace,
2411 direction: SplitDirection,
2412 window: &mut Window,
2413 cx: &mut Context<Workspace>,
2414 ) {
2415 let project = workspace.project().clone();
2416 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2417
2418 cx.spawn_in(window, async move |workspace, cx| {
2419 let buffer = create.await?;
2420 workspace.update_in(cx, move |workspace, window, cx| {
2421 workspace.split_item(
2422 direction,
2423 Box::new(
2424 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2425 ),
2426 window,
2427 cx,
2428 )
2429 })?;
2430 anyhow::Ok(())
2431 })
2432 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2433 match e.error_code() {
2434 ErrorCode::RemoteUpgradeRequired => Some(format!(
2435 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2436 e.error_tag("required").unwrap_or("the latest version")
2437 )),
2438 _ => None,
2439 }
2440 });
2441 }
2442
2443 pub fn leader_id(&self) -> Option<CollaboratorId> {
2444 self.leader_id
2445 }
2446
2447 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2448 &self.buffer
2449 }
2450
2451 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2452 self.workspace.as_ref()?.0.upgrade()
2453 }
2454
2455 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2456 self.buffer().read(cx).title(cx)
2457 }
2458
2459 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2460 let git_blame_gutter_max_author_length = self
2461 .render_git_blame_gutter(cx)
2462 .then(|| {
2463 if let Some(blame) = self.blame.as_ref() {
2464 let max_author_length =
2465 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2466 Some(max_author_length)
2467 } else {
2468 None
2469 }
2470 })
2471 .flatten();
2472
2473 EditorSnapshot {
2474 mode: self.mode.clone(),
2475 show_gutter: self.show_gutter,
2476 show_line_numbers: self.show_line_numbers,
2477 show_git_diff_gutter: self.show_git_diff_gutter,
2478 show_code_actions: self.show_code_actions,
2479 show_runnables: self.show_runnables,
2480 show_breakpoints: self.show_breakpoints,
2481 git_blame_gutter_max_author_length,
2482 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2483 scroll_anchor: self.scroll_manager.anchor(),
2484 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2485 placeholder_text: self.placeholder_text.clone(),
2486 is_focused: self.focus_handle.is_focused(window),
2487 current_line_highlight: self
2488 .current_line_highlight
2489 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2490 gutter_hovered: self.gutter_hovered,
2491 }
2492 }
2493
2494 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2495 self.buffer.read(cx).language_at(point, cx)
2496 }
2497
2498 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2499 self.buffer.read(cx).read(cx).file_at(point).cloned()
2500 }
2501
2502 pub fn active_excerpt(
2503 &self,
2504 cx: &App,
2505 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2506 self.buffer
2507 .read(cx)
2508 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2509 }
2510
2511 pub fn mode(&self) -> &EditorMode {
2512 &self.mode
2513 }
2514
2515 pub fn set_mode(&mut self, mode: EditorMode) {
2516 self.mode = mode;
2517 }
2518
2519 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2520 self.collaboration_hub.as_deref()
2521 }
2522
2523 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2524 self.collaboration_hub = Some(hub);
2525 }
2526
2527 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2528 self.in_project_search = in_project_search;
2529 }
2530
2531 pub fn set_custom_context_menu(
2532 &mut self,
2533 f: impl 'static
2534 + Fn(
2535 &mut Self,
2536 DisplayPoint,
2537 &mut Window,
2538 &mut Context<Self>,
2539 ) -> Option<Entity<ui::ContextMenu>>,
2540 ) {
2541 self.custom_context_menu = Some(Box::new(f))
2542 }
2543
2544 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2545 self.completion_provider = provider;
2546 }
2547
2548 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2549 self.semantics_provider.clone()
2550 }
2551
2552 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2553 self.semantics_provider = provider;
2554 }
2555
2556 pub fn set_edit_prediction_provider<T>(
2557 &mut self,
2558 provider: Option<Entity<T>>,
2559 window: &mut Window,
2560 cx: &mut Context<Self>,
2561 ) where
2562 T: EditPredictionProvider,
2563 {
2564 self.edit_prediction_provider =
2565 provider.map(|provider| RegisteredInlineCompletionProvider {
2566 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2567 if this.focus_handle.is_focused(window) {
2568 this.update_visible_inline_completion(window, cx);
2569 }
2570 }),
2571 provider: Arc::new(provider),
2572 });
2573 self.update_edit_prediction_settings(cx);
2574 self.refresh_inline_completion(false, false, window, cx);
2575 }
2576
2577 pub fn placeholder_text(&self) -> Option<&str> {
2578 self.placeholder_text.as_deref()
2579 }
2580
2581 pub fn set_placeholder_text(
2582 &mut self,
2583 placeholder_text: impl Into<Arc<str>>,
2584 cx: &mut Context<Self>,
2585 ) {
2586 let placeholder_text = Some(placeholder_text.into());
2587 if self.placeholder_text != placeholder_text {
2588 self.placeholder_text = placeholder_text;
2589 cx.notify();
2590 }
2591 }
2592
2593 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2594 self.cursor_shape = cursor_shape;
2595
2596 // Disrupt blink for immediate user feedback that the cursor shape has changed
2597 self.blink_manager.update(cx, BlinkManager::show_cursor);
2598
2599 cx.notify();
2600 }
2601
2602 pub fn set_current_line_highlight(
2603 &mut self,
2604 current_line_highlight: Option<CurrentLineHighlight>,
2605 ) {
2606 self.current_line_highlight = current_line_highlight;
2607 }
2608
2609 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2610 self.collapse_matches = collapse_matches;
2611 }
2612
2613 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2614 let buffers = self.buffer.read(cx).all_buffers();
2615 let Some(project) = self.project.as_ref() else {
2616 return;
2617 };
2618 project.update(cx, |project, cx| {
2619 for buffer in buffers {
2620 self.registered_buffers
2621 .entry(buffer.read(cx).remote_id())
2622 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2623 }
2624 })
2625 }
2626
2627 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2628 if self.collapse_matches {
2629 return range.start..range.start;
2630 }
2631 range.clone()
2632 }
2633
2634 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2635 if self.display_map.read(cx).clip_at_line_ends != clip {
2636 self.display_map
2637 .update(cx, |map, _| map.clip_at_line_ends = clip);
2638 }
2639 }
2640
2641 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2642 self.input_enabled = input_enabled;
2643 }
2644
2645 pub fn set_inline_completions_hidden_for_vim_mode(
2646 &mut self,
2647 hidden: bool,
2648 window: &mut Window,
2649 cx: &mut Context<Self>,
2650 ) {
2651 if hidden != self.inline_completions_hidden_for_vim_mode {
2652 self.inline_completions_hidden_for_vim_mode = hidden;
2653 if hidden {
2654 self.update_visible_inline_completion(window, cx);
2655 } else {
2656 self.refresh_inline_completion(true, false, window, cx);
2657 }
2658 }
2659 }
2660
2661 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2662 self.menu_inline_completions_policy = value;
2663 }
2664
2665 pub fn set_autoindent(&mut self, autoindent: bool) {
2666 if autoindent {
2667 self.autoindent_mode = Some(AutoindentMode::EachLine);
2668 } else {
2669 self.autoindent_mode = None;
2670 }
2671 }
2672
2673 pub fn read_only(&self, cx: &App) -> bool {
2674 self.read_only || self.buffer.read(cx).read_only()
2675 }
2676
2677 pub fn set_read_only(&mut self, read_only: bool) {
2678 self.read_only = read_only;
2679 }
2680
2681 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2682 self.use_autoclose = autoclose;
2683 }
2684
2685 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2686 self.use_auto_surround = auto_surround;
2687 }
2688
2689 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2690 self.auto_replace_emoji_shortcode = auto_replace;
2691 }
2692
2693 pub fn toggle_edit_predictions(
2694 &mut self,
2695 _: &ToggleEditPrediction,
2696 window: &mut Window,
2697 cx: &mut Context<Self>,
2698 ) {
2699 if self.show_inline_completions_override.is_some() {
2700 self.set_show_edit_predictions(None, window, cx);
2701 } else {
2702 let show_edit_predictions = !self.edit_predictions_enabled();
2703 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2704 }
2705 }
2706
2707 pub fn set_show_edit_predictions(
2708 &mut self,
2709 show_edit_predictions: Option<bool>,
2710 window: &mut Window,
2711 cx: &mut Context<Self>,
2712 ) {
2713 self.show_inline_completions_override = show_edit_predictions;
2714 self.update_edit_prediction_settings(cx);
2715
2716 if let Some(false) = show_edit_predictions {
2717 self.discard_inline_completion(false, cx);
2718 } else {
2719 self.refresh_inline_completion(false, true, window, cx);
2720 }
2721 }
2722
2723 fn inline_completions_disabled_in_scope(
2724 &self,
2725 buffer: &Entity<Buffer>,
2726 buffer_position: language::Anchor,
2727 cx: &App,
2728 ) -> bool {
2729 let snapshot = buffer.read(cx).snapshot();
2730 let settings = snapshot.settings_at(buffer_position, cx);
2731
2732 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2733 return false;
2734 };
2735
2736 scope.override_name().map_or(false, |scope_name| {
2737 settings
2738 .edit_predictions_disabled_in
2739 .iter()
2740 .any(|s| s == scope_name)
2741 })
2742 }
2743
2744 pub fn set_use_modal_editing(&mut self, to: bool) {
2745 self.use_modal_editing = to;
2746 }
2747
2748 pub fn use_modal_editing(&self) -> bool {
2749 self.use_modal_editing
2750 }
2751
2752 fn selections_did_change(
2753 &mut self,
2754 local: bool,
2755 old_cursor_position: &Anchor,
2756 effects: SelectionEffects,
2757 window: &mut Window,
2758 cx: &mut Context<Self>,
2759 ) {
2760 window.invalidate_character_coordinates();
2761
2762 // Copy selections to primary selection buffer
2763 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2764 if local {
2765 let selections = self.selections.all::<usize>(cx);
2766 let buffer_handle = self.buffer.read(cx).read(cx);
2767
2768 let mut text = String::new();
2769 for (index, selection) in selections.iter().enumerate() {
2770 let text_for_selection = buffer_handle
2771 .text_for_range(selection.start..selection.end)
2772 .collect::<String>();
2773
2774 text.push_str(&text_for_selection);
2775 if index != selections.len() - 1 {
2776 text.push('\n');
2777 }
2778 }
2779
2780 if !text.is_empty() {
2781 cx.write_to_primary(ClipboardItem::new_string(text));
2782 }
2783 }
2784
2785 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2786 self.buffer.update(cx, |buffer, cx| {
2787 buffer.set_active_selections(
2788 &self.selections.disjoint_anchors(),
2789 self.selections.line_mode,
2790 self.cursor_shape,
2791 cx,
2792 )
2793 });
2794 }
2795 let display_map = self
2796 .display_map
2797 .update(cx, |display_map, cx| display_map.snapshot(cx));
2798 let buffer = &display_map.buffer_snapshot;
2799 if self.selections.count() == 1 {
2800 self.add_selections_state = None;
2801 }
2802 self.select_next_state = None;
2803 self.select_prev_state = None;
2804 self.select_syntax_node_history.try_clear();
2805 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2806 self.snippet_stack
2807 .invalidate(&self.selections.disjoint_anchors(), buffer);
2808 self.take_rename(false, window, cx);
2809
2810 let newest_selection = self.selections.newest_anchor();
2811 let new_cursor_position = newest_selection.head();
2812 let selection_start = newest_selection.start;
2813
2814 if effects.nav_history {
2815 self.push_to_nav_history(
2816 *old_cursor_position,
2817 Some(new_cursor_position.to_point(buffer)),
2818 false,
2819 cx,
2820 );
2821 }
2822
2823 if local {
2824 if let Some(buffer_id) = new_cursor_position.buffer_id {
2825 if !self.registered_buffers.contains_key(&buffer_id) {
2826 if let Some(project) = self.project.as_ref() {
2827 project.update(cx, |project, cx| {
2828 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2829 return;
2830 };
2831 self.registered_buffers.insert(
2832 buffer_id,
2833 project.register_buffer_with_language_servers(&buffer, cx),
2834 );
2835 })
2836 }
2837 }
2838 }
2839
2840 let mut context_menu = self.context_menu.borrow_mut();
2841 let completion_menu = match context_menu.as_ref() {
2842 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2843 Some(CodeContextMenu::CodeActions(_)) => {
2844 *context_menu = None;
2845 None
2846 }
2847 None => None,
2848 };
2849 let completion_position = completion_menu.map(|menu| menu.initial_position);
2850 drop(context_menu);
2851
2852 if effects.completions {
2853 if let Some(completion_position) = completion_position {
2854 let start_offset = selection_start.to_offset(buffer);
2855 let position_matches = start_offset == completion_position.to_offset(buffer);
2856 let continue_showing = if position_matches {
2857 if self.snippet_stack.is_empty() {
2858 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2859 } else {
2860 // Snippet choices can be shown even when the cursor is in whitespace.
2861 // Dismissing the menu when actions like backspace
2862 true
2863 }
2864 } else {
2865 false
2866 };
2867
2868 if continue_showing {
2869 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2870 } else {
2871 self.hide_context_menu(window, cx);
2872 }
2873 }
2874 }
2875
2876 hide_hover(self, cx);
2877
2878 if old_cursor_position.to_display_point(&display_map).row()
2879 != new_cursor_position.to_display_point(&display_map).row()
2880 {
2881 self.available_code_actions.take();
2882 }
2883 self.refresh_code_actions(window, cx);
2884 self.refresh_document_highlights(cx);
2885 self.refresh_selected_text_highlights(false, window, cx);
2886 refresh_matching_bracket_highlights(self, window, cx);
2887 self.update_visible_inline_completion(window, cx);
2888 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2889 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2890 self.inline_blame_popover.take();
2891 if self.git_blame_inline_enabled {
2892 self.start_inline_blame_timer(window, cx);
2893 }
2894 }
2895
2896 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2897 cx.emit(EditorEvent::SelectionsChanged { local });
2898
2899 let selections = &self.selections.disjoint;
2900 if selections.len() == 1 {
2901 cx.emit(SearchEvent::ActiveMatchChanged)
2902 }
2903 if local {
2904 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2905 let inmemory_selections = selections
2906 .iter()
2907 .map(|s| {
2908 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2909 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2910 })
2911 .collect();
2912 self.update_restoration_data(cx, |data| {
2913 data.selections = inmemory_selections;
2914 });
2915
2916 if WorkspaceSettings::get(None, cx).restore_on_startup
2917 != RestoreOnStartupBehavior::None
2918 {
2919 if let Some(workspace_id) =
2920 self.workspace.as_ref().and_then(|workspace| workspace.1)
2921 {
2922 let snapshot = self.buffer().read(cx).snapshot(cx);
2923 let selections = selections.clone();
2924 let background_executor = cx.background_executor().clone();
2925 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2926 self.serialize_selections = cx.background_spawn(async move {
2927 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2928 let db_selections = selections
2929 .iter()
2930 .map(|selection| {
2931 (
2932 selection.start.to_offset(&snapshot),
2933 selection.end.to_offset(&snapshot),
2934 )
2935 })
2936 .collect();
2937
2938 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2939 .await
2940 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2941 .log_err();
2942 });
2943 }
2944 }
2945 }
2946 }
2947
2948 cx.notify();
2949 }
2950
2951 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2952 use text::ToOffset as _;
2953 use text::ToPoint as _;
2954
2955 if self.mode.is_minimap()
2956 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2957 {
2958 return;
2959 }
2960
2961 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2962 return;
2963 };
2964
2965 let snapshot = singleton.read(cx).snapshot();
2966 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2967 let display_snapshot = display_map.snapshot(cx);
2968
2969 display_snapshot
2970 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2971 .map(|fold| {
2972 fold.range.start.text_anchor.to_point(&snapshot)
2973 ..fold.range.end.text_anchor.to_point(&snapshot)
2974 })
2975 .collect()
2976 });
2977 self.update_restoration_data(cx, |data| {
2978 data.folds = inmemory_folds;
2979 });
2980
2981 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2982 return;
2983 };
2984 let background_executor = cx.background_executor().clone();
2985 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2986 let db_folds = self.display_map.update(cx, |display_map, cx| {
2987 display_map
2988 .snapshot(cx)
2989 .folds_in_range(0..snapshot.len())
2990 .map(|fold| {
2991 (
2992 fold.range.start.text_anchor.to_offset(&snapshot),
2993 fold.range.end.text_anchor.to_offset(&snapshot),
2994 )
2995 })
2996 .collect()
2997 });
2998 self.serialize_folds = cx.background_spawn(async move {
2999 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3000 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3001 .await
3002 .with_context(|| {
3003 format!(
3004 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3005 )
3006 })
3007 .log_err();
3008 });
3009 }
3010
3011 pub fn sync_selections(
3012 &mut self,
3013 other: Entity<Editor>,
3014 cx: &mut Context<Self>,
3015 ) -> gpui::Subscription {
3016 let other_selections = other.read(cx).selections.disjoint.to_vec();
3017 self.selections.change_with(cx, |selections| {
3018 selections.select_anchors(other_selections);
3019 });
3020
3021 let other_subscription =
3022 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3023 EditorEvent::SelectionsChanged { local: true } => {
3024 let other_selections = other.read(cx).selections.disjoint.to_vec();
3025 if other_selections.is_empty() {
3026 return;
3027 }
3028 this.selections.change_with(cx, |selections| {
3029 selections.select_anchors(other_selections);
3030 });
3031 }
3032 _ => {}
3033 });
3034
3035 let this_subscription =
3036 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3037 EditorEvent::SelectionsChanged { local: true } => {
3038 let these_selections = this.selections.disjoint.to_vec();
3039 if these_selections.is_empty() {
3040 return;
3041 }
3042 other.update(cx, |other_editor, cx| {
3043 other_editor.selections.change_with(cx, |selections| {
3044 selections.select_anchors(these_selections);
3045 })
3046 });
3047 }
3048 _ => {}
3049 });
3050
3051 Subscription::join(other_subscription, this_subscription)
3052 }
3053
3054 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3055 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3056 /// effects of selection change occur at the end of the transaction.
3057 pub fn change_selections<R>(
3058 &mut self,
3059 effects: impl Into<SelectionEffects>,
3060 window: &mut Window,
3061 cx: &mut Context<Self>,
3062 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3063 ) -> R {
3064 let effects = effects.into();
3065 if let Some(state) = &mut self.deferred_selection_effects_state {
3066 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3067 state.effects.completions = effects.completions;
3068 state.effects.nav_history |= effects.nav_history;
3069 let (changed, result) = self.selections.change_with(cx, change);
3070 state.changed |= changed;
3071 return result;
3072 }
3073 let mut state = DeferredSelectionEffectsState {
3074 changed: false,
3075 effects,
3076 old_cursor_position: self.selections.newest_anchor().head(),
3077 history_entry: SelectionHistoryEntry {
3078 selections: self.selections.disjoint_anchors(),
3079 select_next_state: self.select_next_state.clone(),
3080 select_prev_state: self.select_prev_state.clone(),
3081 add_selections_state: self.add_selections_state.clone(),
3082 },
3083 };
3084 let (changed, result) = self.selections.change_with(cx, change);
3085 state.changed = state.changed || changed;
3086 if self.defer_selection_effects {
3087 self.deferred_selection_effects_state = Some(state);
3088 } else {
3089 self.apply_selection_effects(state, window, cx);
3090 }
3091 result
3092 }
3093
3094 /// Defers the effects of selection change, so that the effects of multiple calls to
3095 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3096 /// to selection history and the state of popovers based on selection position aren't
3097 /// erroneously updated.
3098 pub fn with_selection_effects_deferred<R>(
3099 &mut self,
3100 window: &mut Window,
3101 cx: &mut Context<Self>,
3102 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3103 ) -> R {
3104 let already_deferred = self.defer_selection_effects;
3105 self.defer_selection_effects = true;
3106 let result = update(self, window, cx);
3107 if !already_deferred {
3108 self.defer_selection_effects = false;
3109 if let Some(state) = self.deferred_selection_effects_state.take() {
3110 self.apply_selection_effects(state, window, cx);
3111 }
3112 }
3113 result
3114 }
3115
3116 fn apply_selection_effects(
3117 &mut self,
3118 state: DeferredSelectionEffectsState,
3119 window: &mut Window,
3120 cx: &mut Context<Self>,
3121 ) {
3122 if state.changed {
3123 self.selection_history.push(state.history_entry);
3124
3125 if let Some(autoscroll) = state.effects.scroll {
3126 self.request_autoscroll(autoscroll, cx);
3127 }
3128
3129 let old_cursor_position = &state.old_cursor_position;
3130
3131 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3132
3133 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3134 self.show_signature_help(&ShowSignatureHelp, window, cx);
3135 }
3136 }
3137 }
3138
3139 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3140 where
3141 I: IntoIterator<Item = (Range<S>, T)>,
3142 S: ToOffset,
3143 T: Into<Arc<str>>,
3144 {
3145 if self.read_only(cx) {
3146 return;
3147 }
3148
3149 self.buffer
3150 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3151 }
3152
3153 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3154 where
3155 I: IntoIterator<Item = (Range<S>, T)>,
3156 S: ToOffset,
3157 T: Into<Arc<str>>,
3158 {
3159 if self.read_only(cx) {
3160 return;
3161 }
3162
3163 self.buffer.update(cx, |buffer, cx| {
3164 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3165 });
3166 }
3167
3168 pub fn edit_with_block_indent<I, S, T>(
3169 &mut self,
3170 edits: I,
3171 original_indent_columns: Vec<Option<u32>>,
3172 cx: &mut Context<Self>,
3173 ) where
3174 I: IntoIterator<Item = (Range<S>, T)>,
3175 S: ToOffset,
3176 T: Into<Arc<str>>,
3177 {
3178 if self.read_only(cx) {
3179 return;
3180 }
3181
3182 self.buffer.update(cx, |buffer, cx| {
3183 buffer.edit(
3184 edits,
3185 Some(AutoindentMode::Block {
3186 original_indent_columns,
3187 }),
3188 cx,
3189 )
3190 });
3191 }
3192
3193 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3194 self.hide_context_menu(window, cx);
3195
3196 match phase {
3197 SelectPhase::Begin {
3198 position,
3199 add,
3200 click_count,
3201 } => self.begin_selection(position, add, click_count, window, cx),
3202 SelectPhase::BeginColumnar {
3203 position,
3204 goal_column,
3205 reset,
3206 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3207 SelectPhase::Extend {
3208 position,
3209 click_count,
3210 } => self.extend_selection(position, click_count, window, cx),
3211 SelectPhase::Update {
3212 position,
3213 goal_column,
3214 scroll_delta,
3215 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3216 SelectPhase::End => self.end_selection(window, cx),
3217 }
3218 }
3219
3220 fn extend_selection(
3221 &mut self,
3222 position: DisplayPoint,
3223 click_count: usize,
3224 window: &mut Window,
3225 cx: &mut Context<Self>,
3226 ) {
3227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3228 let tail = self.selections.newest::<usize>(cx).tail();
3229 self.begin_selection(position, false, click_count, window, cx);
3230
3231 let position = position.to_offset(&display_map, Bias::Left);
3232 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3233
3234 let mut pending_selection = self
3235 .selections
3236 .pending_anchor()
3237 .expect("extend_selection not called with pending selection");
3238 if position >= tail {
3239 pending_selection.start = tail_anchor;
3240 } else {
3241 pending_selection.end = tail_anchor;
3242 pending_selection.reversed = true;
3243 }
3244
3245 let mut pending_mode = self.selections.pending_mode().unwrap();
3246 match &mut pending_mode {
3247 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3248 _ => {}
3249 }
3250
3251 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3252 SelectionEffects::scroll(Autoscroll::fit())
3253 } else {
3254 SelectionEffects::no_scroll()
3255 };
3256
3257 self.change_selections(effects, window, cx, |s| {
3258 s.set_pending(pending_selection, pending_mode)
3259 });
3260 }
3261
3262 fn begin_selection(
3263 &mut self,
3264 position: DisplayPoint,
3265 add: bool,
3266 click_count: usize,
3267 window: &mut Window,
3268 cx: &mut Context<Self>,
3269 ) {
3270 if !self.focus_handle.is_focused(window) {
3271 self.last_focused_descendant = None;
3272 window.focus(&self.focus_handle);
3273 }
3274
3275 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3276 let buffer = &display_map.buffer_snapshot;
3277 let position = display_map.clip_point(position, Bias::Left);
3278
3279 let start;
3280 let end;
3281 let mode;
3282 let mut auto_scroll;
3283 match click_count {
3284 1 => {
3285 start = buffer.anchor_before(position.to_point(&display_map));
3286 end = start;
3287 mode = SelectMode::Character;
3288 auto_scroll = true;
3289 }
3290 2 => {
3291 let range = movement::surrounding_word(&display_map, position);
3292 start = buffer.anchor_before(range.start.to_point(&display_map));
3293 end = buffer.anchor_before(range.end.to_point(&display_map));
3294 mode = SelectMode::Word(start..end);
3295 auto_scroll = true;
3296 }
3297 3 => {
3298 let position = display_map
3299 .clip_point(position, Bias::Left)
3300 .to_point(&display_map);
3301 let line_start = display_map.prev_line_boundary(position).0;
3302 let next_line_start = buffer.clip_point(
3303 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3304 Bias::Left,
3305 );
3306 start = buffer.anchor_before(line_start);
3307 end = buffer.anchor_before(next_line_start);
3308 mode = SelectMode::Line(start..end);
3309 auto_scroll = true;
3310 }
3311 _ => {
3312 start = buffer.anchor_before(0);
3313 end = buffer.anchor_before(buffer.len());
3314 mode = SelectMode::All;
3315 auto_scroll = false;
3316 }
3317 }
3318 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3319
3320 let point_to_delete: Option<usize> = {
3321 let selected_points: Vec<Selection<Point>> =
3322 self.selections.disjoint_in_range(start..end, cx);
3323
3324 if !add || click_count > 1 {
3325 None
3326 } else if !selected_points.is_empty() {
3327 Some(selected_points[0].id)
3328 } else {
3329 let clicked_point_already_selected =
3330 self.selections.disjoint.iter().find(|selection| {
3331 selection.start.to_point(buffer) == start.to_point(buffer)
3332 || selection.end.to_point(buffer) == end.to_point(buffer)
3333 });
3334
3335 clicked_point_already_selected.map(|selection| selection.id)
3336 }
3337 };
3338
3339 let selections_count = self.selections.count();
3340
3341 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3342 if let Some(point_to_delete) = point_to_delete {
3343 s.delete(point_to_delete);
3344
3345 if selections_count == 1 {
3346 s.set_pending_anchor_range(start..end, mode);
3347 }
3348 } else {
3349 if !add {
3350 s.clear_disjoint();
3351 }
3352
3353 s.set_pending_anchor_range(start..end, mode);
3354 }
3355 });
3356 }
3357
3358 fn begin_columnar_selection(
3359 &mut self,
3360 position: DisplayPoint,
3361 goal_column: u32,
3362 reset: bool,
3363 window: &mut Window,
3364 cx: &mut Context<Self>,
3365 ) {
3366 if !self.focus_handle.is_focused(window) {
3367 self.last_focused_descendant = None;
3368 window.focus(&self.focus_handle);
3369 }
3370
3371 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3372
3373 if reset {
3374 let pointer_position = display_map
3375 .buffer_snapshot
3376 .anchor_before(position.to_point(&display_map));
3377
3378 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3379 s.clear_disjoint();
3380 s.set_pending_anchor_range(
3381 pointer_position..pointer_position,
3382 SelectMode::Character,
3383 );
3384 });
3385 if position.column() != goal_column {
3386 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3387 } else {
3388 self.columnar_display_point = None;
3389 }
3390 }
3391
3392 let tail = self.selections.newest::<Point>(cx).tail();
3393 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3394
3395 if !reset {
3396 self.columnar_display_point = None;
3397 self.select_columns(
3398 tail.to_display_point(&display_map),
3399 position,
3400 goal_column,
3401 &display_map,
3402 window,
3403 cx,
3404 );
3405 }
3406 }
3407
3408 fn update_selection(
3409 &mut self,
3410 position: DisplayPoint,
3411 goal_column: u32,
3412 scroll_delta: gpui::Point<f32>,
3413 window: &mut Window,
3414 cx: &mut Context<Self>,
3415 ) {
3416 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3417
3418 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3419 let tail = self
3420 .columnar_display_point
3421 .unwrap_or_else(|| tail.to_display_point(&display_map));
3422 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3423 } else if let Some(mut pending) = self.selections.pending_anchor() {
3424 let buffer = self.buffer.read(cx).snapshot(cx);
3425 let head;
3426 let tail;
3427 let mode = self.selections.pending_mode().unwrap();
3428 match &mode {
3429 SelectMode::Character => {
3430 head = position.to_point(&display_map);
3431 tail = pending.tail().to_point(&buffer);
3432 }
3433 SelectMode::Word(original_range) => {
3434 let original_display_range = original_range.start.to_display_point(&display_map)
3435 ..original_range.end.to_display_point(&display_map);
3436 let original_buffer_range = original_display_range.start.to_point(&display_map)
3437 ..original_display_range.end.to_point(&display_map);
3438 if movement::is_inside_word(&display_map, position)
3439 || original_display_range.contains(&position)
3440 {
3441 let word_range = movement::surrounding_word(&display_map, position);
3442 if word_range.start < original_display_range.start {
3443 head = word_range.start.to_point(&display_map);
3444 } else {
3445 head = word_range.end.to_point(&display_map);
3446 }
3447 } else {
3448 head = position.to_point(&display_map);
3449 }
3450
3451 if head <= original_buffer_range.start {
3452 tail = original_buffer_range.end;
3453 } else {
3454 tail = original_buffer_range.start;
3455 }
3456 }
3457 SelectMode::Line(original_range) => {
3458 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3459
3460 let position = display_map
3461 .clip_point(position, Bias::Left)
3462 .to_point(&display_map);
3463 let line_start = display_map.prev_line_boundary(position).0;
3464 let next_line_start = buffer.clip_point(
3465 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3466 Bias::Left,
3467 );
3468
3469 if line_start < original_range.start {
3470 head = line_start
3471 } else {
3472 head = next_line_start
3473 }
3474
3475 if head <= original_range.start {
3476 tail = original_range.end;
3477 } else {
3478 tail = original_range.start;
3479 }
3480 }
3481 SelectMode::All => {
3482 return;
3483 }
3484 };
3485
3486 if head < tail {
3487 pending.start = buffer.anchor_before(head);
3488 pending.end = buffer.anchor_before(tail);
3489 pending.reversed = true;
3490 } else {
3491 pending.start = buffer.anchor_before(tail);
3492 pending.end = buffer.anchor_before(head);
3493 pending.reversed = false;
3494 }
3495
3496 self.change_selections(None, window, cx, |s| {
3497 s.set_pending(pending, mode);
3498 });
3499 } else {
3500 log::error!("update_selection dispatched with no pending selection");
3501 return;
3502 }
3503
3504 self.apply_scroll_delta(scroll_delta, window, cx);
3505 cx.notify();
3506 }
3507
3508 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3509 self.columnar_selection_tail.take();
3510 if self.selections.pending_anchor().is_some() {
3511 let selections = self.selections.all::<usize>(cx);
3512 self.change_selections(None, window, cx, |s| {
3513 s.select(selections);
3514 s.clear_pending();
3515 });
3516 }
3517 }
3518
3519 fn select_columns(
3520 &mut self,
3521 tail: DisplayPoint,
3522 head: DisplayPoint,
3523 goal_column: u32,
3524 display_map: &DisplaySnapshot,
3525 window: &mut Window,
3526 cx: &mut Context<Self>,
3527 ) {
3528 let start_row = cmp::min(tail.row(), head.row());
3529 let end_row = cmp::max(tail.row(), head.row());
3530 let start_column = cmp::min(tail.column(), goal_column);
3531 let end_column = cmp::max(tail.column(), goal_column);
3532 let reversed = start_column < tail.column();
3533
3534 let selection_ranges = (start_row.0..=end_row.0)
3535 .map(DisplayRow)
3536 .filter_map(|row| {
3537 if !display_map.is_block_line(row) {
3538 let start = display_map
3539 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3540 .to_point(display_map);
3541 let end = display_map
3542 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3543 .to_point(display_map);
3544 if reversed {
3545 Some(end..start)
3546 } else {
3547 Some(start..end)
3548 }
3549 } else {
3550 None
3551 }
3552 })
3553 .collect::<Vec<_>>();
3554
3555 let mut non_empty_ranges = selection_ranges
3556 .iter()
3557 .filter(|selection_range| selection_range.start != selection_range.end)
3558 .peekable();
3559
3560 let ranges = if non_empty_ranges.peek().is_some() {
3561 non_empty_ranges.cloned().collect()
3562 } else {
3563 selection_ranges
3564 };
3565
3566 self.change_selections(None, window, cx, |s| {
3567 s.select_ranges(ranges);
3568 });
3569 cx.notify();
3570 }
3571
3572 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3573 self.selections
3574 .all_adjusted(cx)
3575 .iter()
3576 .any(|selection| !selection.is_empty())
3577 }
3578
3579 pub fn has_pending_nonempty_selection(&self) -> bool {
3580 let pending_nonempty_selection = match self.selections.pending_anchor() {
3581 Some(Selection { start, end, .. }) => start != end,
3582 None => false,
3583 };
3584
3585 pending_nonempty_selection
3586 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3587 }
3588
3589 pub fn has_pending_selection(&self) -> bool {
3590 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3591 }
3592
3593 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3594 self.selection_mark_mode = false;
3595 self.selection_drag_state = SelectionDragState::None;
3596
3597 if self.clear_expanded_diff_hunks(cx) {
3598 cx.notify();
3599 return;
3600 }
3601 if self.dismiss_menus_and_popups(true, window, cx) {
3602 return;
3603 }
3604
3605 if self.mode.is_full()
3606 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3607 {
3608 return;
3609 }
3610
3611 cx.propagate();
3612 }
3613
3614 pub fn dismiss_menus_and_popups(
3615 &mut self,
3616 is_user_requested: bool,
3617 window: &mut Window,
3618 cx: &mut Context<Self>,
3619 ) -> bool {
3620 if self.take_rename(false, window, cx).is_some() {
3621 return true;
3622 }
3623
3624 if hide_hover(self, cx) {
3625 return true;
3626 }
3627
3628 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3629 return true;
3630 }
3631
3632 if self.hide_context_menu(window, cx).is_some() {
3633 return true;
3634 }
3635
3636 if self.mouse_context_menu.take().is_some() {
3637 return true;
3638 }
3639
3640 if is_user_requested && self.discard_inline_completion(true, cx) {
3641 return true;
3642 }
3643
3644 if self.snippet_stack.pop().is_some() {
3645 return true;
3646 }
3647
3648 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3649 self.dismiss_diagnostics(cx);
3650 return true;
3651 }
3652
3653 false
3654 }
3655
3656 fn linked_editing_ranges_for(
3657 &self,
3658 selection: Range<text::Anchor>,
3659 cx: &App,
3660 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3661 if self.linked_edit_ranges.is_empty() {
3662 return None;
3663 }
3664 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3665 selection.end.buffer_id.and_then(|end_buffer_id| {
3666 if selection.start.buffer_id != Some(end_buffer_id) {
3667 return None;
3668 }
3669 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3670 let snapshot = buffer.read(cx).snapshot();
3671 self.linked_edit_ranges
3672 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3673 .map(|ranges| (ranges, snapshot, buffer))
3674 })?;
3675 use text::ToOffset as TO;
3676 // find offset from the start of current range to current cursor position
3677 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3678
3679 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3680 let start_difference = start_offset - start_byte_offset;
3681 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3682 let end_difference = end_offset - start_byte_offset;
3683 // Current range has associated linked ranges.
3684 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3685 for range in linked_ranges.iter() {
3686 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3687 let end_offset = start_offset + end_difference;
3688 let start_offset = start_offset + start_difference;
3689 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3690 continue;
3691 }
3692 if self.selections.disjoint_anchor_ranges().any(|s| {
3693 if s.start.buffer_id != selection.start.buffer_id
3694 || s.end.buffer_id != selection.end.buffer_id
3695 {
3696 return false;
3697 }
3698 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3699 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3700 }) {
3701 continue;
3702 }
3703 let start = buffer_snapshot.anchor_after(start_offset);
3704 let end = buffer_snapshot.anchor_after(end_offset);
3705 linked_edits
3706 .entry(buffer.clone())
3707 .or_default()
3708 .push(start..end);
3709 }
3710 Some(linked_edits)
3711 }
3712
3713 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3714 let text: Arc<str> = text.into();
3715
3716 if self.read_only(cx) {
3717 return;
3718 }
3719
3720 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3721
3722 let selections = self.selections.all_adjusted(cx);
3723 let mut bracket_inserted = false;
3724 let mut edits = Vec::new();
3725 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3726 let mut new_selections = Vec::with_capacity(selections.len());
3727 let mut new_autoclose_regions = Vec::new();
3728 let snapshot = self.buffer.read(cx).read(cx);
3729 let mut clear_linked_edit_ranges = false;
3730
3731 for (selection, autoclose_region) in
3732 self.selections_with_autoclose_regions(selections, &snapshot)
3733 {
3734 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3735 // Determine if the inserted text matches the opening or closing
3736 // bracket of any of this language's bracket pairs.
3737 let mut bracket_pair = None;
3738 let mut is_bracket_pair_start = false;
3739 let mut is_bracket_pair_end = false;
3740 if !text.is_empty() {
3741 let mut bracket_pair_matching_end = None;
3742 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3743 // and they are removing the character that triggered IME popup.
3744 for (pair, enabled) in scope.brackets() {
3745 if !pair.close && !pair.surround {
3746 continue;
3747 }
3748
3749 if enabled && pair.start.ends_with(text.as_ref()) {
3750 let prefix_len = pair.start.len() - text.len();
3751 let preceding_text_matches_prefix = prefix_len == 0
3752 || (selection.start.column >= (prefix_len as u32)
3753 && snapshot.contains_str_at(
3754 Point::new(
3755 selection.start.row,
3756 selection.start.column - (prefix_len as u32),
3757 ),
3758 &pair.start[..prefix_len],
3759 ));
3760 if preceding_text_matches_prefix {
3761 bracket_pair = Some(pair.clone());
3762 is_bracket_pair_start = true;
3763 break;
3764 }
3765 }
3766 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3767 {
3768 // take first bracket pair matching end, but don't break in case a later bracket
3769 // pair matches start
3770 bracket_pair_matching_end = Some(pair.clone());
3771 }
3772 }
3773 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3774 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3775 is_bracket_pair_end = true;
3776 }
3777 }
3778
3779 if let Some(bracket_pair) = bracket_pair {
3780 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3781 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3782 let auto_surround =
3783 self.use_auto_surround && snapshot_settings.use_auto_surround;
3784 if selection.is_empty() {
3785 if is_bracket_pair_start {
3786 // If the inserted text is a suffix of an opening bracket and the
3787 // selection is preceded by the rest of the opening bracket, then
3788 // insert the closing bracket.
3789 let following_text_allows_autoclose = snapshot
3790 .chars_at(selection.start)
3791 .next()
3792 .map_or(true, |c| scope.should_autoclose_before(c));
3793
3794 let preceding_text_allows_autoclose = selection.start.column == 0
3795 || snapshot.reversed_chars_at(selection.start).next().map_or(
3796 true,
3797 |c| {
3798 bracket_pair.start != bracket_pair.end
3799 || !snapshot
3800 .char_classifier_at(selection.start)
3801 .is_word(c)
3802 },
3803 );
3804
3805 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3806 && bracket_pair.start.len() == 1
3807 {
3808 let target = bracket_pair.start.chars().next().unwrap();
3809 let current_line_count = snapshot
3810 .reversed_chars_at(selection.start)
3811 .take_while(|&c| c != '\n')
3812 .filter(|&c| c == target)
3813 .count();
3814 current_line_count % 2 == 1
3815 } else {
3816 false
3817 };
3818
3819 if autoclose
3820 && bracket_pair.close
3821 && following_text_allows_autoclose
3822 && preceding_text_allows_autoclose
3823 && !is_closing_quote
3824 {
3825 let anchor = snapshot.anchor_before(selection.end);
3826 new_selections.push((selection.map(|_| anchor), text.len()));
3827 new_autoclose_regions.push((
3828 anchor,
3829 text.len(),
3830 selection.id,
3831 bracket_pair.clone(),
3832 ));
3833 edits.push((
3834 selection.range(),
3835 format!("{}{}", text, bracket_pair.end).into(),
3836 ));
3837 bracket_inserted = true;
3838 continue;
3839 }
3840 }
3841
3842 if let Some(region) = autoclose_region {
3843 // If the selection is followed by an auto-inserted closing bracket,
3844 // then don't insert that closing bracket again; just move the selection
3845 // past the closing bracket.
3846 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3847 && text.as_ref() == region.pair.end.as_str();
3848 if should_skip {
3849 let anchor = snapshot.anchor_after(selection.end);
3850 new_selections
3851 .push((selection.map(|_| anchor), region.pair.end.len()));
3852 continue;
3853 }
3854 }
3855
3856 let always_treat_brackets_as_autoclosed = snapshot
3857 .language_settings_at(selection.start, cx)
3858 .always_treat_brackets_as_autoclosed;
3859 if always_treat_brackets_as_autoclosed
3860 && is_bracket_pair_end
3861 && snapshot.contains_str_at(selection.end, text.as_ref())
3862 {
3863 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3864 // and the inserted text is a closing bracket and the selection is followed
3865 // by the closing bracket then move the selection past the closing bracket.
3866 let anchor = snapshot.anchor_after(selection.end);
3867 new_selections.push((selection.map(|_| anchor), text.len()));
3868 continue;
3869 }
3870 }
3871 // If an opening bracket is 1 character long and is typed while
3872 // text is selected, then surround that text with the bracket pair.
3873 else if auto_surround
3874 && bracket_pair.surround
3875 && is_bracket_pair_start
3876 && bracket_pair.start.chars().count() == 1
3877 {
3878 edits.push((selection.start..selection.start, text.clone()));
3879 edits.push((
3880 selection.end..selection.end,
3881 bracket_pair.end.as_str().into(),
3882 ));
3883 bracket_inserted = true;
3884 new_selections.push((
3885 Selection {
3886 id: selection.id,
3887 start: snapshot.anchor_after(selection.start),
3888 end: snapshot.anchor_before(selection.end),
3889 reversed: selection.reversed,
3890 goal: selection.goal,
3891 },
3892 0,
3893 ));
3894 continue;
3895 }
3896 }
3897 }
3898
3899 if self.auto_replace_emoji_shortcode
3900 && selection.is_empty()
3901 && text.as_ref().ends_with(':')
3902 {
3903 if let Some(possible_emoji_short_code) =
3904 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3905 {
3906 if !possible_emoji_short_code.is_empty() {
3907 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3908 let emoji_shortcode_start = Point::new(
3909 selection.start.row,
3910 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3911 );
3912
3913 // Remove shortcode from buffer
3914 edits.push((
3915 emoji_shortcode_start..selection.start,
3916 "".to_string().into(),
3917 ));
3918 new_selections.push((
3919 Selection {
3920 id: selection.id,
3921 start: snapshot.anchor_after(emoji_shortcode_start),
3922 end: snapshot.anchor_before(selection.start),
3923 reversed: selection.reversed,
3924 goal: selection.goal,
3925 },
3926 0,
3927 ));
3928
3929 // Insert emoji
3930 let selection_start_anchor = snapshot.anchor_after(selection.start);
3931 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3932 edits.push((selection.start..selection.end, emoji.to_string().into()));
3933
3934 continue;
3935 }
3936 }
3937 }
3938 }
3939
3940 // If not handling any auto-close operation, then just replace the selected
3941 // text with the given input and move the selection to the end of the
3942 // newly inserted text.
3943 let anchor = snapshot.anchor_after(selection.end);
3944 if !self.linked_edit_ranges.is_empty() {
3945 let start_anchor = snapshot.anchor_before(selection.start);
3946
3947 let is_word_char = text.chars().next().map_or(true, |char| {
3948 let classifier = snapshot
3949 .char_classifier_at(start_anchor.to_offset(&snapshot))
3950 .ignore_punctuation(true);
3951 classifier.is_word(char)
3952 });
3953
3954 if is_word_char {
3955 if let Some(ranges) = self
3956 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3957 {
3958 for (buffer, edits) in ranges {
3959 linked_edits
3960 .entry(buffer.clone())
3961 .or_default()
3962 .extend(edits.into_iter().map(|range| (range, text.clone())));
3963 }
3964 }
3965 } else {
3966 clear_linked_edit_ranges = true;
3967 }
3968 }
3969
3970 new_selections.push((selection.map(|_| anchor), 0));
3971 edits.push((selection.start..selection.end, text.clone()));
3972 }
3973
3974 drop(snapshot);
3975
3976 self.transact(window, cx, |this, window, cx| {
3977 if clear_linked_edit_ranges {
3978 this.linked_edit_ranges.clear();
3979 }
3980 let initial_buffer_versions =
3981 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3982
3983 this.buffer.update(cx, |buffer, cx| {
3984 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3985 });
3986 for (buffer, edits) in linked_edits {
3987 buffer.update(cx, |buffer, cx| {
3988 let snapshot = buffer.snapshot();
3989 let edits = edits
3990 .into_iter()
3991 .map(|(range, text)| {
3992 use text::ToPoint as TP;
3993 let end_point = TP::to_point(&range.end, &snapshot);
3994 let start_point = TP::to_point(&range.start, &snapshot);
3995 (start_point..end_point, text)
3996 })
3997 .sorted_by_key(|(range, _)| range.start);
3998 buffer.edit(edits, None, cx);
3999 })
4000 }
4001 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4002 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4003 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4004 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4005 .zip(new_selection_deltas)
4006 .map(|(selection, delta)| Selection {
4007 id: selection.id,
4008 start: selection.start + delta,
4009 end: selection.end + delta,
4010 reversed: selection.reversed,
4011 goal: SelectionGoal::None,
4012 })
4013 .collect::<Vec<_>>();
4014
4015 let mut i = 0;
4016 for (position, delta, selection_id, pair) in new_autoclose_regions {
4017 let position = position.to_offset(&map.buffer_snapshot) + delta;
4018 let start = map.buffer_snapshot.anchor_before(position);
4019 let end = map.buffer_snapshot.anchor_after(position);
4020 while let Some(existing_state) = this.autoclose_regions.get(i) {
4021 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4022 Ordering::Less => i += 1,
4023 Ordering::Greater => break,
4024 Ordering::Equal => {
4025 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4026 Ordering::Less => i += 1,
4027 Ordering::Equal => break,
4028 Ordering::Greater => break,
4029 }
4030 }
4031 }
4032 }
4033 this.autoclose_regions.insert(
4034 i,
4035 AutocloseRegion {
4036 selection_id,
4037 range: start..end,
4038 pair,
4039 },
4040 );
4041 }
4042
4043 let had_active_inline_completion = this.has_active_inline_completion();
4044 this.change_selections(
4045 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4046 window,
4047 cx,
4048 |s| s.select(new_selections),
4049 );
4050
4051 if !bracket_inserted {
4052 if let Some(on_type_format_task) =
4053 this.trigger_on_type_formatting(text.to_string(), window, cx)
4054 {
4055 on_type_format_task.detach_and_log_err(cx);
4056 }
4057 }
4058
4059 let editor_settings = EditorSettings::get_global(cx);
4060 if bracket_inserted
4061 && (editor_settings.auto_signature_help
4062 || editor_settings.show_signature_help_after_edits)
4063 {
4064 this.show_signature_help(&ShowSignatureHelp, window, cx);
4065 }
4066
4067 let trigger_in_words =
4068 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4069 if this.hard_wrap.is_some() {
4070 let latest: Range<Point> = this.selections.newest(cx).range();
4071 if latest.is_empty()
4072 && this
4073 .buffer()
4074 .read(cx)
4075 .snapshot(cx)
4076 .line_len(MultiBufferRow(latest.start.row))
4077 == latest.start.column
4078 {
4079 this.rewrap_impl(
4080 RewrapOptions {
4081 override_language_settings: true,
4082 preserve_existing_whitespace: true,
4083 },
4084 cx,
4085 )
4086 }
4087 }
4088 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4089 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4090 this.refresh_inline_completion(true, false, window, cx);
4091 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4092 });
4093 }
4094
4095 fn find_possible_emoji_shortcode_at_position(
4096 snapshot: &MultiBufferSnapshot,
4097 position: Point,
4098 ) -> Option<String> {
4099 let mut chars = Vec::new();
4100 let mut found_colon = false;
4101 for char in snapshot.reversed_chars_at(position).take(100) {
4102 // Found a possible emoji shortcode in the middle of the buffer
4103 if found_colon {
4104 if char.is_whitespace() {
4105 chars.reverse();
4106 return Some(chars.iter().collect());
4107 }
4108 // If the previous character is not a whitespace, we are in the middle of a word
4109 // and we only want to complete the shortcode if the word is made up of other emojis
4110 let mut containing_word = String::new();
4111 for ch in snapshot
4112 .reversed_chars_at(position)
4113 .skip(chars.len() + 1)
4114 .take(100)
4115 {
4116 if ch.is_whitespace() {
4117 break;
4118 }
4119 containing_word.push(ch);
4120 }
4121 let containing_word = containing_word.chars().rev().collect::<String>();
4122 if util::word_consists_of_emojis(containing_word.as_str()) {
4123 chars.reverse();
4124 return Some(chars.iter().collect());
4125 }
4126 }
4127
4128 if char.is_whitespace() || !char.is_ascii() {
4129 return None;
4130 }
4131 if char == ':' {
4132 found_colon = true;
4133 } else {
4134 chars.push(char);
4135 }
4136 }
4137 // Found a possible emoji shortcode at the beginning of the buffer
4138 chars.reverse();
4139 Some(chars.iter().collect())
4140 }
4141
4142 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4143 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4144 self.transact(window, cx, |this, window, cx| {
4145 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4146 let selections = this.selections.all::<usize>(cx);
4147 let multi_buffer = this.buffer.read(cx);
4148 let buffer = multi_buffer.snapshot(cx);
4149 selections
4150 .iter()
4151 .map(|selection| {
4152 let start_point = selection.start.to_point(&buffer);
4153 let mut existing_indent =
4154 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4155 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4156 let start = selection.start;
4157 let end = selection.end;
4158 let selection_is_empty = start == end;
4159 let language_scope = buffer.language_scope_at(start);
4160 let (
4161 comment_delimiter,
4162 doc_delimiter,
4163 insert_extra_newline,
4164 indent_on_newline,
4165 indent_on_extra_newline,
4166 ) = if let Some(language) = &language_scope {
4167 let mut insert_extra_newline =
4168 insert_extra_newline_brackets(&buffer, start..end, language)
4169 || insert_extra_newline_tree_sitter(&buffer, start..end);
4170
4171 // Comment extension on newline is allowed only for cursor selections
4172 let comment_delimiter = maybe!({
4173 if !selection_is_empty {
4174 return None;
4175 }
4176
4177 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4178 return None;
4179 }
4180
4181 let delimiters = language.line_comment_prefixes();
4182 let max_len_of_delimiter =
4183 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4184 let (snapshot, range) =
4185 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4186
4187 let num_of_whitespaces = snapshot
4188 .chars_for_range(range.clone())
4189 .take_while(|c| c.is_whitespace())
4190 .count();
4191 let comment_candidate = snapshot
4192 .chars_for_range(range)
4193 .skip(num_of_whitespaces)
4194 .take(max_len_of_delimiter)
4195 .collect::<String>();
4196 let (delimiter, trimmed_len) = delimiters
4197 .iter()
4198 .filter_map(|delimiter| {
4199 let prefix = delimiter.trim_end();
4200 if comment_candidate.starts_with(prefix) {
4201 Some((delimiter, prefix.len()))
4202 } else {
4203 None
4204 }
4205 })
4206 .max_by_key(|(_, len)| *len)?;
4207
4208 let cursor_is_placed_after_comment_marker =
4209 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4210 if cursor_is_placed_after_comment_marker {
4211 Some(delimiter.clone())
4212 } else {
4213 None
4214 }
4215 });
4216
4217 let mut indent_on_newline = IndentSize::spaces(0);
4218 let mut indent_on_extra_newline = IndentSize::spaces(0);
4219
4220 let doc_delimiter = maybe!({
4221 if !selection_is_empty {
4222 return None;
4223 }
4224
4225 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4226 return None;
4227 }
4228
4229 let DocumentationConfig {
4230 start: start_tag,
4231 end: end_tag,
4232 prefix: delimiter,
4233 tab_size: len,
4234 } = language.documentation()?;
4235
4236 let is_within_block_comment = buffer
4237 .language_scope_at(start_point)
4238 .is_some_and(|scope| scope.override_name() == Some("comment"));
4239 if !is_within_block_comment {
4240 return None;
4241 }
4242
4243 let (snapshot, range) =
4244 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4245
4246 let num_of_whitespaces = snapshot
4247 .chars_for_range(range.clone())
4248 .take_while(|c| c.is_whitespace())
4249 .count();
4250
4251 // 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.
4252 let column = start_point.column;
4253 let cursor_is_after_start_tag = {
4254 let start_tag_len = start_tag.len();
4255 let start_tag_line = snapshot
4256 .chars_for_range(range.clone())
4257 .skip(num_of_whitespaces)
4258 .take(start_tag_len)
4259 .collect::<String>();
4260 if start_tag_line.starts_with(start_tag.as_ref()) {
4261 num_of_whitespaces + start_tag_len <= column as usize
4262 } else {
4263 false
4264 }
4265 };
4266
4267 let cursor_is_after_delimiter = {
4268 let delimiter_trim = delimiter.trim_end();
4269 let delimiter_line = snapshot
4270 .chars_for_range(range.clone())
4271 .skip(num_of_whitespaces)
4272 .take(delimiter_trim.len())
4273 .collect::<String>();
4274 if delimiter_line.starts_with(delimiter_trim) {
4275 num_of_whitespaces + delimiter_trim.len() <= column as usize
4276 } else {
4277 false
4278 }
4279 };
4280
4281 let cursor_is_before_end_tag_if_exists = {
4282 let mut char_position = 0u32;
4283 let mut end_tag_offset = None;
4284
4285 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4286 if let Some(byte_pos) = chunk.find(&**end_tag) {
4287 let chars_before_match =
4288 chunk[..byte_pos].chars().count() as u32;
4289 end_tag_offset =
4290 Some(char_position + chars_before_match);
4291 break 'outer;
4292 }
4293 char_position += chunk.chars().count() as u32;
4294 }
4295
4296 if let Some(end_tag_offset) = end_tag_offset {
4297 let cursor_is_before_end_tag = column <= end_tag_offset;
4298 if cursor_is_after_start_tag {
4299 if cursor_is_before_end_tag {
4300 insert_extra_newline = true;
4301 }
4302 let cursor_is_at_start_of_end_tag =
4303 column == end_tag_offset;
4304 if cursor_is_at_start_of_end_tag {
4305 indent_on_extra_newline.len = (*len).into();
4306 }
4307 }
4308 cursor_is_before_end_tag
4309 } else {
4310 true
4311 }
4312 };
4313
4314 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4315 && cursor_is_before_end_tag_if_exists
4316 {
4317 if cursor_is_after_start_tag {
4318 indent_on_newline.len = (*len).into();
4319 }
4320 Some(delimiter.clone())
4321 } else {
4322 None
4323 }
4324 });
4325
4326 (
4327 comment_delimiter,
4328 doc_delimiter,
4329 insert_extra_newline,
4330 indent_on_newline,
4331 indent_on_extra_newline,
4332 )
4333 } else {
4334 (
4335 None,
4336 None,
4337 false,
4338 IndentSize::default(),
4339 IndentSize::default(),
4340 )
4341 };
4342
4343 let prevent_auto_indent = doc_delimiter.is_some();
4344 let delimiter = comment_delimiter.or(doc_delimiter);
4345
4346 let capacity_for_delimiter =
4347 delimiter.as_deref().map(str::len).unwrap_or_default();
4348 let mut new_text = String::with_capacity(
4349 1 + capacity_for_delimiter
4350 + existing_indent.len as usize
4351 + indent_on_newline.len as usize
4352 + indent_on_extra_newline.len as usize,
4353 );
4354 new_text.push('\n');
4355 new_text.extend(existing_indent.chars());
4356 new_text.extend(indent_on_newline.chars());
4357
4358 if let Some(delimiter) = &delimiter {
4359 new_text.push_str(delimiter);
4360 }
4361
4362 if insert_extra_newline {
4363 new_text.push('\n');
4364 new_text.extend(existing_indent.chars());
4365 new_text.extend(indent_on_extra_newline.chars());
4366 }
4367
4368 let anchor = buffer.anchor_after(end);
4369 let new_selection = selection.map(|_| anchor);
4370 (
4371 ((start..end, new_text), prevent_auto_indent),
4372 (insert_extra_newline, new_selection),
4373 )
4374 })
4375 .unzip()
4376 };
4377
4378 let mut auto_indent_edits = Vec::new();
4379 let mut edits = Vec::new();
4380 for (edit, prevent_auto_indent) in edits_with_flags {
4381 if prevent_auto_indent {
4382 edits.push(edit);
4383 } else {
4384 auto_indent_edits.push(edit);
4385 }
4386 }
4387 if !edits.is_empty() {
4388 this.edit(edits, cx);
4389 }
4390 if !auto_indent_edits.is_empty() {
4391 this.edit_with_autoindent(auto_indent_edits, cx);
4392 }
4393
4394 let buffer = this.buffer.read(cx).snapshot(cx);
4395 let new_selections = selection_info
4396 .into_iter()
4397 .map(|(extra_newline_inserted, new_selection)| {
4398 let mut cursor = new_selection.end.to_point(&buffer);
4399 if extra_newline_inserted {
4400 cursor.row -= 1;
4401 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4402 }
4403 new_selection.map(|_| cursor)
4404 })
4405 .collect();
4406
4407 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4408 s.select(new_selections)
4409 });
4410 this.refresh_inline_completion(true, false, window, cx);
4411 });
4412 }
4413
4414 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4415 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4416
4417 let buffer = self.buffer.read(cx);
4418 let snapshot = buffer.snapshot(cx);
4419
4420 let mut edits = Vec::new();
4421 let mut rows = Vec::new();
4422
4423 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4424 let cursor = selection.head();
4425 let row = cursor.row;
4426
4427 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4428
4429 let newline = "\n".to_string();
4430 edits.push((start_of_line..start_of_line, newline));
4431
4432 rows.push(row + rows_inserted as u32);
4433 }
4434
4435 self.transact(window, cx, |editor, window, cx| {
4436 editor.edit(edits, cx);
4437
4438 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4439 let mut index = 0;
4440 s.move_cursors_with(|map, _, _| {
4441 let row = rows[index];
4442 index += 1;
4443
4444 let point = Point::new(row, 0);
4445 let boundary = map.next_line_boundary(point).1;
4446 let clipped = map.clip_point(boundary, Bias::Left);
4447
4448 (clipped, SelectionGoal::None)
4449 });
4450 });
4451
4452 let mut indent_edits = Vec::new();
4453 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4454 for row in rows {
4455 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4456 for (row, indent) in indents {
4457 if indent.len == 0 {
4458 continue;
4459 }
4460
4461 let text = match indent.kind {
4462 IndentKind::Space => " ".repeat(indent.len as usize),
4463 IndentKind::Tab => "\t".repeat(indent.len as usize),
4464 };
4465 let point = Point::new(row.0, 0);
4466 indent_edits.push((point..point, text));
4467 }
4468 }
4469 editor.edit(indent_edits, cx);
4470 });
4471 }
4472
4473 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4474 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4475
4476 let buffer = self.buffer.read(cx);
4477 let snapshot = buffer.snapshot(cx);
4478
4479 let mut edits = Vec::new();
4480 let mut rows = Vec::new();
4481 let mut rows_inserted = 0;
4482
4483 for selection in self.selections.all_adjusted(cx) {
4484 let cursor = selection.head();
4485 let row = cursor.row;
4486
4487 let point = Point::new(row + 1, 0);
4488 let start_of_line = snapshot.clip_point(point, Bias::Left);
4489
4490 let newline = "\n".to_string();
4491 edits.push((start_of_line..start_of_line, newline));
4492
4493 rows_inserted += 1;
4494 rows.push(row + rows_inserted);
4495 }
4496
4497 self.transact(window, cx, |editor, window, cx| {
4498 editor.edit(edits, cx);
4499
4500 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4501 let mut index = 0;
4502 s.move_cursors_with(|map, _, _| {
4503 let row = rows[index];
4504 index += 1;
4505
4506 let point = Point::new(row, 0);
4507 let boundary = map.next_line_boundary(point).1;
4508 let clipped = map.clip_point(boundary, Bias::Left);
4509
4510 (clipped, SelectionGoal::None)
4511 });
4512 });
4513
4514 let mut indent_edits = Vec::new();
4515 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4516 for row in rows {
4517 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4518 for (row, indent) in indents {
4519 if indent.len == 0 {
4520 continue;
4521 }
4522
4523 let text = match indent.kind {
4524 IndentKind::Space => " ".repeat(indent.len as usize),
4525 IndentKind::Tab => "\t".repeat(indent.len as usize),
4526 };
4527 let point = Point::new(row.0, 0);
4528 indent_edits.push((point..point, text));
4529 }
4530 }
4531 editor.edit(indent_edits, cx);
4532 });
4533 }
4534
4535 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4536 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4537 original_indent_columns: Vec::new(),
4538 });
4539 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4540 }
4541
4542 fn insert_with_autoindent_mode(
4543 &mut self,
4544 text: &str,
4545 autoindent_mode: Option<AutoindentMode>,
4546 window: &mut Window,
4547 cx: &mut Context<Self>,
4548 ) {
4549 if self.read_only(cx) {
4550 return;
4551 }
4552
4553 let text: Arc<str> = text.into();
4554 self.transact(window, cx, |this, window, cx| {
4555 let old_selections = this.selections.all_adjusted(cx);
4556 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4557 let anchors = {
4558 let snapshot = buffer.read(cx);
4559 old_selections
4560 .iter()
4561 .map(|s| {
4562 let anchor = snapshot.anchor_after(s.head());
4563 s.map(|_| anchor)
4564 })
4565 .collect::<Vec<_>>()
4566 };
4567 buffer.edit(
4568 old_selections
4569 .iter()
4570 .map(|s| (s.start..s.end, text.clone())),
4571 autoindent_mode,
4572 cx,
4573 );
4574 anchors
4575 });
4576
4577 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4578 s.select_anchors(selection_anchors);
4579 });
4580
4581 cx.notify();
4582 });
4583 }
4584
4585 fn trigger_completion_on_input(
4586 &mut self,
4587 text: &str,
4588 trigger_in_words: bool,
4589 window: &mut Window,
4590 cx: &mut Context<Self>,
4591 ) {
4592 let completions_source = self
4593 .context_menu
4594 .borrow()
4595 .as_ref()
4596 .and_then(|menu| match menu {
4597 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4598 CodeContextMenu::CodeActions(_) => None,
4599 });
4600
4601 match completions_source {
4602 Some(CompletionsMenuSource::Words) => {
4603 self.show_word_completions(&ShowWordCompletions, window, cx)
4604 }
4605 Some(CompletionsMenuSource::Normal)
4606 | Some(CompletionsMenuSource::SnippetChoices)
4607 | None
4608 if self.is_completion_trigger(
4609 text,
4610 trigger_in_words,
4611 completions_source.is_some(),
4612 cx,
4613 ) =>
4614 {
4615 self.show_completions(
4616 &ShowCompletions {
4617 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4618 },
4619 window,
4620 cx,
4621 )
4622 }
4623 _ => {
4624 self.hide_context_menu(window, cx);
4625 }
4626 }
4627 }
4628
4629 fn is_completion_trigger(
4630 &self,
4631 text: &str,
4632 trigger_in_words: bool,
4633 menu_is_open: bool,
4634 cx: &mut Context<Self>,
4635 ) -> bool {
4636 let position = self.selections.newest_anchor().head();
4637 let multibuffer = self.buffer.read(cx);
4638 let Some(buffer) = position
4639 .buffer_id
4640 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4641 else {
4642 return false;
4643 };
4644
4645 if let Some(completion_provider) = &self.completion_provider {
4646 completion_provider.is_completion_trigger(
4647 &buffer,
4648 position.text_anchor,
4649 text,
4650 trigger_in_words,
4651 menu_is_open,
4652 cx,
4653 )
4654 } else {
4655 false
4656 }
4657 }
4658
4659 /// If any empty selections is touching the start of its innermost containing autoclose
4660 /// region, expand it to select the brackets.
4661 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4662 let selections = self.selections.all::<usize>(cx);
4663 let buffer = self.buffer.read(cx).read(cx);
4664 let new_selections = self
4665 .selections_with_autoclose_regions(selections, &buffer)
4666 .map(|(mut selection, region)| {
4667 if !selection.is_empty() {
4668 return selection;
4669 }
4670
4671 if let Some(region) = region {
4672 let mut range = region.range.to_offset(&buffer);
4673 if selection.start == range.start && range.start >= region.pair.start.len() {
4674 range.start -= region.pair.start.len();
4675 if buffer.contains_str_at(range.start, ®ion.pair.start)
4676 && buffer.contains_str_at(range.end, ®ion.pair.end)
4677 {
4678 range.end += region.pair.end.len();
4679 selection.start = range.start;
4680 selection.end = range.end;
4681
4682 return selection;
4683 }
4684 }
4685 }
4686
4687 let always_treat_brackets_as_autoclosed = buffer
4688 .language_settings_at(selection.start, cx)
4689 .always_treat_brackets_as_autoclosed;
4690
4691 if !always_treat_brackets_as_autoclosed {
4692 return selection;
4693 }
4694
4695 if let Some(scope) = buffer.language_scope_at(selection.start) {
4696 for (pair, enabled) in scope.brackets() {
4697 if !enabled || !pair.close {
4698 continue;
4699 }
4700
4701 if buffer.contains_str_at(selection.start, &pair.end) {
4702 let pair_start_len = pair.start.len();
4703 if buffer.contains_str_at(
4704 selection.start.saturating_sub(pair_start_len),
4705 &pair.start,
4706 ) {
4707 selection.start -= pair_start_len;
4708 selection.end += pair.end.len();
4709
4710 return selection;
4711 }
4712 }
4713 }
4714 }
4715
4716 selection
4717 })
4718 .collect();
4719
4720 drop(buffer);
4721 self.change_selections(None, window, cx, |selections| {
4722 selections.select(new_selections)
4723 });
4724 }
4725
4726 /// Iterate the given selections, and for each one, find the smallest surrounding
4727 /// autoclose region. This uses the ordering of the selections and the autoclose
4728 /// regions to avoid repeated comparisons.
4729 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4730 &'a self,
4731 selections: impl IntoIterator<Item = Selection<D>>,
4732 buffer: &'a MultiBufferSnapshot,
4733 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4734 let mut i = 0;
4735 let mut regions = self.autoclose_regions.as_slice();
4736 selections.into_iter().map(move |selection| {
4737 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4738
4739 let mut enclosing = None;
4740 while let Some(pair_state) = regions.get(i) {
4741 if pair_state.range.end.to_offset(buffer) < range.start {
4742 regions = ®ions[i + 1..];
4743 i = 0;
4744 } else if pair_state.range.start.to_offset(buffer) > range.end {
4745 break;
4746 } else {
4747 if pair_state.selection_id == selection.id {
4748 enclosing = Some(pair_state);
4749 }
4750 i += 1;
4751 }
4752 }
4753
4754 (selection, enclosing)
4755 })
4756 }
4757
4758 /// Remove any autoclose regions that no longer contain their selection.
4759 fn invalidate_autoclose_regions(
4760 &mut self,
4761 mut selections: &[Selection<Anchor>],
4762 buffer: &MultiBufferSnapshot,
4763 ) {
4764 self.autoclose_regions.retain(|state| {
4765 let mut i = 0;
4766 while let Some(selection) = selections.get(i) {
4767 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4768 selections = &selections[1..];
4769 continue;
4770 }
4771 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4772 break;
4773 }
4774 if selection.id == state.selection_id {
4775 return true;
4776 } else {
4777 i += 1;
4778 }
4779 }
4780 false
4781 });
4782 }
4783
4784 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4785 let offset = position.to_offset(buffer);
4786 let (word_range, kind) = buffer.surrounding_word(offset, true);
4787 if offset > word_range.start && kind == Some(CharKind::Word) {
4788 Some(
4789 buffer
4790 .text_for_range(word_range.start..offset)
4791 .collect::<String>(),
4792 )
4793 } else {
4794 None
4795 }
4796 }
4797
4798 pub fn toggle_inline_values(
4799 &mut self,
4800 _: &ToggleInlineValues,
4801 _: &mut Window,
4802 cx: &mut Context<Self>,
4803 ) {
4804 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4805
4806 self.refresh_inline_values(cx);
4807 }
4808
4809 pub fn toggle_inlay_hints(
4810 &mut self,
4811 _: &ToggleInlayHints,
4812 _: &mut Window,
4813 cx: &mut Context<Self>,
4814 ) {
4815 self.refresh_inlay_hints(
4816 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4817 cx,
4818 );
4819 }
4820
4821 pub fn inlay_hints_enabled(&self) -> bool {
4822 self.inlay_hint_cache.enabled
4823 }
4824
4825 pub fn inline_values_enabled(&self) -> bool {
4826 self.inline_value_cache.enabled
4827 }
4828
4829 #[cfg(any(test, feature = "test-support"))]
4830 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4831 self.display_map
4832 .read(cx)
4833 .current_inlays()
4834 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4835 .cloned()
4836 .collect()
4837 }
4838
4839 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4840 if self.semantics_provider.is_none() || !self.mode.is_full() {
4841 return;
4842 }
4843
4844 let reason_description = reason.description();
4845 let ignore_debounce = matches!(
4846 reason,
4847 InlayHintRefreshReason::SettingsChange(_)
4848 | InlayHintRefreshReason::Toggle(_)
4849 | InlayHintRefreshReason::ExcerptsRemoved(_)
4850 | InlayHintRefreshReason::ModifiersChanged(_)
4851 );
4852 let (invalidate_cache, required_languages) = match reason {
4853 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4854 match self.inlay_hint_cache.modifiers_override(enabled) {
4855 Some(enabled) => {
4856 if enabled {
4857 (InvalidationStrategy::RefreshRequested, None)
4858 } else {
4859 self.splice_inlays(
4860 &self
4861 .visible_inlay_hints(cx)
4862 .iter()
4863 .map(|inlay| inlay.id)
4864 .collect::<Vec<InlayId>>(),
4865 Vec::new(),
4866 cx,
4867 );
4868 return;
4869 }
4870 }
4871 None => return,
4872 }
4873 }
4874 InlayHintRefreshReason::Toggle(enabled) => {
4875 if self.inlay_hint_cache.toggle(enabled) {
4876 if enabled {
4877 (InvalidationStrategy::RefreshRequested, None)
4878 } else {
4879 self.splice_inlays(
4880 &self
4881 .visible_inlay_hints(cx)
4882 .iter()
4883 .map(|inlay| inlay.id)
4884 .collect::<Vec<InlayId>>(),
4885 Vec::new(),
4886 cx,
4887 );
4888 return;
4889 }
4890 } else {
4891 return;
4892 }
4893 }
4894 InlayHintRefreshReason::SettingsChange(new_settings) => {
4895 match self.inlay_hint_cache.update_settings(
4896 &self.buffer,
4897 new_settings,
4898 self.visible_inlay_hints(cx),
4899 cx,
4900 ) {
4901 ControlFlow::Break(Some(InlaySplice {
4902 to_remove,
4903 to_insert,
4904 })) => {
4905 self.splice_inlays(&to_remove, to_insert, cx);
4906 return;
4907 }
4908 ControlFlow::Break(None) => return,
4909 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4910 }
4911 }
4912 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4913 if let Some(InlaySplice {
4914 to_remove,
4915 to_insert,
4916 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4917 {
4918 self.splice_inlays(&to_remove, to_insert, cx);
4919 }
4920 self.display_map.update(cx, |display_map, _| {
4921 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4922 });
4923 return;
4924 }
4925 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4926 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4927 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4928 }
4929 InlayHintRefreshReason::RefreshRequested => {
4930 (InvalidationStrategy::RefreshRequested, None)
4931 }
4932 };
4933
4934 if let Some(InlaySplice {
4935 to_remove,
4936 to_insert,
4937 }) = self.inlay_hint_cache.spawn_hint_refresh(
4938 reason_description,
4939 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4940 invalidate_cache,
4941 ignore_debounce,
4942 cx,
4943 ) {
4944 self.splice_inlays(&to_remove, to_insert, cx);
4945 }
4946 }
4947
4948 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4949 self.display_map
4950 .read(cx)
4951 .current_inlays()
4952 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4953 .cloned()
4954 .collect()
4955 }
4956
4957 pub fn excerpts_for_inlay_hints_query(
4958 &self,
4959 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4960 cx: &mut Context<Editor>,
4961 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4962 let Some(project) = self.project.as_ref() else {
4963 return HashMap::default();
4964 };
4965 let project = project.read(cx);
4966 let multi_buffer = self.buffer().read(cx);
4967 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4968 let multi_buffer_visible_start = self
4969 .scroll_manager
4970 .anchor()
4971 .anchor
4972 .to_point(&multi_buffer_snapshot);
4973 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4974 multi_buffer_visible_start
4975 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4976 Bias::Left,
4977 );
4978 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4979 multi_buffer_snapshot
4980 .range_to_buffer_ranges(multi_buffer_visible_range)
4981 .into_iter()
4982 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4983 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4984 let buffer_file = project::File::from_dyn(buffer.file())?;
4985 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4986 let worktree_entry = buffer_worktree
4987 .read(cx)
4988 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4989 if worktree_entry.is_ignored {
4990 return None;
4991 }
4992
4993 let language = buffer.language()?;
4994 if let Some(restrict_to_languages) = restrict_to_languages {
4995 if !restrict_to_languages.contains(language) {
4996 return None;
4997 }
4998 }
4999 Some((
5000 excerpt_id,
5001 (
5002 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5003 buffer.version().clone(),
5004 excerpt_visible_range,
5005 ),
5006 ))
5007 })
5008 .collect()
5009 }
5010
5011 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5012 TextLayoutDetails {
5013 text_system: window.text_system().clone(),
5014 editor_style: self.style.clone().unwrap(),
5015 rem_size: window.rem_size(),
5016 scroll_anchor: self.scroll_manager.anchor(),
5017 visible_rows: self.visible_line_count(),
5018 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5019 }
5020 }
5021
5022 pub fn splice_inlays(
5023 &self,
5024 to_remove: &[InlayId],
5025 to_insert: Vec<Inlay>,
5026 cx: &mut Context<Self>,
5027 ) {
5028 self.display_map.update(cx, |display_map, cx| {
5029 display_map.splice_inlays(to_remove, to_insert, cx)
5030 });
5031 cx.notify();
5032 }
5033
5034 fn trigger_on_type_formatting(
5035 &self,
5036 input: String,
5037 window: &mut Window,
5038 cx: &mut Context<Self>,
5039 ) -> Option<Task<Result<()>>> {
5040 if input.len() != 1 {
5041 return None;
5042 }
5043
5044 let project = self.project.as_ref()?;
5045 let position = self.selections.newest_anchor().head();
5046 let (buffer, buffer_position) = self
5047 .buffer
5048 .read(cx)
5049 .text_anchor_for_position(position, cx)?;
5050
5051 let settings = language_settings::language_settings(
5052 buffer
5053 .read(cx)
5054 .language_at(buffer_position)
5055 .map(|l| l.name()),
5056 buffer.read(cx).file(),
5057 cx,
5058 );
5059 if !settings.use_on_type_format {
5060 return None;
5061 }
5062
5063 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5064 // hence we do LSP request & edit on host side only — add formats to host's history.
5065 let push_to_lsp_host_history = true;
5066 // If this is not the host, append its history with new edits.
5067 let push_to_client_history = project.read(cx).is_via_collab();
5068
5069 let on_type_formatting = project.update(cx, |project, cx| {
5070 project.on_type_format(
5071 buffer.clone(),
5072 buffer_position,
5073 input,
5074 push_to_lsp_host_history,
5075 cx,
5076 )
5077 });
5078 Some(cx.spawn_in(window, async move |editor, cx| {
5079 if let Some(transaction) = on_type_formatting.await? {
5080 if push_to_client_history {
5081 buffer
5082 .update(cx, |buffer, _| {
5083 buffer.push_transaction(transaction, Instant::now());
5084 buffer.finalize_last_transaction();
5085 })
5086 .ok();
5087 }
5088 editor.update(cx, |editor, cx| {
5089 editor.refresh_document_highlights(cx);
5090 })?;
5091 }
5092 Ok(())
5093 }))
5094 }
5095
5096 pub fn show_word_completions(
5097 &mut self,
5098 _: &ShowWordCompletions,
5099 window: &mut Window,
5100 cx: &mut Context<Self>,
5101 ) {
5102 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5103 }
5104
5105 pub fn show_completions(
5106 &mut self,
5107 options: &ShowCompletions,
5108 window: &mut Window,
5109 cx: &mut Context<Self>,
5110 ) {
5111 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5112 }
5113
5114 fn open_or_update_completions_menu(
5115 &mut self,
5116 requested_source: Option<CompletionsMenuSource>,
5117 trigger: Option<&str>,
5118 window: &mut Window,
5119 cx: &mut Context<Self>,
5120 ) {
5121 if self.pending_rename.is_some() {
5122 return;
5123 }
5124
5125 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5126
5127 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5128 // inserted and selected. To handle that case, the start of the selection is used so that
5129 // the menu starts with all choices.
5130 let position = self
5131 .selections
5132 .newest_anchor()
5133 .start
5134 .bias_right(&multibuffer_snapshot);
5135 if position.diff_base_anchor.is_some() {
5136 return;
5137 }
5138 let (buffer, buffer_position) =
5139 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5140 output
5141 } else {
5142 return;
5143 };
5144 let buffer_snapshot = buffer.read(cx).snapshot();
5145
5146 let query: Option<Arc<String>> =
5147 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5148
5149 drop(multibuffer_snapshot);
5150
5151 let provider = match requested_source {
5152 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5153 Some(CompletionsMenuSource::Words) => None,
5154 Some(CompletionsMenuSource::SnippetChoices) => {
5155 log::error!("bug: SnippetChoices requested_source is not handled");
5156 None
5157 }
5158 };
5159
5160 let sort_completions = provider
5161 .as_ref()
5162 .map_or(false, |provider| provider.sort_completions());
5163
5164 let filter_completions = provider
5165 .as_ref()
5166 .map_or(true, |provider| provider.filter_completions());
5167
5168 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5169 if filter_completions {
5170 menu.filter(query.clone(), provider.clone(), window, cx);
5171 }
5172 // When `is_incomplete` is false, no need to re-query completions when the current query
5173 // is a suffix of the initial query.
5174 if !menu.is_incomplete {
5175 // If the new query is a suffix of the old query (typing more characters) and
5176 // the previous result was complete, the existing completions can be filtered.
5177 //
5178 // Note that this is always true for snippet completions.
5179 let query_matches = match (&menu.initial_query, &query) {
5180 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5181 (None, _) => true,
5182 _ => false,
5183 };
5184 if query_matches {
5185 let position_matches = if menu.initial_position == position {
5186 true
5187 } else {
5188 let snapshot = self.buffer.read(cx).read(cx);
5189 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5190 };
5191 if position_matches {
5192 return;
5193 }
5194 }
5195 }
5196 };
5197
5198 let trigger_kind = match trigger {
5199 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5200 CompletionTriggerKind::TRIGGER_CHARACTER
5201 }
5202 _ => CompletionTriggerKind::INVOKED,
5203 };
5204 let completion_context = CompletionContext {
5205 trigger_character: trigger.and_then(|trigger| {
5206 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5207 Some(String::from(trigger))
5208 } else {
5209 None
5210 }
5211 }),
5212 trigger_kind,
5213 };
5214
5215 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5216 buffer_snapshot.surrounding_word(buffer_position)
5217 {
5218 let word_to_exclude = buffer_snapshot
5219 .text_for_range(word_range.clone())
5220 .collect::<String>();
5221 (
5222 buffer_snapshot.anchor_before(word_range.start)
5223 ..buffer_snapshot.anchor_after(buffer_position),
5224 Some(word_to_exclude),
5225 )
5226 } else {
5227 (buffer_position..buffer_position, None)
5228 };
5229
5230 let language = buffer_snapshot
5231 .language_at(buffer_position)
5232 .map(|language| language.name());
5233
5234 let completion_settings =
5235 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5236
5237 let show_completion_documentation = buffer_snapshot
5238 .settings_at(buffer_position, cx)
5239 .show_completion_documentation;
5240
5241 // The document can be large, so stay in reasonable bounds when searching for words,
5242 // otherwise completion pop-up might be slow to appear.
5243 const WORD_LOOKUP_ROWS: u32 = 5_000;
5244 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5245 let min_word_search = buffer_snapshot.clip_point(
5246 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5247 Bias::Left,
5248 );
5249 let max_word_search = buffer_snapshot.clip_point(
5250 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5251 Bias::Right,
5252 );
5253 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5254 ..buffer_snapshot.point_to_offset(max_word_search);
5255
5256 let skip_digits = query
5257 .as_ref()
5258 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5259
5260 let (mut words, provider_responses) = match &provider {
5261 Some(provider) => {
5262 let provider_responses = provider.completions(
5263 position.excerpt_id,
5264 &buffer,
5265 buffer_position,
5266 completion_context,
5267 window,
5268 cx,
5269 );
5270
5271 let words = match completion_settings.words {
5272 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5273 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5274 .background_spawn(async move {
5275 buffer_snapshot.words_in_range(WordsQuery {
5276 fuzzy_contents: None,
5277 range: word_search_range,
5278 skip_digits,
5279 })
5280 }),
5281 };
5282
5283 (words, provider_responses)
5284 }
5285 None => (
5286 cx.background_spawn(async move {
5287 buffer_snapshot.words_in_range(WordsQuery {
5288 fuzzy_contents: None,
5289 range: word_search_range,
5290 skip_digits,
5291 })
5292 }),
5293 Task::ready(Ok(Vec::new())),
5294 ),
5295 };
5296
5297 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5298
5299 let id = post_inc(&mut self.next_completion_id);
5300 let task = cx.spawn_in(window, async move |editor, cx| {
5301 let Ok(()) = editor.update(cx, |this, _| {
5302 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5303 }) else {
5304 return;
5305 };
5306
5307 // TODO: Ideally completions from different sources would be selectively re-queried, so
5308 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5309 let mut completions = Vec::new();
5310 let mut is_incomplete = false;
5311 if let Some(provider_responses) = provider_responses.await.log_err() {
5312 if !provider_responses.is_empty() {
5313 for response in provider_responses {
5314 completions.extend(response.completions);
5315 is_incomplete = is_incomplete || response.is_incomplete;
5316 }
5317 if completion_settings.words == WordsCompletionMode::Fallback {
5318 words = Task::ready(BTreeMap::default());
5319 }
5320 }
5321 }
5322
5323 let mut words = words.await;
5324 if let Some(word_to_exclude) = &word_to_exclude {
5325 words.remove(word_to_exclude);
5326 }
5327 for lsp_completion in &completions {
5328 words.remove(&lsp_completion.new_text);
5329 }
5330 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5331 replace_range: word_replace_range.clone(),
5332 new_text: word.clone(),
5333 label: CodeLabel::plain(word, None),
5334 icon_path: None,
5335 documentation: None,
5336 source: CompletionSource::BufferWord {
5337 word_range,
5338 resolved: false,
5339 },
5340 insert_text_mode: Some(InsertTextMode::AS_IS),
5341 confirm: None,
5342 }));
5343
5344 let menu = if completions.is_empty() {
5345 None
5346 } else {
5347 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5348 let languages = editor
5349 .workspace
5350 .as_ref()
5351 .and_then(|(workspace, _)| workspace.upgrade())
5352 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5353 let menu = CompletionsMenu::new(
5354 id,
5355 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5356 sort_completions,
5357 show_completion_documentation,
5358 position,
5359 query.clone(),
5360 is_incomplete,
5361 buffer.clone(),
5362 completions.into(),
5363 snippet_sort_order,
5364 languages,
5365 language,
5366 cx,
5367 );
5368
5369 let query = if filter_completions { query } else { None };
5370 let matches_task = if let Some(query) = query {
5371 menu.do_async_filtering(query, cx)
5372 } else {
5373 Task::ready(menu.unfiltered_matches())
5374 };
5375 (menu, matches_task)
5376 }) else {
5377 return;
5378 };
5379
5380 let matches = matches_task.await;
5381
5382 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5383 // Newer menu already set, so exit.
5384 match editor.context_menu.borrow().as_ref() {
5385 Some(CodeContextMenu::Completions(prev_menu)) => {
5386 if prev_menu.id > id {
5387 return;
5388 }
5389 }
5390 _ => {}
5391 };
5392
5393 // Only valid to take prev_menu because it the new menu is immediately set
5394 // below, or the menu is hidden.
5395 match editor.context_menu.borrow_mut().take() {
5396 Some(CodeContextMenu::Completions(prev_menu)) => {
5397 let position_matches =
5398 if prev_menu.initial_position == menu.initial_position {
5399 true
5400 } else {
5401 let snapshot = editor.buffer.read(cx).read(cx);
5402 prev_menu.initial_position.to_offset(&snapshot)
5403 == menu.initial_position.to_offset(&snapshot)
5404 };
5405 if position_matches {
5406 // Preserve markdown cache before `set_filter_results` because it will
5407 // try to populate the documentation cache.
5408 menu.preserve_markdown_cache(prev_menu);
5409 }
5410 }
5411 _ => {}
5412 };
5413
5414 menu.set_filter_results(matches, provider, window, cx);
5415 }) else {
5416 return;
5417 };
5418
5419 menu.visible().then_some(menu)
5420 };
5421
5422 editor
5423 .update_in(cx, |editor, window, cx| {
5424 if editor.focus_handle.is_focused(window) {
5425 if let Some(menu) = menu {
5426 *editor.context_menu.borrow_mut() =
5427 Some(CodeContextMenu::Completions(menu));
5428
5429 crate::hover_popover::hide_hover(editor, cx);
5430 if editor.show_edit_predictions_in_menu() {
5431 editor.update_visible_inline_completion(window, cx);
5432 } else {
5433 editor.discard_inline_completion(false, cx);
5434 }
5435
5436 cx.notify();
5437 return;
5438 }
5439 }
5440
5441 if editor.completion_tasks.len() <= 1 {
5442 // If there are no more completion tasks and the last menu was empty, we should hide it.
5443 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5444 // If it was already hidden and we don't show inline completions in the menu, we should
5445 // also show the inline-completion when available.
5446 if was_hidden && editor.show_edit_predictions_in_menu() {
5447 editor.update_visible_inline_completion(window, cx);
5448 }
5449 }
5450 })
5451 .ok();
5452 });
5453
5454 self.completion_tasks.push((id, task));
5455 }
5456
5457 #[cfg(feature = "test-support")]
5458 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5459 let menu = self.context_menu.borrow();
5460 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5461 let completions = menu.completions.borrow();
5462 Some(completions.to_vec())
5463 } else {
5464 None
5465 }
5466 }
5467
5468 pub fn with_completions_menu_matching_id<R>(
5469 &self,
5470 id: CompletionId,
5471 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5472 ) -> R {
5473 let mut context_menu = self.context_menu.borrow_mut();
5474 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5475 return f(None);
5476 };
5477 if completions_menu.id != id {
5478 return f(None);
5479 }
5480 f(Some(completions_menu))
5481 }
5482
5483 pub fn confirm_completion(
5484 &mut self,
5485 action: &ConfirmCompletion,
5486 window: &mut Window,
5487 cx: &mut Context<Self>,
5488 ) -> Option<Task<Result<()>>> {
5489 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5490 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5491 }
5492
5493 pub fn confirm_completion_insert(
5494 &mut self,
5495 _: &ConfirmCompletionInsert,
5496 window: &mut Window,
5497 cx: &mut Context<Self>,
5498 ) -> Option<Task<Result<()>>> {
5499 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5500 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5501 }
5502
5503 pub fn confirm_completion_replace(
5504 &mut self,
5505 _: &ConfirmCompletionReplace,
5506 window: &mut Window,
5507 cx: &mut Context<Self>,
5508 ) -> Option<Task<Result<()>>> {
5509 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5510 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5511 }
5512
5513 pub fn compose_completion(
5514 &mut self,
5515 action: &ComposeCompletion,
5516 window: &mut Window,
5517 cx: &mut Context<Self>,
5518 ) -> Option<Task<Result<()>>> {
5519 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5520 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5521 }
5522
5523 fn do_completion(
5524 &mut self,
5525 item_ix: Option<usize>,
5526 intent: CompletionIntent,
5527 window: &mut Window,
5528 cx: &mut Context<Editor>,
5529 ) -> Option<Task<Result<()>>> {
5530 use language::ToOffset as _;
5531
5532 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5533 else {
5534 return None;
5535 };
5536
5537 let candidate_id = {
5538 let entries = completions_menu.entries.borrow();
5539 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5540 if self.show_edit_predictions_in_menu() {
5541 self.discard_inline_completion(true, cx);
5542 }
5543 mat.candidate_id
5544 };
5545
5546 let completion = completions_menu
5547 .completions
5548 .borrow()
5549 .get(candidate_id)?
5550 .clone();
5551 cx.stop_propagation();
5552
5553 let buffer_handle = completions_menu.buffer.clone();
5554
5555 let CompletionEdit {
5556 new_text,
5557 snippet,
5558 replace_range,
5559 } = process_completion_for_edit(
5560 &completion,
5561 intent,
5562 &buffer_handle,
5563 &completions_menu.initial_position.text_anchor,
5564 cx,
5565 );
5566
5567 let buffer = buffer_handle.read(cx);
5568 let snapshot = self.buffer.read(cx).snapshot(cx);
5569 let newest_anchor = self.selections.newest_anchor();
5570 let replace_range_multibuffer = {
5571 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5572 let multibuffer_anchor = snapshot
5573 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5574 .unwrap()
5575 ..snapshot
5576 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5577 .unwrap();
5578 multibuffer_anchor.start.to_offset(&snapshot)
5579 ..multibuffer_anchor.end.to_offset(&snapshot)
5580 };
5581 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5582 return None;
5583 }
5584
5585 let old_text = buffer
5586 .text_for_range(replace_range.clone())
5587 .collect::<String>();
5588 let lookbehind = newest_anchor
5589 .start
5590 .text_anchor
5591 .to_offset(buffer)
5592 .saturating_sub(replace_range.start);
5593 let lookahead = replace_range
5594 .end
5595 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5596 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5597 let suffix = &old_text[lookbehind.min(old_text.len())..];
5598
5599 let selections = self.selections.all::<usize>(cx);
5600 let mut ranges = Vec::new();
5601 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5602
5603 for selection in &selections {
5604 let range = if selection.id == newest_anchor.id {
5605 replace_range_multibuffer.clone()
5606 } else {
5607 let mut range = selection.range();
5608
5609 // if prefix is present, don't duplicate it
5610 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5611 range.start = range.start.saturating_sub(lookbehind);
5612
5613 // if suffix is also present, mimic the newest cursor and replace it
5614 if selection.id != newest_anchor.id
5615 && snapshot.contains_str_at(range.end, suffix)
5616 {
5617 range.end += lookahead;
5618 }
5619 }
5620 range
5621 };
5622
5623 ranges.push(range.clone());
5624
5625 if !self.linked_edit_ranges.is_empty() {
5626 let start_anchor = snapshot.anchor_before(range.start);
5627 let end_anchor = snapshot.anchor_after(range.end);
5628 if let Some(ranges) = self
5629 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5630 {
5631 for (buffer, edits) in ranges {
5632 linked_edits
5633 .entry(buffer.clone())
5634 .or_default()
5635 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5636 }
5637 }
5638 }
5639 }
5640
5641 let common_prefix_len = old_text
5642 .chars()
5643 .zip(new_text.chars())
5644 .take_while(|(a, b)| a == b)
5645 .map(|(a, _)| a.len_utf8())
5646 .sum::<usize>();
5647
5648 cx.emit(EditorEvent::InputHandled {
5649 utf16_range_to_replace: None,
5650 text: new_text[common_prefix_len..].into(),
5651 });
5652
5653 self.transact(window, cx, |this, window, cx| {
5654 if let Some(mut snippet) = snippet {
5655 snippet.text = new_text.to_string();
5656 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5657 } else {
5658 this.buffer.update(cx, |buffer, cx| {
5659 let auto_indent = match completion.insert_text_mode {
5660 Some(InsertTextMode::AS_IS) => None,
5661 _ => this.autoindent_mode.clone(),
5662 };
5663 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5664 buffer.edit(edits, auto_indent, cx);
5665 });
5666 }
5667 for (buffer, edits) in linked_edits {
5668 buffer.update(cx, |buffer, cx| {
5669 let snapshot = buffer.snapshot();
5670 let edits = edits
5671 .into_iter()
5672 .map(|(range, text)| {
5673 use text::ToPoint as TP;
5674 let end_point = TP::to_point(&range.end, &snapshot);
5675 let start_point = TP::to_point(&range.start, &snapshot);
5676 (start_point..end_point, text)
5677 })
5678 .sorted_by_key(|(range, _)| range.start);
5679 buffer.edit(edits, None, cx);
5680 })
5681 }
5682
5683 this.refresh_inline_completion(true, false, window, cx);
5684 });
5685
5686 let show_new_completions_on_confirm = completion
5687 .confirm
5688 .as_ref()
5689 .map_or(false, |confirm| confirm(intent, window, cx));
5690 if show_new_completions_on_confirm {
5691 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5692 }
5693
5694 let provider = self.completion_provider.as_ref()?;
5695 drop(completion);
5696 let apply_edits = provider.apply_additional_edits_for_completion(
5697 buffer_handle,
5698 completions_menu.completions.clone(),
5699 candidate_id,
5700 true,
5701 cx,
5702 );
5703
5704 let editor_settings = EditorSettings::get_global(cx);
5705 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5706 // After the code completion is finished, users often want to know what signatures are needed.
5707 // so we should automatically call signature_help
5708 self.show_signature_help(&ShowSignatureHelp, window, cx);
5709 }
5710
5711 Some(cx.foreground_executor().spawn(async move {
5712 apply_edits.await?;
5713 Ok(())
5714 }))
5715 }
5716
5717 pub fn toggle_code_actions(
5718 &mut self,
5719 action: &ToggleCodeActions,
5720 window: &mut Window,
5721 cx: &mut Context<Self>,
5722 ) {
5723 let quick_launch = action.quick_launch;
5724 let mut context_menu = self.context_menu.borrow_mut();
5725 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5726 if code_actions.deployed_from == action.deployed_from {
5727 // Toggle if we're selecting the same one
5728 *context_menu = None;
5729 cx.notify();
5730 return;
5731 } else {
5732 // Otherwise, clear it and start a new one
5733 *context_menu = None;
5734 cx.notify();
5735 }
5736 }
5737 drop(context_menu);
5738 let snapshot = self.snapshot(window, cx);
5739 let deployed_from = action.deployed_from.clone();
5740 let action = action.clone();
5741 self.completion_tasks.clear();
5742 self.discard_inline_completion(false, cx);
5743
5744 let multibuffer_point = match &action.deployed_from {
5745 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5746 DisplayPoint::new(*row, 0).to_point(&snapshot)
5747 }
5748 _ => self.selections.newest::<Point>(cx).head(),
5749 };
5750 let Some((buffer, buffer_row)) = snapshot
5751 .buffer_snapshot
5752 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5753 .and_then(|(buffer_snapshot, range)| {
5754 self.buffer()
5755 .read(cx)
5756 .buffer(buffer_snapshot.remote_id())
5757 .map(|buffer| (buffer, range.start.row))
5758 })
5759 else {
5760 return;
5761 };
5762 let buffer_id = buffer.read(cx).remote_id();
5763 let tasks = self
5764 .tasks
5765 .get(&(buffer_id, buffer_row))
5766 .map(|t| Arc::new(t.to_owned()));
5767
5768 if !self.focus_handle.is_focused(window) {
5769 return;
5770 }
5771 let project = self.project.clone();
5772
5773 let code_actions_task = match deployed_from {
5774 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5775 _ => self.code_actions(buffer_row, window, cx),
5776 };
5777
5778 let runnable_task = match deployed_from {
5779 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5780 _ => {
5781 let mut task_context_task = Task::ready(None);
5782 if let Some(tasks) = &tasks {
5783 if let Some(project) = project {
5784 task_context_task =
5785 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5786 }
5787 }
5788
5789 cx.spawn_in(window, {
5790 let buffer = buffer.clone();
5791 async move |editor, cx| {
5792 let task_context = task_context_task.await;
5793
5794 let resolved_tasks =
5795 tasks
5796 .zip(task_context.clone())
5797 .map(|(tasks, task_context)| ResolvedTasks {
5798 templates: tasks.resolve(&task_context).collect(),
5799 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5800 multibuffer_point.row,
5801 tasks.column,
5802 )),
5803 });
5804 let debug_scenarios = editor.update(cx, |editor, cx| {
5805 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5806 })?;
5807 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5808 }
5809 })
5810 }
5811 };
5812
5813 cx.spawn_in(window, async move |editor, cx| {
5814 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5815 let code_actions = code_actions_task.await;
5816 let spawn_straight_away = quick_launch
5817 && resolved_tasks
5818 .as_ref()
5819 .map_or(false, |tasks| tasks.templates.len() == 1)
5820 && code_actions
5821 .as_ref()
5822 .map_or(true, |actions| actions.is_empty())
5823 && debug_scenarios.is_empty();
5824
5825 editor.update_in(cx, |editor, window, cx| {
5826 crate::hover_popover::hide_hover(editor, cx);
5827 *editor.context_menu.borrow_mut() =
5828 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5829 buffer,
5830 actions: CodeActionContents::new(
5831 resolved_tasks,
5832 code_actions,
5833 debug_scenarios,
5834 task_context.unwrap_or_default(),
5835 ),
5836 selected_item: Default::default(),
5837 scroll_handle: UniformListScrollHandle::default(),
5838 deployed_from,
5839 }));
5840 if spawn_straight_away {
5841 if let Some(task) = editor.confirm_code_action(
5842 &ConfirmCodeAction { item_ix: Some(0) },
5843 window,
5844 cx,
5845 ) {
5846 cx.notify();
5847 return task;
5848 }
5849 }
5850
5851 Task::ready(Ok(()))
5852 })
5853 })
5854 .detach_and_log_err(cx);
5855 }
5856
5857 fn debug_scenarios(
5858 &mut self,
5859 resolved_tasks: &Option<ResolvedTasks>,
5860 buffer: &Entity<Buffer>,
5861 cx: &mut App,
5862 ) -> Vec<task::DebugScenario> {
5863 if cx.has_flag::<DebuggerFeatureFlag>() {
5864 maybe!({
5865 let project = self.project.as_ref()?;
5866 let dap_store = project.read(cx).dap_store();
5867 let mut scenarios = vec![];
5868 let resolved_tasks = resolved_tasks.as_ref()?;
5869 let buffer = buffer.read(cx);
5870 let language = buffer.language()?;
5871 let file = buffer.file();
5872 let debug_adapter = language_settings(language.name().into(), file, cx)
5873 .debuggers
5874 .first()
5875 .map(SharedString::from)
5876 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5877
5878 dap_store.update(cx, |dap_store, cx| {
5879 for (_, task) in &resolved_tasks.templates {
5880 if let Some(scenario) = dap_store.debug_scenario_for_build_task(
5881 task.original_task().clone(),
5882 debug_adapter.clone().into(),
5883 task.display_label().to_owned().into(),
5884 cx,
5885 ) {
5886 scenarios.push(scenario);
5887 }
5888 }
5889 });
5890 Some(scenarios)
5891 })
5892 .unwrap_or_default()
5893 } else {
5894 vec![]
5895 }
5896 }
5897
5898 fn code_actions(
5899 &mut self,
5900 buffer_row: u32,
5901 window: &mut Window,
5902 cx: &mut Context<Self>,
5903 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5904 let mut task = self.code_actions_task.take();
5905 cx.spawn_in(window, async move |editor, cx| {
5906 while let Some(prev_task) = task {
5907 prev_task.await.log_err();
5908 task = editor
5909 .update(cx, |this, _| this.code_actions_task.take())
5910 .ok()?;
5911 }
5912
5913 editor
5914 .update(cx, |editor, cx| {
5915 editor
5916 .available_code_actions
5917 .clone()
5918 .and_then(|(location, code_actions)| {
5919 let snapshot = location.buffer.read(cx).snapshot();
5920 let point_range = location.range.to_point(&snapshot);
5921 let point_range = point_range.start.row..=point_range.end.row;
5922 if point_range.contains(&buffer_row) {
5923 Some(code_actions)
5924 } else {
5925 None
5926 }
5927 })
5928 })
5929 .ok()
5930 .flatten()
5931 })
5932 }
5933
5934 pub fn confirm_code_action(
5935 &mut self,
5936 action: &ConfirmCodeAction,
5937 window: &mut Window,
5938 cx: &mut Context<Self>,
5939 ) -> Option<Task<Result<()>>> {
5940 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5941
5942 let actions_menu =
5943 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5944 menu
5945 } else {
5946 return None;
5947 };
5948
5949 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5950 let action = actions_menu.actions.get(action_ix)?;
5951 let title = action.label();
5952 let buffer = actions_menu.buffer;
5953 let workspace = self.workspace()?;
5954
5955 match action {
5956 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5957 workspace.update(cx, |workspace, cx| {
5958 workspace.schedule_resolved_task(
5959 task_source_kind,
5960 resolved_task,
5961 false,
5962 window,
5963 cx,
5964 );
5965
5966 Some(Task::ready(Ok(())))
5967 })
5968 }
5969 CodeActionsItem::CodeAction {
5970 excerpt_id,
5971 action,
5972 provider,
5973 } => {
5974 let apply_code_action =
5975 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5976 let workspace = workspace.downgrade();
5977 Some(cx.spawn_in(window, async move |editor, cx| {
5978 let project_transaction = apply_code_action.await?;
5979 Self::open_project_transaction(
5980 &editor,
5981 workspace,
5982 project_transaction,
5983 title,
5984 cx,
5985 )
5986 .await
5987 }))
5988 }
5989 CodeActionsItem::DebugScenario(scenario) => {
5990 let context = actions_menu.actions.context.clone();
5991
5992 workspace.update(cx, |workspace, cx| {
5993 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5994 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5995 });
5996 Some(Task::ready(Ok(())))
5997 }
5998 }
5999 }
6000
6001 pub async fn open_project_transaction(
6002 this: &WeakEntity<Editor>,
6003 workspace: WeakEntity<Workspace>,
6004 transaction: ProjectTransaction,
6005 title: String,
6006 cx: &mut AsyncWindowContext,
6007 ) -> Result<()> {
6008 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6009 cx.update(|_, cx| {
6010 entries.sort_unstable_by_key(|(buffer, _)| {
6011 buffer.read(cx).file().map(|f| f.path().clone())
6012 });
6013 })?;
6014
6015 // If the project transaction's edits are all contained within this editor, then
6016 // avoid opening a new editor to display them.
6017
6018 if let Some((buffer, transaction)) = entries.first() {
6019 if entries.len() == 1 {
6020 let excerpt = this.update(cx, |editor, cx| {
6021 editor
6022 .buffer()
6023 .read(cx)
6024 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6025 })?;
6026 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6027 if excerpted_buffer == *buffer {
6028 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6029 let excerpt_range = excerpt_range.to_offset(buffer);
6030 buffer
6031 .edited_ranges_for_transaction::<usize>(transaction)
6032 .all(|range| {
6033 excerpt_range.start <= range.start
6034 && excerpt_range.end >= range.end
6035 })
6036 })?;
6037
6038 if all_edits_within_excerpt {
6039 return Ok(());
6040 }
6041 }
6042 }
6043 }
6044 } else {
6045 return Ok(());
6046 }
6047
6048 let mut ranges_to_highlight = Vec::new();
6049 let excerpt_buffer = cx.new(|cx| {
6050 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6051 for (buffer_handle, transaction) in &entries {
6052 let edited_ranges = buffer_handle
6053 .read(cx)
6054 .edited_ranges_for_transaction::<Point>(transaction)
6055 .collect::<Vec<_>>();
6056 let (ranges, _) = multibuffer.set_excerpts_for_path(
6057 PathKey::for_buffer(buffer_handle, cx),
6058 buffer_handle.clone(),
6059 edited_ranges,
6060 DEFAULT_MULTIBUFFER_CONTEXT,
6061 cx,
6062 );
6063
6064 ranges_to_highlight.extend(ranges);
6065 }
6066 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6067 multibuffer
6068 })?;
6069
6070 workspace.update_in(cx, |workspace, window, cx| {
6071 let project = workspace.project().clone();
6072 let editor =
6073 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6074 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6075 editor.update(cx, |editor, cx| {
6076 editor.highlight_background::<Self>(
6077 &ranges_to_highlight,
6078 |theme| theme.editor_highlighted_line_background,
6079 cx,
6080 );
6081 });
6082 })?;
6083
6084 Ok(())
6085 }
6086
6087 pub fn clear_code_action_providers(&mut self) {
6088 self.code_action_providers.clear();
6089 self.available_code_actions.take();
6090 }
6091
6092 pub fn add_code_action_provider(
6093 &mut self,
6094 provider: Rc<dyn CodeActionProvider>,
6095 window: &mut Window,
6096 cx: &mut Context<Self>,
6097 ) {
6098 if self
6099 .code_action_providers
6100 .iter()
6101 .any(|existing_provider| existing_provider.id() == provider.id())
6102 {
6103 return;
6104 }
6105
6106 self.code_action_providers.push(provider);
6107 self.refresh_code_actions(window, cx);
6108 }
6109
6110 pub fn remove_code_action_provider(
6111 &mut self,
6112 id: Arc<str>,
6113 window: &mut Window,
6114 cx: &mut Context<Self>,
6115 ) {
6116 self.code_action_providers
6117 .retain(|provider| provider.id() != id);
6118 self.refresh_code_actions(window, cx);
6119 }
6120
6121 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6122 !self.code_action_providers.is_empty()
6123 && EditorSettings::get_global(cx).toolbar.code_actions
6124 }
6125
6126 pub fn has_available_code_actions(&self) -> bool {
6127 self.available_code_actions
6128 .as_ref()
6129 .is_some_and(|(_, actions)| !actions.is_empty())
6130 }
6131
6132 fn render_inline_code_actions(
6133 &self,
6134 icon_size: ui::IconSize,
6135 display_row: DisplayRow,
6136 is_active: bool,
6137 cx: &mut Context<Self>,
6138 ) -> AnyElement {
6139 let show_tooltip = !self.context_menu_visible();
6140 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6141 .icon_size(icon_size)
6142 .shape(ui::IconButtonShape::Square)
6143 .style(ButtonStyle::Transparent)
6144 .icon_color(ui::Color::Hidden)
6145 .toggle_state(is_active)
6146 .when(show_tooltip, |this| {
6147 this.tooltip({
6148 let focus_handle = self.focus_handle.clone();
6149 move |window, cx| {
6150 Tooltip::for_action_in(
6151 "Toggle Code Actions",
6152 &ToggleCodeActions {
6153 deployed_from: None,
6154 quick_launch: false,
6155 },
6156 &focus_handle,
6157 window,
6158 cx,
6159 )
6160 }
6161 })
6162 })
6163 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6164 window.focus(&editor.focus_handle(cx));
6165 editor.toggle_code_actions(
6166 &crate::actions::ToggleCodeActions {
6167 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6168 display_row,
6169 )),
6170 quick_launch: false,
6171 },
6172 window,
6173 cx,
6174 );
6175 }))
6176 .into_any_element()
6177 }
6178
6179 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6180 &self.context_menu
6181 }
6182
6183 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6184 let newest_selection = self.selections.newest_anchor().clone();
6185 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6186 let buffer = self.buffer.read(cx);
6187 if newest_selection.head().diff_base_anchor.is_some() {
6188 return None;
6189 }
6190 let (start_buffer, start) =
6191 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6192 let (end_buffer, end) =
6193 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6194 if start_buffer != end_buffer {
6195 return None;
6196 }
6197
6198 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6199 cx.background_executor()
6200 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6201 .await;
6202
6203 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6204 let providers = this.code_action_providers.clone();
6205 let tasks = this
6206 .code_action_providers
6207 .iter()
6208 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6209 .collect::<Vec<_>>();
6210 (providers, tasks)
6211 })?;
6212
6213 let mut actions = Vec::new();
6214 for (provider, provider_actions) in
6215 providers.into_iter().zip(future::join_all(tasks).await)
6216 {
6217 if let Some(provider_actions) = provider_actions.log_err() {
6218 actions.extend(provider_actions.into_iter().map(|action| {
6219 AvailableCodeAction {
6220 excerpt_id: newest_selection.start.excerpt_id,
6221 action,
6222 provider: provider.clone(),
6223 }
6224 }));
6225 }
6226 }
6227
6228 this.update(cx, |this, cx| {
6229 this.available_code_actions = if actions.is_empty() {
6230 None
6231 } else {
6232 Some((
6233 Location {
6234 buffer: start_buffer,
6235 range: start..end,
6236 },
6237 actions.into(),
6238 ))
6239 };
6240 cx.notify();
6241 })
6242 }));
6243 None
6244 }
6245
6246 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6247 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6248 self.show_git_blame_inline = false;
6249
6250 self.show_git_blame_inline_delay_task =
6251 Some(cx.spawn_in(window, async move |this, cx| {
6252 cx.background_executor().timer(delay).await;
6253
6254 this.update(cx, |this, cx| {
6255 this.show_git_blame_inline = true;
6256 cx.notify();
6257 })
6258 .log_err();
6259 }));
6260 }
6261 }
6262
6263 fn show_blame_popover(
6264 &mut self,
6265 blame_entry: &BlameEntry,
6266 position: gpui::Point<Pixels>,
6267 cx: &mut Context<Self>,
6268 ) {
6269 if let Some(state) = &mut self.inline_blame_popover {
6270 state.hide_task.take();
6271 cx.notify();
6272 } else {
6273 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6274 let show_task = cx.spawn(async move |editor, cx| {
6275 cx.background_executor()
6276 .timer(std::time::Duration::from_millis(delay))
6277 .await;
6278 editor
6279 .update(cx, |editor, cx| {
6280 if let Some(state) = &mut editor.inline_blame_popover {
6281 state.show_task = None;
6282 cx.notify();
6283 }
6284 })
6285 .ok();
6286 });
6287 let Some(blame) = self.blame.as_ref() else {
6288 return;
6289 };
6290 let blame = blame.read(cx);
6291 let details = blame.details_for_entry(&blame_entry);
6292 let markdown = cx.new(|cx| {
6293 Markdown::new(
6294 details
6295 .as_ref()
6296 .map(|message| message.message.clone())
6297 .unwrap_or_default(),
6298 None,
6299 None,
6300 cx,
6301 )
6302 });
6303 self.inline_blame_popover = Some(InlineBlamePopover {
6304 position,
6305 show_task: Some(show_task),
6306 hide_task: None,
6307 popover_bounds: None,
6308 popover_state: InlineBlamePopoverState {
6309 scroll_handle: ScrollHandle::new(),
6310 commit_message: details,
6311 markdown,
6312 },
6313 });
6314 }
6315 }
6316
6317 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6318 if let Some(state) = &mut self.inline_blame_popover {
6319 if state.show_task.is_some() {
6320 self.inline_blame_popover.take();
6321 cx.notify();
6322 } else {
6323 let hide_task = cx.spawn(async move |editor, cx| {
6324 cx.background_executor()
6325 .timer(std::time::Duration::from_millis(100))
6326 .await;
6327 editor
6328 .update(cx, |editor, cx| {
6329 editor.inline_blame_popover.take();
6330 cx.notify();
6331 })
6332 .ok();
6333 });
6334 state.hide_task = Some(hide_task);
6335 }
6336 }
6337 }
6338
6339 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6340 if self.pending_rename.is_some() {
6341 return None;
6342 }
6343
6344 let provider = self.semantics_provider.clone()?;
6345 let buffer = self.buffer.read(cx);
6346 let newest_selection = self.selections.newest_anchor().clone();
6347 let cursor_position = newest_selection.head();
6348 let (cursor_buffer, cursor_buffer_position) =
6349 buffer.text_anchor_for_position(cursor_position, cx)?;
6350 let (tail_buffer, tail_buffer_position) =
6351 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6352 if cursor_buffer != tail_buffer {
6353 return None;
6354 }
6355
6356 let snapshot = cursor_buffer.read(cx).snapshot();
6357 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6358 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6359 if start_word_range != end_word_range {
6360 self.document_highlights_task.take();
6361 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6362 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6363 return None;
6364 }
6365
6366 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6367 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6368 cx.background_executor()
6369 .timer(Duration::from_millis(debounce))
6370 .await;
6371
6372 let highlights = if let Some(highlights) = cx
6373 .update(|cx| {
6374 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6375 })
6376 .ok()
6377 .flatten()
6378 {
6379 highlights.await.log_err()
6380 } else {
6381 None
6382 };
6383
6384 if let Some(highlights) = highlights {
6385 this.update(cx, |this, cx| {
6386 if this.pending_rename.is_some() {
6387 return;
6388 }
6389
6390 let buffer_id = cursor_position.buffer_id;
6391 let buffer = this.buffer.read(cx);
6392 if !buffer
6393 .text_anchor_for_position(cursor_position, cx)
6394 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6395 {
6396 return;
6397 }
6398
6399 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6400 let mut write_ranges = Vec::new();
6401 let mut read_ranges = Vec::new();
6402 for highlight in highlights {
6403 for (excerpt_id, excerpt_range) in
6404 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6405 {
6406 let start = highlight
6407 .range
6408 .start
6409 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6410 let end = highlight
6411 .range
6412 .end
6413 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6414 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6415 continue;
6416 }
6417
6418 let range = Anchor {
6419 buffer_id,
6420 excerpt_id,
6421 text_anchor: start,
6422 diff_base_anchor: None,
6423 }..Anchor {
6424 buffer_id,
6425 excerpt_id,
6426 text_anchor: end,
6427 diff_base_anchor: None,
6428 };
6429 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6430 write_ranges.push(range);
6431 } else {
6432 read_ranges.push(range);
6433 }
6434 }
6435 }
6436
6437 this.highlight_background::<DocumentHighlightRead>(
6438 &read_ranges,
6439 |theme| theme.editor_document_highlight_read_background,
6440 cx,
6441 );
6442 this.highlight_background::<DocumentHighlightWrite>(
6443 &write_ranges,
6444 |theme| theme.editor_document_highlight_write_background,
6445 cx,
6446 );
6447 cx.notify();
6448 })
6449 .log_err();
6450 }
6451 }));
6452 None
6453 }
6454
6455 fn prepare_highlight_query_from_selection(
6456 &mut self,
6457 cx: &mut Context<Editor>,
6458 ) -> Option<(String, Range<Anchor>)> {
6459 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6460 return None;
6461 }
6462 if !EditorSettings::get_global(cx).selection_highlight {
6463 return None;
6464 }
6465 if self.selections.count() != 1 || self.selections.line_mode {
6466 return None;
6467 }
6468 let selection = self.selections.newest::<Point>(cx);
6469 if selection.is_empty() || selection.start.row != selection.end.row {
6470 return None;
6471 }
6472 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6473 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6474 let query = multi_buffer_snapshot
6475 .text_for_range(selection_anchor_range.clone())
6476 .collect::<String>();
6477 if query.trim().is_empty() {
6478 return None;
6479 }
6480 Some((query, selection_anchor_range))
6481 }
6482
6483 fn update_selection_occurrence_highlights(
6484 &mut self,
6485 query_text: String,
6486 query_range: Range<Anchor>,
6487 multi_buffer_range_to_query: Range<Point>,
6488 use_debounce: bool,
6489 window: &mut Window,
6490 cx: &mut Context<Editor>,
6491 ) -> Task<()> {
6492 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6493 cx.spawn_in(window, async move |editor, cx| {
6494 if use_debounce {
6495 cx.background_executor()
6496 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6497 .await;
6498 }
6499 let match_task = cx.background_spawn(async move {
6500 let buffer_ranges = multi_buffer_snapshot
6501 .range_to_buffer_ranges(multi_buffer_range_to_query)
6502 .into_iter()
6503 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6504 let mut match_ranges = Vec::new();
6505 let Ok(regex) = project::search::SearchQuery::text(
6506 query_text.clone(),
6507 false,
6508 false,
6509 false,
6510 Default::default(),
6511 Default::default(),
6512 false,
6513 None,
6514 ) else {
6515 return Vec::default();
6516 };
6517 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6518 match_ranges.extend(
6519 regex
6520 .search(&buffer_snapshot, Some(search_range.clone()))
6521 .await
6522 .into_iter()
6523 .filter_map(|match_range| {
6524 let match_start = buffer_snapshot
6525 .anchor_after(search_range.start + match_range.start);
6526 let match_end = buffer_snapshot
6527 .anchor_before(search_range.start + match_range.end);
6528 let match_anchor_range = Anchor::range_in_buffer(
6529 excerpt_id,
6530 buffer_snapshot.remote_id(),
6531 match_start..match_end,
6532 );
6533 (match_anchor_range != query_range).then_some(match_anchor_range)
6534 }),
6535 );
6536 }
6537 match_ranges
6538 });
6539 let match_ranges = match_task.await;
6540 editor
6541 .update_in(cx, |editor, _, cx| {
6542 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6543 if !match_ranges.is_empty() {
6544 editor.highlight_background::<SelectedTextHighlight>(
6545 &match_ranges,
6546 |theme| theme.editor_document_highlight_bracket_background,
6547 cx,
6548 )
6549 }
6550 })
6551 .log_err();
6552 })
6553 }
6554
6555 fn refresh_selected_text_highlights(
6556 &mut self,
6557 on_buffer_edit: bool,
6558 window: &mut Window,
6559 cx: &mut Context<Editor>,
6560 ) {
6561 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6562 else {
6563 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6564 self.quick_selection_highlight_task.take();
6565 self.debounced_selection_highlight_task.take();
6566 return;
6567 };
6568 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6569 if on_buffer_edit
6570 || self
6571 .quick_selection_highlight_task
6572 .as_ref()
6573 .map_or(true, |(prev_anchor_range, _)| {
6574 prev_anchor_range != &query_range
6575 })
6576 {
6577 let multi_buffer_visible_start = self
6578 .scroll_manager
6579 .anchor()
6580 .anchor
6581 .to_point(&multi_buffer_snapshot);
6582 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6583 multi_buffer_visible_start
6584 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6585 Bias::Left,
6586 );
6587 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6588 self.quick_selection_highlight_task = Some((
6589 query_range.clone(),
6590 self.update_selection_occurrence_highlights(
6591 query_text.clone(),
6592 query_range.clone(),
6593 multi_buffer_visible_range,
6594 false,
6595 window,
6596 cx,
6597 ),
6598 ));
6599 }
6600 if on_buffer_edit
6601 || self
6602 .debounced_selection_highlight_task
6603 .as_ref()
6604 .map_or(true, |(prev_anchor_range, _)| {
6605 prev_anchor_range != &query_range
6606 })
6607 {
6608 let multi_buffer_start = multi_buffer_snapshot
6609 .anchor_before(0)
6610 .to_point(&multi_buffer_snapshot);
6611 let multi_buffer_end = multi_buffer_snapshot
6612 .anchor_after(multi_buffer_snapshot.len())
6613 .to_point(&multi_buffer_snapshot);
6614 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6615 self.debounced_selection_highlight_task = Some((
6616 query_range.clone(),
6617 self.update_selection_occurrence_highlights(
6618 query_text,
6619 query_range,
6620 multi_buffer_full_range,
6621 true,
6622 window,
6623 cx,
6624 ),
6625 ));
6626 }
6627 }
6628
6629 pub fn refresh_inline_completion(
6630 &mut self,
6631 debounce: bool,
6632 user_requested: bool,
6633 window: &mut Window,
6634 cx: &mut Context<Self>,
6635 ) -> Option<()> {
6636 let provider = self.edit_prediction_provider()?;
6637 let cursor = self.selections.newest_anchor().head();
6638 let (buffer, cursor_buffer_position) =
6639 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6640
6641 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6642 self.discard_inline_completion(false, cx);
6643 return None;
6644 }
6645
6646 if !user_requested
6647 && (!self.should_show_edit_predictions()
6648 || !self.is_focused(window)
6649 || buffer.read(cx).is_empty())
6650 {
6651 self.discard_inline_completion(false, cx);
6652 return None;
6653 }
6654
6655 self.update_visible_inline_completion(window, cx);
6656 provider.refresh(
6657 self.project.clone(),
6658 buffer,
6659 cursor_buffer_position,
6660 debounce,
6661 cx,
6662 );
6663 Some(())
6664 }
6665
6666 fn show_edit_predictions_in_menu(&self) -> bool {
6667 match self.edit_prediction_settings {
6668 EditPredictionSettings::Disabled => false,
6669 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6670 }
6671 }
6672
6673 pub fn edit_predictions_enabled(&self) -> bool {
6674 match self.edit_prediction_settings {
6675 EditPredictionSettings::Disabled => false,
6676 EditPredictionSettings::Enabled { .. } => true,
6677 }
6678 }
6679
6680 fn edit_prediction_requires_modifier(&self) -> bool {
6681 match self.edit_prediction_settings {
6682 EditPredictionSettings::Disabled => false,
6683 EditPredictionSettings::Enabled {
6684 preview_requires_modifier,
6685 ..
6686 } => preview_requires_modifier,
6687 }
6688 }
6689
6690 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6691 if self.edit_prediction_provider.is_none() {
6692 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6693 } else {
6694 let selection = self.selections.newest_anchor();
6695 let cursor = selection.head();
6696
6697 if let Some((buffer, cursor_buffer_position)) =
6698 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6699 {
6700 self.edit_prediction_settings =
6701 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6702 }
6703 }
6704 }
6705
6706 fn edit_prediction_settings_at_position(
6707 &self,
6708 buffer: &Entity<Buffer>,
6709 buffer_position: language::Anchor,
6710 cx: &App,
6711 ) -> EditPredictionSettings {
6712 if !self.mode.is_full()
6713 || !self.show_inline_completions_override.unwrap_or(true)
6714 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6715 {
6716 return EditPredictionSettings::Disabled;
6717 }
6718
6719 let buffer = buffer.read(cx);
6720
6721 let file = buffer.file();
6722
6723 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6724 return EditPredictionSettings::Disabled;
6725 };
6726
6727 let by_provider = matches!(
6728 self.menu_inline_completions_policy,
6729 MenuInlineCompletionsPolicy::ByProvider
6730 );
6731
6732 let show_in_menu = by_provider
6733 && self
6734 .edit_prediction_provider
6735 .as_ref()
6736 .map_or(false, |provider| {
6737 provider.provider.show_completions_in_menu()
6738 });
6739
6740 let preview_requires_modifier =
6741 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6742
6743 EditPredictionSettings::Enabled {
6744 show_in_menu,
6745 preview_requires_modifier,
6746 }
6747 }
6748
6749 fn should_show_edit_predictions(&self) -> bool {
6750 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6751 }
6752
6753 pub fn edit_prediction_preview_is_active(&self) -> bool {
6754 matches!(
6755 self.edit_prediction_preview,
6756 EditPredictionPreview::Active { .. }
6757 )
6758 }
6759
6760 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6761 let cursor = self.selections.newest_anchor().head();
6762 if let Some((buffer, cursor_position)) =
6763 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6764 {
6765 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6766 } else {
6767 false
6768 }
6769 }
6770
6771 pub fn supports_minimap(&self, cx: &App) -> bool {
6772 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6773 }
6774
6775 fn edit_predictions_enabled_in_buffer(
6776 &self,
6777 buffer: &Entity<Buffer>,
6778 buffer_position: language::Anchor,
6779 cx: &App,
6780 ) -> bool {
6781 maybe!({
6782 if self.read_only(cx) {
6783 return Some(false);
6784 }
6785 let provider = self.edit_prediction_provider()?;
6786 if !provider.is_enabled(&buffer, buffer_position, cx) {
6787 return Some(false);
6788 }
6789 let buffer = buffer.read(cx);
6790 let Some(file) = buffer.file() else {
6791 return Some(true);
6792 };
6793 let settings = all_language_settings(Some(file), cx);
6794 Some(settings.edit_predictions_enabled_for_file(file, cx))
6795 })
6796 .unwrap_or(false)
6797 }
6798
6799 fn cycle_inline_completion(
6800 &mut self,
6801 direction: Direction,
6802 window: &mut Window,
6803 cx: &mut Context<Self>,
6804 ) -> Option<()> {
6805 let provider = self.edit_prediction_provider()?;
6806 let cursor = self.selections.newest_anchor().head();
6807 let (buffer, cursor_buffer_position) =
6808 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6809 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6810 return None;
6811 }
6812
6813 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6814 self.update_visible_inline_completion(window, cx);
6815
6816 Some(())
6817 }
6818
6819 pub fn show_inline_completion(
6820 &mut self,
6821 _: &ShowEditPrediction,
6822 window: &mut Window,
6823 cx: &mut Context<Self>,
6824 ) {
6825 if !self.has_active_inline_completion() {
6826 self.refresh_inline_completion(false, true, window, cx);
6827 return;
6828 }
6829
6830 self.update_visible_inline_completion(window, cx);
6831 }
6832
6833 pub fn display_cursor_names(
6834 &mut self,
6835 _: &DisplayCursorNames,
6836 window: &mut Window,
6837 cx: &mut Context<Self>,
6838 ) {
6839 self.show_cursor_names(window, cx);
6840 }
6841
6842 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6843 self.show_cursor_names = true;
6844 cx.notify();
6845 cx.spawn_in(window, async move |this, cx| {
6846 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6847 this.update(cx, |this, cx| {
6848 this.show_cursor_names = false;
6849 cx.notify()
6850 })
6851 .ok()
6852 })
6853 .detach();
6854 }
6855
6856 pub fn next_edit_prediction(
6857 &mut self,
6858 _: &NextEditPrediction,
6859 window: &mut Window,
6860 cx: &mut Context<Self>,
6861 ) {
6862 if self.has_active_inline_completion() {
6863 self.cycle_inline_completion(Direction::Next, window, cx);
6864 } else {
6865 let is_copilot_disabled = self
6866 .refresh_inline_completion(false, true, window, cx)
6867 .is_none();
6868 if is_copilot_disabled {
6869 cx.propagate();
6870 }
6871 }
6872 }
6873
6874 pub fn previous_edit_prediction(
6875 &mut self,
6876 _: &PreviousEditPrediction,
6877 window: &mut Window,
6878 cx: &mut Context<Self>,
6879 ) {
6880 if self.has_active_inline_completion() {
6881 self.cycle_inline_completion(Direction::Prev, window, cx);
6882 } else {
6883 let is_copilot_disabled = self
6884 .refresh_inline_completion(false, true, window, cx)
6885 .is_none();
6886 if is_copilot_disabled {
6887 cx.propagate();
6888 }
6889 }
6890 }
6891
6892 pub fn accept_edit_prediction(
6893 &mut self,
6894 _: &AcceptEditPrediction,
6895 window: &mut Window,
6896 cx: &mut Context<Self>,
6897 ) {
6898 if self.show_edit_predictions_in_menu() {
6899 self.hide_context_menu(window, cx);
6900 }
6901
6902 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6903 return;
6904 };
6905
6906 self.report_inline_completion_event(
6907 active_inline_completion.completion_id.clone(),
6908 true,
6909 cx,
6910 );
6911
6912 match &active_inline_completion.completion {
6913 InlineCompletion::Move { target, .. } => {
6914 let target = *target;
6915
6916 if let Some(position_map) = &self.last_position_map {
6917 if position_map
6918 .visible_row_range
6919 .contains(&target.to_display_point(&position_map.snapshot).row())
6920 || !self.edit_prediction_requires_modifier()
6921 {
6922 self.unfold_ranges(&[target..target], true, false, cx);
6923 // Note that this is also done in vim's handler of the Tab action.
6924 self.change_selections(
6925 Some(Autoscroll::newest()),
6926 window,
6927 cx,
6928 |selections| {
6929 selections.select_anchor_ranges([target..target]);
6930 },
6931 );
6932 self.clear_row_highlights::<EditPredictionPreview>();
6933
6934 self.edit_prediction_preview
6935 .set_previous_scroll_position(None);
6936 } else {
6937 self.edit_prediction_preview
6938 .set_previous_scroll_position(Some(
6939 position_map.snapshot.scroll_anchor,
6940 ));
6941
6942 self.highlight_rows::<EditPredictionPreview>(
6943 target..target,
6944 cx.theme().colors().editor_highlighted_line_background,
6945 RowHighlightOptions {
6946 autoscroll: true,
6947 ..Default::default()
6948 },
6949 cx,
6950 );
6951 self.request_autoscroll(Autoscroll::fit(), cx);
6952 }
6953 }
6954 }
6955 InlineCompletion::Edit { edits, .. } => {
6956 if let Some(provider) = self.edit_prediction_provider() {
6957 provider.accept(cx);
6958 }
6959
6960 // Store the transaction ID and selections before applying the edit
6961 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6962
6963 let snapshot = self.buffer.read(cx).snapshot(cx);
6964 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6965
6966 self.buffer.update(cx, |buffer, cx| {
6967 buffer.edit(edits.iter().cloned(), None, cx)
6968 });
6969
6970 self.change_selections(None, window, cx, |s| {
6971 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6972 });
6973
6974 let selections = self.selections.disjoint_anchors();
6975 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6976 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6977 if has_new_transaction {
6978 self.selection_history
6979 .insert_transaction(transaction_id_now, selections);
6980 }
6981 }
6982
6983 self.update_visible_inline_completion(window, cx);
6984 if self.active_inline_completion.is_none() {
6985 self.refresh_inline_completion(true, true, window, cx);
6986 }
6987
6988 cx.notify();
6989 }
6990 }
6991
6992 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6993 }
6994
6995 pub fn accept_partial_inline_completion(
6996 &mut self,
6997 _: &AcceptPartialEditPrediction,
6998 window: &mut Window,
6999 cx: &mut Context<Self>,
7000 ) {
7001 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7002 return;
7003 };
7004 if self.selections.count() != 1 {
7005 return;
7006 }
7007
7008 self.report_inline_completion_event(
7009 active_inline_completion.completion_id.clone(),
7010 true,
7011 cx,
7012 );
7013
7014 match &active_inline_completion.completion {
7015 InlineCompletion::Move { target, .. } => {
7016 let target = *target;
7017 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7018 selections.select_anchor_ranges([target..target]);
7019 });
7020 }
7021 InlineCompletion::Edit { edits, .. } => {
7022 // Find an insertion that starts at the cursor position.
7023 let snapshot = self.buffer.read(cx).snapshot(cx);
7024 let cursor_offset = self.selections.newest::<usize>(cx).head();
7025 let insertion = edits.iter().find_map(|(range, text)| {
7026 let range = range.to_offset(&snapshot);
7027 if range.is_empty() && range.start == cursor_offset {
7028 Some(text)
7029 } else {
7030 None
7031 }
7032 });
7033
7034 if let Some(text) = insertion {
7035 let mut partial_completion = text
7036 .chars()
7037 .by_ref()
7038 .take_while(|c| c.is_alphabetic())
7039 .collect::<String>();
7040 if partial_completion.is_empty() {
7041 partial_completion = text
7042 .chars()
7043 .by_ref()
7044 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7045 .collect::<String>();
7046 }
7047
7048 cx.emit(EditorEvent::InputHandled {
7049 utf16_range_to_replace: None,
7050 text: partial_completion.clone().into(),
7051 });
7052
7053 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7054
7055 self.refresh_inline_completion(true, true, window, cx);
7056 cx.notify();
7057 } else {
7058 self.accept_edit_prediction(&Default::default(), window, cx);
7059 }
7060 }
7061 }
7062 }
7063
7064 fn discard_inline_completion(
7065 &mut self,
7066 should_report_inline_completion_event: bool,
7067 cx: &mut Context<Self>,
7068 ) -> bool {
7069 if should_report_inline_completion_event {
7070 let completion_id = self
7071 .active_inline_completion
7072 .as_ref()
7073 .and_then(|active_completion| active_completion.completion_id.clone());
7074
7075 self.report_inline_completion_event(completion_id, false, cx);
7076 }
7077
7078 if let Some(provider) = self.edit_prediction_provider() {
7079 provider.discard(cx);
7080 }
7081
7082 self.take_active_inline_completion(cx)
7083 }
7084
7085 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7086 let Some(provider) = self.edit_prediction_provider() else {
7087 return;
7088 };
7089
7090 let Some((_, buffer, _)) = self
7091 .buffer
7092 .read(cx)
7093 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7094 else {
7095 return;
7096 };
7097
7098 let extension = buffer
7099 .read(cx)
7100 .file()
7101 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7102
7103 let event_type = match accepted {
7104 true => "Edit Prediction Accepted",
7105 false => "Edit Prediction Discarded",
7106 };
7107 telemetry::event!(
7108 event_type,
7109 provider = provider.name(),
7110 prediction_id = id,
7111 suggestion_accepted = accepted,
7112 file_extension = extension,
7113 );
7114 }
7115
7116 pub fn has_active_inline_completion(&self) -> bool {
7117 self.active_inline_completion.is_some()
7118 }
7119
7120 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7121 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7122 return false;
7123 };
7124
7125 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7126 self.clear_highlights::<InlineCompletionHighlight>(cx);
7127 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7128 true
7129 }
7130
7131 /// Returns true when we're displaying the edit prediction popover below the cursor
7132 /// like we are not previewing and the LSP autocomplete menu is visible
7133 /// or we are in `when_holding_modifier` mode.
7134 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7135 if self.edit_prediction_preview_is_active()
7136 || !self.show_edit_predictions_in_menu()
7137 || !self.edit_predictions_enabled()
7138 {
7139 return false;
7140 }
7141
7142 if self.has_visible_completions_menu() {
7143 return true;
7144 }
7145
7146 has_completion && self.edit_prediction_requires_modifier()
7147 }
7148
7149 fn handle_modifiers_changed(
7150 &mut self,
7151 modifiers: Modifiers,
7152 position_map: &PositionMap,
7153 window: &mut Window,
7154 cx: &mut Context<Self>,
7155 ) {
7156 if self.show_edit_predictions_in_menu() {
7157 self.update_edit_prediction_preview(&modifiers, window, cx);
7158 }
7159
7160 self.update_selection_mode(&modifiers, position_map, window, cx);
7161
7162 let mouse_position = window.mouse_position();
7163 if !position_map.text_hitbox.is_hovered(window) {
7164 return;
7165 }
7166
7167 self.update_hovered_link(
7168 position_map.point_for_position(mouse_position),
7169 &position_map.snapshot,
7170 modifiers,
7171 window,
7172 cx,
7173 )
7174 }
7175
7176 fn multi_cursor_modifier(
7177 cursor_event: bool,
7178 modifiers: &Modifiers,
7179 cx: &mut Context<Self>,
7180 ) -> bool {
7181 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7182 if cursor_event {
7183 match multi_cursor_setting {
7184 MultiCursorModifier::Alt => modifiers.alt,
7185 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7186 }
7187 } else {
7188 match multi_cursor_setting {
7189 MultiCursorModifier::Alt => modifiers.secondary(),
7190 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7191 }
7192 }
7193 }
7194
7195 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7196 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7197 }
7198
7199 fn update_selection_mode(
7200 &mut self,
7201 modifiers: &Modifiers,
7202 position_map: &PositionMap,
7203 window: &mut Window,
7204 cx: &mut Context<Self>,
7205 ) {
7206 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7207 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7208 || self.selections.pending.is_none()
7209 {
7210 return;
7211 }
7212
7213 let mouse_position = window.mouse_position();
7214 let point_for_position = position_map.point_for_position(mouse_position);
7215 let position = point_for_position.previous_valid;
7216
7217 self.select(
7218 SelectPhase::BeginColumnar {
7219 position,
7220 reset: false,
7221 goal_column: point_for_position.exact_unclipped.column(),
7222 },
7223 window,
7224 cx,
7225 );
7226 }
7227
7228 fn update_edit_prediction_preview(
7229 &mut self,
7230 modifiers: &Modifiers,
7231 window: &mut Window,
7232 cx: &mut Context<Self>,
7233 ) {
7234 let mut modifiers_held = false;
7235 if let Some(accept_keystroke) = self
7236 .accept_edit_prediction_keybind(false, window, cx)
7237 .keystroke()
7238 {
7239 modifiers_held = modifiers_held
7240 || (&accept_keystroke.modifiers == modifiers
7241 && accept_keystroke.modifiers.modified());
7242 };
7243 if let Some(accept_partial_keystroke) = self
7244 .accept_edit_prediction_keybind(true, window, cx)
7245 .keystroke()
7246 {
7247 modifiers_held = modifiers_held
7248 || (&accept_partial_keystroke.modifiers == modifiers
7249 && accept_partial_keystroke.modifiers.modified());
7250 }
7251
7252 if modifiers_held {
7253 if matches!(
7254 self.edit_prediction_preview,
7255 EditPredictionPreview::Inactive { .. }
7256 ) {
7257 self.edit_prediction_preview = EditPredictionPreview::Active {
7258 previous_scroll_position: None,
7259 since: Instant::now(),
7260 };
7261
7262 self.update_visible_inline_completion(window, cx);
7263 cx.notify();
7264 }
7265 } else if let EditPredictionPreview::Active {
7266 previous_scroll_position,
7267 since,
7268 } = self.edit_prediction_preview
7269 {
7270 if let (Some(previous_scroll_position), Some(position_map)) =
7271 (previous_scroll_position, self.last_position_map.as_ref())
7272 {
7273 self.set_scroll_position(
7274 previous_scroll_position
7275 .scroll_position(&position_map.snapshot.display_snapshot),
7276 window,
7277 cx,
7278 );
7279 }
7280
7281 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7282 released_too_fast: since.elapsed() < Duration::from_millis(200),
7283 };
7284 self.clear_row_highlights::<EditPredictionPreview>();
7285 self.update_visible_inline_completion(window, cx);
7286 cx.notify();
7287 }
7288 }
7289
7290 fn update_visible_inline_completion(
7291 &mut self,
7292 _window: &mut Window,
7293 cx: &mut Context<Self>,
7294 ) -> Option<()> {
7295 let selection = self.selections.newest_anchor();
7296 let cursor = selection.head();
7297 let multibuffer = self.buffer.read(cx).snapshot(cx);
7298 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7299 let excerpt_id = cursor.excerpt_id;
7300
7301 let show_in_menu = self.show_edit_predictions_in_menu();
7302 let completions_menu_has_precedence = !show_in_menu
7303 && (self.context_menu.borrow().is_some()
7304 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7305
7306 if completions_menu_has_precedence
7307 || !offset_selection.is_empty()
7308 || self
7309 .active_inline_completion
7310 .as_ref()
7311 .map_or(false, |completion| {
7312 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7313 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7314 !invalidation_range.contains(&offset_selection.head())
7315 })
7316 {
7317 self.discard_inline_completion(false, cx);
7318 return None;
7319 }
7320
7321 self.take_active_inline_completion(cx);
7322 let Some(provider) = self.edit_prediction_provider() else {
7323 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7324 return None;
7325 };
7326
7327 let (buffer, cursor_buffer_position) =
7328 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7329
7330 self.edit_prediction_settings =
7331 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7332
7333 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7334
7335 if self.edit_prediction_indent_conflict {
7336 let cursor_point = cursor.to_point(&multibuffer);
7337
7338 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7339
7340 if let Some((_, indent)) = indents.iter().next() {
7341 if indent.len == cursor_point.column {
7342 self.edit_prediction_indent_conflict = false;
7343 }
7344 }
7345 }
7346
7347 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7348 let edits = inline_completion
7349 .edits
7350 .into_iter()
7351 .flat_map(|(range, new_text)| {
7352 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7353 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7354 Some((start..end, new_text))
7355 })
7356 .collect::<Vec<_>>();
7357 if edits.is_empty() {
7358 return None;
7359 }
7360
7361 let first_edit_start = edits.first().unwrap().0.start;
7362 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7363 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7364
7365 let last_edit_end = edits.last().unwrap().0.end;
7366 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7367 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7368
7369 let cursor_row = cursor.to_point(&multibuffer).row;
7370
7371 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7372
7373 let mut inlay_ids = Vec::new();
7374 let invalidation_row_range;
7375 let move_invalidation_row_range = if cursor_row < edit_start_row {
7376 Some(cursor_row..edit_end_row)
7377 } else if cursor_row > edit_end_row {
7378 Some(edit_start_row..cursor_row)
7379 } else {
7380 None
7381 };
7382 let is_move =
7383 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7384 let completion = if is_move {
7385 invalidation_row_range =
7386 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7387 let target = first_edit_start;
7388 InlineCompletion::Move { target, snapshot }
7389 } else {
7390 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7391 && !self.inline_completions_hidden_for_vim_mode;
7392
7393 if show_completions_in_buffer {
7394 if edits
7395 .iter()
7396 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7397 {
7398 let mut inlays = Vec::new();
7399 for (range, new_text) in &edits {
7400 let inlay = Inlay::inline_completion(
7401 post_inc(&mut self.next_inlay_id),
7402 range.start,
7403 new_text.as_str(),
7404 );
7405 inlay_ids.push(inlay.id);
7406 inlays.push(inlay);
7407 }
7408
7409 self.splice_inlays(&[], inlays, cx);
7410 } else {
7411 let background_color = cx.theme().status().deleted_background;
7412 self.highlight_text::<InlineCompletionHighlight>(
7413 edits.iter().map(|(range, _)| range.clone()).collect(),
7414 HighlightStyle {
7415 background_color: Some(background_color),
7416 ..Default::default()
7417 },
7418 cx,
7419 );
7420 }
7421 }
7422
7423 invalidation_row_range = edit_start_row..edit_end_row;
7424
7425 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7426 if provider.show_tab_accept_marker() {
7427 EditDisplayMode::TabAccept
7428 } else {
7429 EditDisplayMode::Inline
7430 }
7431 } else {
7432 EditDisplayMode::DiffPopover
7433 };
7434
7435 InlineCompletion::Edit {
7436 edits,
7437 edit_preview: inline_completion.edit_preview,
7438 display_mode,
7439 snapshot,
7440 }
7441 };
7442
7443 let invalidation_range = multibuffer
7444 .anchor_before(Point::new(invalidation_row_range.start, 0))
7445 ..multibuffer.anchor_after(Point::new(
7446 invalidation_row_range.end,
7447 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7448 ));
7449
7450 self.stale_inline_completion_in_menu = None;
7451 self.active_inline_completion = Some(InlineCompletionState {
7452 inlay_ids,
7453 completion,
7454 completion_id: inline_completion.id,
7455 invalidation_range,
7456 });
7457
7458 cx.notify();
7459
7460 Some(())
7461 }
7462
7463 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7464 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7465 }
7466
7467 fn clear_tasks(&mut self) {
7468 self.tasks.clear()
7469 }
7470
7471 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7472 if self.tasks.insert(key, value).is_some() {
7473 // This case should hopefully be rare, but just in case...
7474 log::error!(
7475 "multiple different run targets found on a single line, only the last target will be rendered"
7476 )
7477 }
7478 }
7479
7480 /// Get all display points of breakpoints that will be rendered within editor
7481 ///
7482 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7483 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7484 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7485 fn active_breakpoints(
7486 &self,
7487 range: Range<DisplayRow>,
7488 window: &mut Window,
7489 cx: &mut Context<Self>,
7490 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7491 let mut breakpoint_display_points = HashMap::default();
7492
7493 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7494 return breakpoint_display_points;
7495 };
7496
7497 let snapshot = self.snapshot(window, cx);
7498
7499 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7500 let Some(project) = self.project.as_ref() else {
7501 return breakpoint_display_points;
7502 };
7503
7504 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7505 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7506
7507 for (buffer_snapshot, range, excerpt_id) in
7508 multi_buffer_snapshot.range_to_buffer_ranges(range)
7509 {
7510 let Some(buffer) = project
7511 .read(cx)
7512 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7513 else {
7514 continue;
7515 };
7516 let breakpoints = breakpoint_store.read(cx).breakpoints(
7517 &buffer,
7518 Some(
7519 buffer_snapshot.anchor_before(range.start)
7520 ..buffer_snapshot.anchor_after(range.end),
7521 ),
7522 buffer_snapshot,
7523 cx,
7524 );
7525 for (breakpoint, state) in breakpoints {
7526 let multi_buffer_anchor =
7527 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7528 let position = multi_buffer_anchor
7529 .to_point(&multi_buffer_snapshot)
7530 .to_display_point(&snapshot);
7531
7532 breakpoint_display_points.insert(
7533 position.row(),
7534 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7535 );
7536 }
7537 }
7538
7539 breakpoint_display_points
7540 }
7541
7542 fn breakpoint_context_menu(
7543 &self,
7544 anchor: Anchor,
7545 window: &mut Window,
7546 cx: &mut Context<Self>,
7547 ) -> Entity<ui::ContextMenu> {
7548 let weak_editor = cx.weak_entity();
7549 let focus_handle = self.focus_handle(cx);
7550
7551 let row = self
7552 .buffer
7553 .read(cx)
7554 .snapshot(cx)
7555 .summary_for_anchor::<Point>(&anchor)
7556 .row;
7557
7558 let breakpoint = self
7559 .breakpoint_at_row(row, window, cx)
7560 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7561
7562 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7563 "Edit Log Breakpoint"
7564 } else {
7565 "Set Log Breakpoint"
7566 };
7567
7568 let condition_breakpoint_msg = if breakpoint
7569 .as_ref()
7570 .is_some_and(|bp| bp.1.condition.is_some())
7571 {
7572 "Edit Condition Breakpoint"
7573 } else {
7574 "Set Condition Breakpoint"
7575 };
7576
7577 let hit_condition_breakpoint_msg = if breakpoint
7578 .as_ref()
7579 .is_some_and(|bp| bp.1.hit_condition.is_some())
7580 {
7581 "Edit Hit Condition Breakpoint"
7582 } else {
7583 "Set Hit Condition Breakpoint"
7584 };
7585
7586 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7587 "Unset Breakpoint"
7588 } else {
7589 "Set Breakpoint"
7590 };
7591
7592 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7593
7594 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7595 BreakpointState::Enabled => Some("Disable"),
7596 BreakpointState::Disabled => Some("Enable"),
7597 });
7598
7599 let (anchor, breakpoint) =
7600 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7601
7602 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7603 menu.on_blur_subscription(Subscription::new(|| {}))
7604 .context(focus_handle)
7605 .when(run_to_cursor, |this| {
7606 let weak_editor = weak_editor.clone();
7607 this.entry("Run to cursor", None, move |window, cx| {
7608 weak_editor
7609 .update(cx, |editor, cx| {
7610 editor.change_selections(None, window, cx, |s| {
7611 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7612 });
7613 })
7614 .ok();
7615
7616 window.dispatch_action(Box::new(RunToCursor), cx);
7617 })
7618 .separator()
7619 })
7620 .when_some(toggle_state_msg, |this, msg| {
7621 this.entry(msg, None, {
7622 let weak_editor = weak_editor.clone();
7623 let breakpoint = breakpoint.clone();
7624 move |_window, cx| {
7625 weak_editor
7626 .update(cx, |this, cx| {
7627 this.edit_breakpoint_at_anchor(
7628 anchor,
7629 breakpoint.as_ref().clone(),
7630 BreakpointEditAction::InvertState,
7631 cx,
7632 );
7633 })
7634 .log_err();
7635 }
7636 })
7637 })
7638 .entry(set_breakpoint_msg, None, {
7639 let weak_editor = weak_editor.clone();
7640 let breakpoint = breakpoint.clone();
7641 move |_window, cx| {
7642 weak_editor
7643 .update(cx, |this, cx| {
7644 this.edit_breakpoint_at_anchor(
7645 anchor,
7646 breakpoint.as_ref().clone(),
7647 BreakpointEditAction::Toggle,
7648 cx,
7649 );
7650 })
7651 .log_err();
7652 }
7653 })
7654 .entry(log_breakpoint_msg, None, {
7655 let breakpoint = breakpoint.clone();
7656 let weak_editor = weak_editor.clone();
7657 move |window, cx| {
7658 weak_editor
7659 .update(cx, |this, cx| {
7660 this.add_edit_breakpoint_block(
7661 anchor,
7662 breakpoint.as_ref(),
7663 BreakpointPromptEditAction::Log,
7664 window,
7665 cx,
7666 );
7667 })
7668 .log_err();
7669 }
7670 })
7671 .entry(condition_breakpoint_msg, None, {
7672 let breakpoint = breakpoint.clone();
7673 let weak_editor = weak_editor.clone();
7674 move |window, cx| {
7675 weak_editor
7676 .update(cx, |this, cx| {
7677 this.add_edit_breakpoint_block(
7678 anchor,
7679 breakpoint.as_ref(),
7680 BreakpointPromptEditAction::Condition,
7681 window,
7682 cx,
7683 );
7684 })
7685 .log_err();
7686 }
7687 })
7688 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7689 weak_editor
7690 .update(cx, |this, cx| {
7691 this.add_edit_breakpoint_block(
7692 anchor,
7693 breakpoint.as_ref(),
7694 BreakpointPromptEditAction::HitCondition,
7695 window,
7696 cx,
7697 );
7698 })
7699 .log_err();
7700 })
7701 })
7702 }
7703
7704 fn render_breakpoint(
7705 &self,
7706 position: Anchor,
7707 row: DisplayRow,
7708 breakpoint: &Breakpoint,
7709 state: Option<BreakpointSessionState>,
7710 cx: &mut Context<Self>,
7711 ) -> IconButton {
7712 let is_rejected = state.is_some_and(|s| !s.verified);
7713 // Is it a breakpoint that shows up when hovering over gutter?
7714 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7715 (false, false),
7716 |PhantomBreakpointIndicator {
7717 is_active,
7718 display_row,
7719 collides_with_existing_breakpoint,
7720 }| {
7721 (
7722 is_active && display_row == row,
7723 collides_with_existing_breakpoint,
7724 )
7725 },
7726 );
7727
7728 let (color, icon) = {
7729 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7730 (false, false) => ui::IconName::DebugBreakpoint,
7731 (true, false) => ui::IconName::DebugLogBreakpoint,
7732 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7733 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7734 };
7735
7736 let color = if is_phantom {
7737 Color::Hint
7738 } else if is_rejected {
7739 Color::Disabled
7740 } else {
7741 Color::Debugger
7742 };
7743
7744 (color, icon)
7745 };
7746
7747 let breakpoint = Arc::from(breakpoint.clone());
7748
7749 let alt_as_text = gpui::Keystroke {
7750 modifiers: Modifiers::secondary_key(),
7751 ..Default::default()
7752 };
7753 let primary_action_text = if breakpoint.is_disabled() {
7754 "Enable breakpoint"
7755 } else if is_phantom && !collides_with_existing {
7756 "Set breakpoint"
7757 } else {
7758 "Unset breakpoint"
7759 };
7760 let focus_handle = self.focus_handle.clone();
7761
7762 let meta = if is_rejected {
7763 SharedString::from("No executable code is associated with this line.")
7764 } else if collides_with_existing && !breakpoint.is_disabled() {
7765 SharedString::from(format!(
7766 "{alt_as_text}-click to disable,\nright-click for more options."
7767 ))
7768 } else {
7769 SharedString::from("Right-click for more options.")
7770 };
7771 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7772 .icon_size(IconSize::XSmall)
7773 .size(ui::ButtonSize::None)
7774 .when(is_rejected, |this| {
7775 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7776 })
7777 .icon_color(color)
7778 .style(ButtonStyle::Transparent)
7779 .on_click(cx.listener({
7780 let breakpoint = breakpoint.clone();
7781
7782 move |editor, event: &ClickEvent, window, cx| {
7783 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7784 BreakpointEditAction::InvertState
7785 } else {
7786 BreakpointEditAction::Toggle
7787 };
7788
7789 window.focus(&editor.focus_handle(cx));
7790 editor.edit_breakpoint_at_anchor(
7791 position,
7792 breakpoint.as_ref().clone(),
7793 edit_action,
7794 cx,
7795 );
7796 }
7797 }))
7798 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7799 editor.set_breakpoint_context_menu(
7800 row,
7801 Some(position),
7802 event.down.position,
7803 window,
7804 cx,
7805 );
7806 }))
7807 .tooltip(move |window, cx| {
7808 Tooltip::with_meta_in(
7809 primary_action_text,
7810 Some(&ToggleBreakpoint),
7811 meta.clone(),
7812 &focus_handle,
7813 window,
7814 cx,
7815 )
7816 })
7817 }
7818
7819 fn build_tasks_context(
7820 project: &Entity<Project>,
7821 buffer: &Entity<Buffer>,
7822 buffer_row: u32,
7823 tasks: &Arc<RunnableTasks>,
7824 cx: &mut Context<Self>,
7825 ) -> Task<Option<task::TaskContext>> {
7826 let position = Point::new(buffer_row, tasks.column);
7827 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7828 let location = Location {
7829 buffer: buffer.clone(),
7830 range: range_start..range_start,
7831 };
7832 // Fill in the environmental variables from the tree-sitter captures
7833 let mut captured_task_variables = TaskVariables::default();
7834 for (capture_name, value) in tasks.extra_variables.clone() {
7835 captured_task_variables.insert(
7836 task::VariableName::Custom(capture_name.into()),
7837 value.clone(),
7838 );
7839 }
7840 project.update(cx, |project, cx| {
7841 project.task_store().update(cx, |task_store, cx| {
7842 task_store.task_context_for_location(captured_task_variables, location, cx)
7843 })
7844 })
7845 }
7846
7847 pub fn spawn_nearest_task(
7848 &mut self,
7849 action: &SpawnNearestTask,
7850 window: &mut Window,
7851 cx: &mut Context<Self>,
7852 ) {
7853 let Some((workspace, _)) = self.workspace.clone() else {
7854 return;
7855 };
7856 let Some(project) = self.project.clone() else {
7857 return;
7858 };
7859
7860 // Try to find a closest, enclosing node using tree-sitter that has a
7861 // task
7862 let Some((buffer, buffer_row, tasks)) = self
7863 .find_enclosing_node_task(cx)
7864 // Or find the task that's closest in row-distance.
7865 .or_else(|| self.find_closest_task(cx))
7866 else {
7867 return;
7868 };
7869
7870 let reveal_strategy = action.reveal;
7871 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7872 cx.spawn_in(window, async move |_, cx| {
7873 let context = task_context.await?;
7874 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7875
7876 let resolved = &mut resolved_task.resolved;
7877 resolved.reveal = reveal_strategy;
7878
7879 workspace
7880 .update_in(cx, |workspace, window, cx| {
7881 workspace.schedule_resolved_task(
7882 task_source_kind,
7883 resolved_task,
7884 false,
7885 window,
7886 cx,
7887 );
7888 })
7889 .ok()
7890 })
7891 .detach();
7892 }
7893
7894 fn find_closest_task(
7895 &mut self,
7896 cx: &mut Context<Self>,
7897 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7898 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7899
7900 let ((buffer_id, row), tasks) = self
7901 .tasks
7902 .iter()
7903 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7904
7905 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7906 let tasks = Arc::new(tasks.to_owned());
7907 Some((buffer, *row, tasks))
7908 }
7909
7910 fn find_enclosing_node_task(
7911 &mut self,
7912 cx: &mut Context<Self>,
7913 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7914 let snapshot = self.buffer.read(cx).snapshot(cx);
7915 let offset = self.selections.newest::<usize>(cx).head();
7916 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7917 let buffer_id = excerpt.buffer().remote_id();
7918
7919 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7920 let mut cursor = layer.node().walk();
7921
7922 while cursor.goto_first_child_for_byte(offset).is_some() {
7923 if cursor.node().end_byte() == offset {
7924 cursor.goto_next_sibling();
7925 }
7926 }
7927
7928 // Ascend to the smallest ancestor that contains the range and has a task.
7929 loop {
7930 let node = cursor.node();
7931 let node_range = node.byte_range();
7932 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7933
7934 // Check if this node contains our offset
7935 if node_range.start <= offset && node_range.end >= offset {
7936 // If it contains offset, check for task
7937 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7938 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7939 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7940 }
7941 }
7942
7943 if !cursor.goto_parent() {
7944 break;
7945 }
7946 }
7947 None
7948 }
7949
7950 fn render_run_indicator(
7951 &self,
7952 _style: &EditorStyle,
7953 is_active: bool,
7954 row: DisplayRow,
7955 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7956 cx: &mut Context<Self>,
7957 ) -> IconButton {
7958 let color = Color::Muted;
7959 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7960
7961 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7962 .shape(ui::IconButtonShape::Square)
7963 .icon_size(IconSize::XSmall)
7964 .icon_color(color)
7965 .toggle_state(is_active)
7966 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7967 let quick_launch = e.down.button == MouseButton::Left;
7968 window.focus(&editor.focus_handle(cx));
7969 editor.toggle_code_actions(
7970 &ToggleCodeActions {
7971 deployed_from: Some(CodeActionSource::RunMenu(row)),
7972 quick_launch,
7973 },
7974 window,
7975 cx,
7976 );
7977 }))
7978 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7979 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7980 }))
7981 }
7982
7983 pub fn context_menu_visible(&self) -> bool {
7984 !self.edit_prediction_preview_is_active()
7985 && self
7986 .context_menu
7987 .borrow()
7988 .as_ref()
7989 .map_or(false, |menu| menu.visible())
7990 }
7991
7992 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7993 self.context_menu
7994 .borrow()
7995 .as_ref()
7996 .map(|menu| menu.origin())
7997 }
7998
7999 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8000 self.context_menu_options = Some(options);
8001 }
8002
8003 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8004 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8005
8006 fn render_edit_prediction_popover(
8007 &mut self,
8008 text_bounds: &Bounds<Pixels>,
8009 content_origin: gpui::Point<Pixels>,
8010 right_margin: Pixels,
8011 editor_snapshot: &EditorSnapshot,
8012 visible_row_range: Range<DisplayRow>,
8013 scroll_top: f32,
8014 scroll_bottom: f32,
8015 line_layouts: &[LineWithInvisibles],
8016 line_height: Pixels,
8017 scroll_pixel_position: gpui::Point<Pixels>,
8018 newest_selection_head: Option<DisplayPoint>,
8019 editor_width: Pixels,
8020 style: &EditorStyle,
8021 window: &mut Window,
8022 cx: &mut App,
8023 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8024 if self.mode().is_minimap() {
8025 return None;
8026 }
8027 let active_inline_completion = self.active_inline_completion.as_ref()?;
8028
8029 if self.edit_prediction_visible_in_cursor_popover(true) {
8030 return None;
8031 }
8032
8033 match &active_inline_completion.completion {
8034 InlineCompletion::Move { target, .. } => {
8035 let target_display_point = target.to_display_point(editor_snapshot);
8036
8037 if self.edit_prediction_requires_modifier() {
8038 if !self.edit_prediction_preview_is_active() {
8039 return None;
8040 }
8041
8042 self.render_edit_prediction_modifier_jump_popover(
8043 text_bounds,
8044 content_origin,
8045 visible_row_range,
8046 line_layouts,
8047 line_height,
8048 scroll_pixel_position,
8049 newest_selection_head,
8050 target_display_point,
8051 window,
8052 cx,
8053 )
8054 } else {
8055 self.render_edit_prediction_eager_jump_popover(
8056 text_bounds,
8057 content_origin,
8058 editor_snapshot,
8059 visible_row_range,
8060 scroll_top,
8061 scroll_bottom,
8062 line_height,
8063 scroll_pixel_position,
8064 target_display_point,
8065 editor_width,
8066 window,
8067 cx,
8068 )
8069 }
8070 }
8071 InlineCompletion::Edit {
8072 display_mode: EditDisplayMode::Inline,
8073 ..
8074 } => None,
8075 InlineCompletion::Edit {
8076 display_mode: EditDisplayMode::TabAccept,
8077 edits,
8078 ..
8079 } => {
8080 let range = &edits.first()?.0;
8081 let target_display_point = range.end.to_display_point(editor_snapshot);
8082
8083 self.render_edit_prediction_end_of_line_popover(
8084 "Accept",
8085 editor_snapshot,
8086 visible_row_range,
8087 target_display_point,
8088 line_height,
8089 scroll_pixel_position,
8090 content_origin,
8091 editor_width,
8092 window,
8093 cx,
8094 )
8095 }
8096 InlineCompletion::Edit {
8097 edits,
8098 edit_preview,
8099 display_mode: EditDisplayMode::DiffPopover,
8100 snapshot,
8101 } => self.render_edit_prediction_diff_popover(
8102 text_bounds,
8103 content_origin,
8104 right_margin,
8105 editor_snapshot,
8106 visible_row_range,
8107 line_layouts,
8108 line_height,
8109 scroll_pixel_position,
8110 newest_selection_head,
8111 editor_width,
8112 style,
8113 edits,
8114 edit_preview,
8115 snapshot,
8116 window,
8117 cx,
8118 ),
8119 }
8120 }
8121
8122 fn render_edit_prediction_modifier_jump_popover(
8123 &mut self,
8124 text_bounds: &Bounds<Pixels>,
8125 content_origin: gpui::Point<Pixels>,
8126 visible_row_range: Range<DisplayRow>,
8127 line_layouts: &[LineWithInvisibles],
8128 line_height: Pixels,
8129 scroll_pixel_position: gpui::Point<Pixels>,
8130 newest_selection_head: Option<DisplayPoint>,
8131 target_display_point: DisplayPoint,
8132 window: &mut Window,
8133 cx: &mut App,
8134 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8135 let scrolled_content_origin =
8136 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8137
8138 const SCROLL_PADDING_Y: Pixels = px(12.);
8139
8140 if target_display_point.row() < visible_row_range.start {
8141 return self.render_edit_prediction_scroll_popover(
8142 |_| SCROLL_PADDING_Y,
8143 IconName::ArrowUp,
8144 visible_row_range,
8145 line_layouts,
8146 newest_selection_head,
8147 scrolled_content_origin,
8148 window,
8149 cx,
8150 );
8151 } else if target_display_point.row() >= visible_row_range.end {
8152 return self.render_edit_prediction_scroll_popover(
8153 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8154 IconName::ArrowDown,
8155 visible_row_range,
8156 line_layouts,
8157 newest_selection_head,
8158 scrolled_content_origin,
8159 window,
8160 cx,
8161 );
8162 }
8163
8164 const POLE_WIDTH: Pixels = px(2.);
8165
8166 let line_layout =
8167 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8168 let target_column = target_display_point.column() as usize;
8169
8170 let target_x = line_layout.x_for_index(target_column);
8171 let target_y =
8172 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8173
8174 let flag_on_right = target_x < text_bounds.size.width / 2.;
8175
8176 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8177 border_color.l += 0.001;
8178
8179 let mut element = v_flex()
8180 .items_end()
8181 .when(flag_on_right, |el| el.items_start())
8182 .child(if flag_on_right {
8183 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8184 .rounded_bl(px(0.))
8185 .rounded_tl(px(0.))
8186 .border_l_2()
8187 .border_color(border_color)
8188 } else {
8189 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8190 .rounded_br(px(0.))
8191 .rounded_tr(px(0.))
8192 .border_r_2()
8193 .border_color(border_color)
8194 })
8195 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8196 .into_any();
8197
8198 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8199
8200 let mut origin = scrolled_content_origin + point(target_x, target_y)
8201 - point(
8202 if flag_on_right {
8203 POLE_WIDTH
8204 } else {
8205 size.width - POLE_WIDTH
8206 },
8207 size.height - line_height,
8208 );
8209
8210 origin.x = origin.x.max(content_origin.x);
8211
8212 element.prepaint_at(origin, window, cx);
8213
8214 Some((element, origin))
8215 }
8216
8217 fn render_edit_prediction_scroll_popover(
8218 &mut self,
8219 to_y: impl Fn(Size<Pixels>) -> Pixels,
8220 scroll_icon: IconName,
8221 visible_row_range: Range<DisplayRow>,
8222 line_layouts: &[LineWithInvisibles],
8223 newest_selection_head: Option<DisplayPoint>,
8224 scrolled_content_origin: gpui::Point<Pixels>,
8225 window: &mut Window,
8226 cx: &mut App,
8227 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8228 let mut element = self
8229 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8230 .into_any();
8231
8232 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8233
8234 let cursor = newest_selection_head?;
8235 let cursor_row_layout =
8236 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8237 let cursor_column = cursor.column() as usize;
8238
8239 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8240
8241 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8242
8243 element.prepaint_at(origin, window, cx);
8244 Some((element, origin))
8245 }
8246
8247 fn render_edit_prediction_eager_jump_popover(
8248 &mut self,
8249 text_bounds: &Bounds<Pixels>,
8250 content_origin: gpui::Point<Pixels>,
8251 editor_snapshot: &EditorSnapshot,
8252 visible_row_range: Range<DisplayRow>,
8253 scroll_top: f32,
8254 scroll_bottom: f32,
8255 line_height: Pixels,
8256 scroll_pixel_position: gpui::Point<Pixels>,
8257 target_display_point: DisplayPoint,
8258 editor_width: Pixels,
8259 window: &mut Window,
8260 cx: &mut App,
8261 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8262 if target_display_point.row().as_f32() < scroll_top {
8263 let mut element = self
8264 .render_edit_prediction_line_popover(
8265 "Jump to Edit",
8266 Some(IconName::ArrowUp),
8267 window,
8268 cx,
8269 )?
8270 .into_any();
8271
8272 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8273 let offset = point(
8274 (text_bounds.size.width - size.width) / 2.,
8275 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8276 );
8277
8278 let origin = text_bounds.origin + offset;
8279 element.prepaint_at(origin, window, cx);
8280 Some((element, origin))
8281 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8282 let mut element = self
8283 .render_edit_prediction_line_popover(
8284 "Jump to Edit",
8285 Some(IconName::ArrowDown),
8286 window,
8287 cx,
8288 )?
8289 .into_any();
8290
8291 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8292 let offset = point(
8293 (text_bounds.size.width - size.width) / 2.,
8294 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8295 );
8296
8297 let origin = text_bounds.origin + offset;
8298 element.prepaint_at(origin, window, cx);
8299 Some((element, origin))
8300 } else {
8301 self.render_edit_prediction_end_of_line_popover(
8302 "Jump to Edit",
8303 editor_snapshot,
8304 visible_row_range,
8305 target_display_point,
8306 line_height,
8307 scroll_pixel_position,
8308 content_origin,
8309 editor_width,
8310 window,
8311 cx,
8312 )
8313 }
8314 }
8315
8316 fn render_edit_prediction_end_of_line_popover(
8317 self: &mut Editor,
8318 label: &'static str,
8319 editor_snapshot: &EditorSnapshot,
8320 visible_row_range: Range<DisplayRow>,
8321 target_display_point: DisplayPoint,
8322 line_height: Pixels,
8323 scroll_pixel_position: gpui::Point<Pixels>,
8324 content_origin: gpui::Point<Pixels>,
8325 editor_width: Pixels,
8326 window: &mut Window,
8327 cx: &mut App,
8328 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8329 let target_line_end = DisplayPoint::new(
8330 target_display_point.row(),
8331 editor_snapshot.line_len(target_display_point.row()),
8332 );
8333
8334 let mut element = self
8335 .render_edit_prediction_line_popover(label, None, window, cx)?
8336 .into_any();
8337
8338 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8339
8340 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8341
8342 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8343 let mut origin = start_point
8344 + line_origin
8345 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8346 origin.x = origin.x.max(content_origin.x);
8347
8348 let max_x = content_origin.x + editor_width - size.width;
8349
8350 if origin.x > max_x {
8351 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8352
8353 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8354 origin.y += offset;
8355 IconName::ArrowUp
8356 } else {
8357 origin.y -= offset;
8358 IconName::ArrowDown
8359 };
8360
8361 element = self
8362 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8363 .into_any();
8364
8365 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8366
8367 origin.x = content_origin.x + editor_width - size.width - px(2.);
8368 }
8369
8370 element.prepaint_at(origin, window, cx);
8371 Some((element, origin))
8372 }
8373
8374 fn render_edit_prediction_diff_popover(
8375 self: &Editor,
8376 text_bounds: &Bounds<Pixels>,
8377 content_origin: gpui::Point<Pixels>,
8378 right_margin: Pixels,
8379 editor_snapshot: &EditorSnapshot,
8380 visible_row_range: Range<DisplayRow>,
8381 line_layouts: &[LineWithInvisibles],
8382 line_height: Pixels,
8383 scroll_pixel_position: gpui::Point<Pixels>,
8384 newest_selection_head: Option<DisplayPoint>,
8385 editor_width: Pixels,
8386 style: &EditorStyle,
8387 edits: &Vec<(Range<Anchor>, String)>,
8388 edit_preview: &Option<language::EditPreview>,
8389 snapshot: &language::BufferSnapshot,
8390 window: &mut Window,
8391 cx: &mut App,
8392 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8393 let edit_start = edits
8394 .first()
8395 .unwrap()
8396 .0
8397 .start
8398 .to_display_point(editor_snapshot);
8399 let edit_end = edits
8400 .last()
8401 .unwrap()
8402 .0
8403 .end
8404 .to_display_point(editor_snapshot);
8405
8406 let is_visible = visible_row_range.contains(&edit_start.row())
8407 || visible_row_range.contains(&edit_end.row());
8408 if !is_visible {
8409 return None;
8410 }
8411
8412 let highlighted_edits =
8413 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8414
8415 let styled_text = highlighted_edits.to_styled_text(&style.text);
8416 let line_count = highlighted_edits.text.lines().count();
8417
8418 const BORDER_WIDTH: Pixels = px(1.);
8419
8420 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8421 let has_keybind = keybind.is_some();
8422
8423 let mut element = h_flex()
8424 .items_start()
8425 .child(
8426 h_flex()
8427 .bg(cx.theme().colors().editor_background)
8428 .border(BORDER_WIDTH)
8429 .shadow_sm()
8430 .border_color(cx.theme().colors().border)
8431 .rounded_l_lg()
8432 .when(line_count > 1, |el| el.rounded_br_lg())
8433 .pr_1()
8434 .child(styled_text),
8435 )
8436 .child(
8437 h_flex()
8438 .h(line_height + BORDER_WIDTH * 2.)
8439 .px_1p5()
8440 .gap_1()
8441 // Workaround: For some reason, there's a gap if we don't do this
8442 .ml(-BORDER_WIDTH)
8443 .shadow(vec![gpui::BoxShadow {
8444 color: gpui::black().opacity(0.05),
8445 offset: point(px(1.), px(1.)),
8446 blur_radius: px(2.),
8447 spread_radius: px(0.),
8448 }])
8449 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8450 .border(BORDER_WIDTH)
8451 .border_color(cx.theme().colors().border)
8452 .rounded_r_lg()
8453 .id("edit_prediction_diff_popover_keybind")
8454 .when(!has_keybind, |el| {
8455 let status_colors = cx.theme().status();
8456
8457 el.bg(status_colors.error_background)
8458 .border_color(status_colors.error.opacity(0.6))
8459 .child(Icon::new(IconName::Info).color(Color::Error))
8460 .cursor_default()
8461 .hoverable_tooltip(move |_window, cx| {
8462 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8463 })
8464 })
8465 .children(keybind),
8466 )
8467 .into_any();
8468
8469 let longest_row =
8470 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8471 let longest_line_width = if visible_row_range.contains(&longest_row) {
8472 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8473 } else {
8474 layout_line(
8475 longest_row,
8476 editor_snapshot,
8477 style,
8478 editor_width,
8479 |_| false,
8480 window,
8481 cx,
8482 )
8483 .width
8484 };
8485
8486 let viewport_bounds =
8487 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8488 right: -right_margin,
8489 ..Default::default()
8490 });
8491
8492 let x_after_longest =
8493 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8494 - scroll_pixel_position.x;
8495
8496 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8497
8498 // Fully visible if it can be displayed within the window (allow overlapping other
8499 // panes). However, this is only allowed if the popover starts within text_bounds.
8500 let can_position_to_the_right = x_after_longest < text_bounds.right()
8501 && x_after_longest + element_bounds.width < viewport_bounds.right();
8502
8503 let mut origin = if can_position_to_the_right {
8504 point(
8505 x_after_longest,
8506 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8507 - scroll_pixel_position.y,
8508 )
8509 } else {
8510 let cursor_row = newest_selection_head.map(|head| head.row());
8511 let above_edit = edit_start
8512 .row()
8513 .0
8514 .checked_sub(line_count as u32)
8515 .map(DisplayRow);
8516 let below_edit = Some(edit_end.row() + 1);
8517 let above_cursor =
8518 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8519 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8520
8521 // Place the edit popover adjacent to the edit if there is a location
8522 // available that is onscreen and does not obscure the cursor. Otherwise,
8523 // place it adjacent to the cursor.
8524 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8525 .into_iter()
8526 .flatten()
8527 .find(|&start_row| {
8528 let end_row = start_row + line_count as u32;
8529 visible_row_range.contains(&start_row)
8530 && visible_row_range.contains(&end_row)
8531 && cursor_row.map_or(true, |cursor_row| {
8532 !((start_row..end_row).contains(&cursor_row))
8533 })
8534 })?;
8535
8536 content_origin
8537 + point(
8538 -scroll_pixel_position.x,
8539 row_target.as_f32() * line_height - scroll_pixel_position.y,
8540 )
8541 };
8542
8543 origin.x -= BORDER_WIDTH;
8544
8545 window.defer_draw(element, origin, 1);
8546
8547 // Do not return an element, since it will already be drawn due to defer_draw.
8548 None
8549 }
8550
8551 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8552 px(30.)
8553 }
8554
8555 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8556 if self.read_only(cx) {
8557 cx.theme().players().read_only()
8558 } else {
8559 self.style.as_ref().unwrap().local_player
8560 }
8561 }
8562
8563 fn render_edit_prediction_accept_keybind(
8564 &self,
8565 window: &mut Window,
8566 cx: &App,
8567 ) -> Option<AnyElement> {
8568 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8569 let accept_keystroke = accept_binding.keystroke()?;
8570
8571 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8572
8573 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8574 Color::Accent
8575 } else {
8576 Color::Muted
8577 };
8578
8579 h_flex()
8580 .px_0p5()
8581 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8582 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8583 .text_size(TextSize::XSmall.rems(cx))
8584 .child(h_flex().children(ui::render_modifiers(
8585 &accept_keystroke.modifiers,
8586 PlatformStyle::platform(),
8587 Some(modifiers_color),
8588 Some(IconSize::XSmall.rems().into()),
8589 true,
8590 )))
8591 .when(is_platform_style_mac, |parent| {
8592 parent.child(accept_keystroke.key.clone())
8593 })
8594 .when(!is_platform_style_mac, |parent| {
8595 parent.child(
8596 Key::new(
8597 util::capitalize(&accept_keystroke.key),
8598 Some(Color::Default),
8599 )
8600 .size(Some(IconSize::XSmall.rems().into())),
8601 )
8602 })
8603 .into_any()
8604 .into()
8605 }
8606
8607 fn render_edit_prediction_line_popover(
8608 &self,
8609 label: impl Into<SharedString>,
8610 icon: Option<IconName>,
8611 window: &mut Window,
8612 cx: &App,
8613 ) -> Option<Stateful<Div>> {
8614 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8615
8616 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8617 let has_keybind = keybind.is_some();
8618
8619 let result = h_flex()
8620 .id("ep-line-popover")
8621 .py_0p5()
8622 .pl_1()
8623 .pr(padding_right)
8624 .gap_1()
8625 .rounded_md()
8626 .border_1()
8627 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8628 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8629 .shadow_sm()
8630 .when(!has_keybind, |el| {
8631 let status_colors = cx.theme().status();
8632
8633 el.bg(status_colors.error_background)
8634 .border_color(status_colors.error.opacity(0.6))
8635 .pl_2()
8636 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8637 .cursor_default()
8638 .hoverable_tooltip(move |_window, cx| {
8639 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8640 })
8641 })
8642 .children(keybind)
8643 .child(
8644 Label::new(label)
8645 .size(LabelSize::Small)
8646 .when(!has_keybind, |el| {
8647 el.color(cx.theme().status().error.into()).strikethrough()
8648 }),
8649 )
8650 .when(!has_keybind, |el| {
8651 el.child(
8652 h_flex().ml_1().child(
8653 Icon::new(IconName::Info)
8654 .size(IconSize::Small)
8655 .color(cx.theme().status().error.into()),
8656 ),
8657 )
8658 })
8659 .when_some(icon, |element, icon| {
8660 element.child(
8661 div()
8662 .mt(px(1.5))
8663 .child(Icon::new(icon).size(IconSize::Small)),
8664 )
8665 });
8666
8667 Some(result)
8668 }
8669
8670 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8671 let accent_color = cx.theme().colors().text_accent;
8672 let editor_bg_color = cx.theme().colors().editor_background;
8673 editor_bg_color.blend(accent_color.opacity(0.1))
8674 }
8675
8676 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8677 let accent_color = cx.theme().colors().text_accent;
8678 let editor_bg_color = cx.theme().colors().editor_background;
8679 editor_bg_color.blend(accent_color.opacity(0.6))
8680 }
8681
8682 fn render_edit_prediction_cursor_popover(
8683 &self,
8684 min_width: Pixels,
8685 max_width: Pixels,
8686 cursor_point: Point,
8687 style: &EditorStyle,
8688 accept_keystroke: Option<&gpui::Keystroke>,
8689 _window: &Window,
8690 cx: &mut Context<Editor>,
8691 ) -> Option<AnyElement> {
8692 let provider = self.edit_prediction_provider.as_ref()?;
8693
8694 if provider.provider.needs_terms_acceptance(cx) {
8695 return Some(
8696 h_flex()
8697 .min_w(min_width)
8698 .flex_1()
8699 .px_2()
8700 .py_1()
8701 .gap_3()
8702 .elevation_2(cx)
8703 .hover(|style| style.bg(cx.theme().colors().element_hover))
8704 .id("accept-terms")
8705 .cursor_pointer()
8706 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8707 .on_click(cx.listener(|this, _event, window, cx| {
8708 cx.stop_propagation();
8709 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8710 window.dispatch_action(
8711 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8712 cx,
8713 );
8714 }))
8715 .child(
8716 h_flex()
8717 .flex_1()
8718 .gap_2()
8719 .child(Icon::new(IconName::ZedPredict))
8720 .child(Label::new("Accept Terms of Service"))
8721 .child(div().w_full())
8722 .child(
8723 Icon::new(IconName::ArrowUpRight)
8724 .color(Color::Muted)
8725 .size(IconSize::Small),
8726 )
8727 .into_any_element(),
8728 )
8729 .into_any(),
8730 );
8731 }
8732
8733 let is_refreshing = provider.provider.is_refreshing(cx);
8734
8735 fn pending_completion_container() -> Div {
8736 h_flex()
8737 .h_full()
8738 .flex_1()
8739 .gap_2()
8740 .child(Icon::new(IconName::ZedPredict))
8741 }
8742
8743 let completion = match &self.active_inline_completion {
8744 Some(prediction) => {
8745 if !self.has_visible_completions_menu() {
8746 const RADIUS: Pixels = px(6.);
8747 const BORDER_WIDTH: Pixels = px(1.);
8748
8749 return Some(
8750 h_flex()
8751 .elevation_2(cx)
8752 .border(BORDER_WIDTH)
8753 .border_color(cx.theme().colors().border)
8754 .when(accept_keystroke.is_none(), |el| {
8755 el.border_color(cx.theme().status().error)
8756 })
8757 .rounded(RADIUS)
8758 .rounded_tl(px(0.))
8759 .overflow_hidden()
8760 .child(div().px_1p5().child(match &prediction.completion {
8761 InlineCompletion::Move { target, snapshot } => {
8762 use text::ToPoint as _;
8763 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8764 {
8765 Icon::new(IconName::ZedPredictDown)
8766 } else {
8767 Icon::new(IconName::ZedPredictUp)
8768 }
8769 }
8770 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8771 }))
8772 .child(
8773 h_flex()
8774 .gap_1()
8775 .py_1()
8776 .px_2()
8777 .rounded_r(RADIUS - BORDER_WIDTH)
8778 .border_l_1()
8779 .border_color(cx.theme().colors().border)
8780 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8781 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8782 el.child(
8783 Label::new("Hold")
8784 .size(LabelSize::Small)
8785 .when(accept_keystroke.is_none(), |el| {
8786 el.strikethrough()
8787 })
8788 .line_height_style(LineHeightStyle::UiLabel),
8789 )
8790 })
8791 .id("edit_prediction_cursor_popover_keybind")
8792 .when(accept_keystroke.is_none(), |el| {
8793 let status_colors = cx.theme().status();
8794
8795 el.bg(status_colors.error_background)
8796 .border_color(status_colors.error.opacity(0.6))
8797 .child(Icon::new(IconName::Info).color(Color::Error))
8798 .cursor_default()
8799 .hoverable_tooltip(move |_window, cx| {
8800 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8801 .into()
8802 })
8803 })
8804 .when_some(
8805 accept_keystroke.as_ref(),
8806 |el, accept_keystroke| {
8807 el.child(h_flex().children(ui::render_modifiers(
8808 &accept_keystroke.modifiers,
8809 PlatformStyle::platform(),
8810 Some(Color::Default),
8811 Some(IconSize::XSmall.rems().into()),
8812 false,
8813 )))
8814 },
8815 ),
8816 )
8817 .into_any(),
8818 );
8819 }
8820
8821 self.render_edit_prediction_cursor_popover_preview(
8822 prediction,
8823 cursor_point,
8824 style,
8825 cx,
8826 )?
8827 }
8828
8829 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8830 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8831 stale_completion,
8832 cursor_point,
8833 style,
8834 cx,
8835 )?,
8836
8837 None => {
8838 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8839 }
8840 },
8841
8842 None => pending_completion_container().child(Label::new("No Prediction")),
8843 };
8844
8845 let completion = if is_refreshing {
8846 completion
8847 .with_animation(
8848 "loading-completion",
8849 Animation::new(Duration::from_secs(2))
8850 .repeat()
8851 .with_easing(pulsating_between(0.4, 0.8)),
8852 |label, delta| label.opacity(delta),
8853 )
8854 .into_any_element()
8855 } else {
8856 completion.into_any_element()
8857 };
8858
8859 let has_completion = self.active_inline_completion.is_some();
8860
8861 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8862 Some(
8863 h_flex()
8864 .min_w(min_width)
8865 .max_w(max_width)
8866 .flex_1()
8867 .elevation_2(cx)
8868 .border_color(cx.theme().colors().border)
8869 .child(
8870 div()
8871 .flex_1()
8872 .py_1()
8873 .px_2()
8874 .overflow_hidden()
8875 .child(completion),
8876 )
8877 .when_some(accept_keystroke, |el, accept_keystroke| {
8878 if !accept_keystroke.modifiers.modified() {
8879 return el;
8880 }
8881
8882 el.child(
8883 h_flex()
8884 .h_full()
8885 .border_l_1()
8886 .rounded_r_lg()
8887 .border_color(cx.theme().colors().border)
8888 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8889 .gap_1()
8890 .py_1()
8891 .px_2()
8892 .child(
8893 h_flex()
8894 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8895 .when(is_platform_style_mac, |parent| parent.gap_1())
8896 .child(h_flex().children(ui::render_modifiers(
8897 &accept_keystroke.modifiers,
8898 PlatformStyle::platform(),
8899 Some(if !has_completion {
8900 Color::Muted
8901 } else {
8902 Color::Default
8903 }),
8904 None,
8905 false,
8906 ))),
8907 )
8908 .child(Label::new("Preview").into_any_element())
8909 .opacity(if has_completion { 1.0 } else { 0.4 }),
8910 )
8911 })
8912 .into_any(),
8913 )
8914 }
8915
8916 fn render_edit_prediction_cursor_popover_preview(
8917 &self,
8918 completion: &InlineCompletionState,
8919 cursor_point: Point,
8920 style: &EditorStyle,
8921 cx: &mut Context<Editor>,
8922 ) -> Option<Div> {
8923 use text::ToPoint as _;
8924
8925 fn render_relative_row_jump(
8926 prefix: impl Into<String>,
8927 current_row: u32,
8928 target_row: u32,
8929 ) -> Div {
8930 let (row_diff, arrow) = if target_row < current_row {
8931 (current_row - target_row, IconName::ArrowUp)
8932 } else {
8933 (target_row - current_row, IconName::ArrowDown)
8934 };
8935
8936 h_flex()
8937 .child(
8938 Label::new(format!("{}{}", prefix.into(), row_diff))
8939 .color(Color::Muted)
8940 .size(LabelSize::Small),
8941 )
8942 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8943 }
8944
8945 match &completion.completion {
8946 InlineCompletion::Move {
8947 target, snapshot, ..
8948 } => Some(
8949 h_flex()
8950 .px_2()
8951 .gap_2()
8952 .flex_1()
8953 .child(
8954 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8955 Icon::new(IconName::ZedPredictDown)
8956 } else {
8957 Icon::new(IconName::ZedPredictUp)
8958 },
8959 )
8960 .child(Label::new("Jump to Edit")),
8961 ),
8962
8963 InlineCompletion::Edit {
8964 edits,
8965 edit_preview,
8966 snapshot,
8967 display_mode: _,
8968 } => {
8969 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8970
8971 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8972 &snapshot,
8973 &edits,
8974 edit_preview.as_ref()?,
8975 true,
8976 cx,
8977 )
8978 .first_line_preview();
8979
8980 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8981 .with_default_highlights(&style.text, highlighted_edits.highlights);
8982
8983 let preview = h_flex()
8984 .gap_1()
8985 .min_w_16()
8986 .child(styled_text)
8987 .when(has_more_lines, |parent| parent.child("…"));
8988
8989 let left = if first_edit_row != cursor_point.row {
8990 render_relative_row_jump("", cursor_point.row, first_edit_row)
8991 .into_any_element()
8992 } else {
8993 Icon::new(IconName::ZedPredict).into_any_element()
8994 };
8995
8996 Some(
8997 h_flex()
8998 .h_full()
8999 .flex_1()
9000 .gap_2()
9001 .pr_1()
9002 .overflow_x_hidden()
9003 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9004 .child(left)
9005 .child(preview),
9006 )
9007 }
9008 }
9009 }
9010
9011 pub fn render_context_menu(
9012 &self,
9013 style: &EditorStyle,
9014 max_height_in_lines: u32,
9015 window: &mut Window,
9016 cx: &mut Context<Editor>,
9017 ) -> Option<AnyElement> {
9018 let menu = self.context_menu.borrow();
9019 let menu = menu.as_ref()?;
9020 if !menu.visible() {
9021 return None;
9022 };
9023 Some(menu.render(style, max_height_in_lines, window, cx))
9024 }
9025
9026 fn render_context_menu_aside(
9027 &mut self,
9028 max_size: Size<Pixels>,
9029 window: &mut Window,
9030 cx: &mut Context<Editor>,
9031 ) -> Option<AnyElement> {
9032 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9033 if menu.visible() {
9034 menu.render_aside(max_size, window, cx)
9035 } else {
9036 None
9037 }
9038 })
9039 }
9040
9041 fn hide_context_menu(
9042 &mut self,
9043 window: &mut Window,
9044 cx: &mut Context<Self>,
9045 ) -> Option<CodeContextMenu> {
9046 cx.notify();
9047 self.completion_tasks.clear();
9048 let context_menu = self.context_menu.borrow_mut().take();
9049 self.stale_inline_completion_in_menu.take();
9050 self.update_visible_inline_completion(window, cx);
9051 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9052 if let Some(completion_provider) = &self.completion_provider {
9053 completion_provider.selection_changed(None, window, cx);
9054 }
9055 }
9056 context_menu
9057 }
9058
9059 fn show_snippet_choices(
9060 &mut self,
9061 choices: &Vec<String>,
9062 selection: Range<Anchor>,
9063 cx: &mut Context<Self>,
9064 ) {
9065 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9066 (Some(a), Some(b)) if a == b => a,
9067 _ => {
9068 log::error!("expected anchor range to have matching buffer IDs");
9069 return;
9070 }
9071 };
9072 let multi_buffer = self.buffer().read(cx);
9073 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9074 return;
9075 };
9076
9077 let id = post_inc(&mut self.next_completion_id);
9078 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9079 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9080 CompletionsMenu::new_snippet_choices(
9081 id,
9082 true,
9083 choices,
9084 selection,
9085 buffer,
9086 snippet_sort_order,
9087 ),
9088 ));
9089 }
9090
9091 pub fn insert_snippet(
9092 &mut self,
9093 insertion_ranges: &[Range<usize>],
9094 snippet: Snippet,
9095 window: &mut Window,
9096 cx: &mut Context<Self>,
9097 ) -> Result<()> {
9098 struct Tabstop<T> {
9099 is_end_tabstop: bool,
9100 ranges: Vec<Range<T>>,
9101 choices: Option<Vec<String>>,
9102 }
9103
9104 let tabstops = self.buffer.update(cx, |buffer, cx| {
9105 let snippet_text: Arc<str> = snippet.text.clone().into();
9106 let edits = insertion_ranges
9107 .iter()
9108 .cloned()
9109 .map(|range| (range, snippet_text.clone()));
9110 let autoindent_mode = AutoindentMode::Block {
9111 original_indent_columns: Vec::new(),
9112 };
9113 buffer.edit(edits, Some(autoindent_mode), cx);
9114
9115 let snapshot = &*buffer.read(cx);
9116 let snippet = &snippet;
9117 snippet
9118 .tabstops
9119 .iter()
9120 .map(|tabstop| {
9121 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9122 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9123 });
9124 let mut tabstop_ranges = tabstop
9125 .ranges
9126 .iter()
9127 .flat_map(|tabstop_range| {
9128 let mut delta = 0_isize;
9129 insertion_ranges.iter().map(move |insertion_range| {
9130 let insertion_start = insertion_range.start as isize + delta;
9131 delta +=
9132 snippet.text.len() as isize - insertion_range.len() as isize;
9133
9134 let start = ((insertion_start + tabstop_range.start) as usize)
9135 .min(snapshot.len());
9136 let end = ((insertion_start + tabstop_range.end) as usize)
9137 .min(snapshot.len());
9138 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9139 })
9140 })
9141 .collect::<Vec<_>>();
9142 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9143
9144 Tabstop {
9145 is_end_tabstop,
9146 ranges: tabstop_ranges,
9147 choices: tabstop.choices.clone(),
9148 }
9149 })
9150 .collect::<Vec<_>>()
9151 });
9152 if let Some(tabstop) = tabstops.first() {
9153 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9154 // Reverse order so that the first range is the newest created selection.
9155 // Completions will use it and autoscroll will prioritize it.
9156 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9157 });
9158
9159 if let Some(choices) = &tabstop.choices {
9160 if let Some(selection) = tabstop.ranges.first() {
9161 self.show_snippet_choices(choices, selection.clone(), cx)
9162 }
9163 }
9164
9165 // If we're already at the last tabstop and it's at the end of the snippet,
9166 // we're done, we don't need to keep the state around.
9167 if !tabstop.is_end_tabstop {
9168 let choices = tabstops
9169 .iter()
9170 .map(|tabstop| tabstop.choices.clone())
9171 .collect();
9172
9173 let ranges = tabstops
9174 .into_iter()
9175 .map(|tabstop| tabstop.ranges)
9176 .collect::<Vec<_>>();
9177
9178 self.snippet_stack.push(SnippetState {
9179 active_index: 0,
9180 ranges,
9181 choices,
9182 });
9183 }
9184
9185 // Check whether the just-entered snippet ends with an auto-closable bracket.
9186 if self.autoclose_regions.is_empty() {
9187 let snapshot = self.buffer.read(cx).snapshot(cx);
9188 for selection in &mut self.selections.all::<Point>(cx) {
9189 let selection_head = selection.head();
9190 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9191 continue;
9192 };
9193
9194 let mut bracket_pair = None;
9195 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9196 let prev_chars = snapshot
9197 .reversed_chars_at(selection_head)
9198 .collect::<String>();
9199 for (pair, enabled) in scope.brackets() {
9200 if enabled
9201 && pair.close
9202 && prev_chars.starts_with(pair.start.as_str())
9203 && next_chars.starts_with(pair.end.as_str())
9204 {
9205 bracket_pair = Some(pair.clone());
9206 break;
9207 }
9208 }
9209 if let Some(pair) = bracket_pair {
9210 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9211 let autoclose_enabled =
9212 self.use_autoclose && snapshot_settings.use_autoclose;
9213 if autoclose_enabled {
9214 let start = snapshot.anchor_after(selection_head);
9215 let end = snapshot.anchor_after(selection_head);
9216 self.autoclose_regions.push(AutocloseRegion {
9217 selection_id: selection.id,
9218 range: start..end,
9219 pair,
9220 });
9221 }
9222 }
9223 }
9224 }
9225 }
9226 Ok(())
9227 }
9228
9229 pub fn move_to_next_snippet_tabstop(
9230 &mut self,
9231 window: &mut Window,
9232 cx: &mut Context<Self>,
9233 ) -> bool {
9234 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9235 }
9236
9237 pub fn move_to_prev_snippet_tabstop(
9238 &mut self,
9239 window: &mut Window,
9240 cx: &mut Context<Self>,
9241 ) -> bool {
9242 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9243 }
9244
9245 pub fn move_to_snippet_tabstop(
9246 &mut self,
9247 bias: Bias,
9248 window: &mut Window,
9249 cx: &mut Context<Self>,
9250 ) -> bool {
9251 if let Some(mut snippet) = self.snippet_stack.pop() {
9252 match bias {
9253 Bias::Left => {
9254 if snippet.active_index > 0 {
9255 snippet.active_index -= 1;
9256 } else {
9257 self.snippet_stack.push(snippet);
9258 return false;
9259 }
9260 }
9261 Bias::Right => {
9262 if snippet.active_index + 1 < snippet.ranges.len() {
9263 snippet.active_index += 1;
9264 } else {
9265 self.snippet_stack.push(snippet);
9266 return false;
9267 }
9268 }
9269 }
9270 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9271 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9272 // Reverse order so that the first range is the newest created selection.
9273 // Completions will use it and autoscroll will prioritize it.
9274 s.select_ranges(current_ranges.iter().rev().cloned())
9275 });
9276
9277 if let Some(choices) = &snippet.choices[snippet.active_index] {
9278 if let Some(selection) = current_ranges.first() {
9279 self.show_snippet_choices(&choices, selection.clone(), cx);
9280 }
9281 }
9282
9283 // If snippet state is not at the last tabstop, push it back on the stack
9284 if snippet.active_index + 1 < snippet.ranges.len() {
9285 self.snippet_stack.push(snippet);
9286 }
9287 return true;
9288 }
9289 }
9290
9291 false
9292 }
9293
9294 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9295 self.transact(window, cx, |this, window, cx| {
9296 this.select_all(&SelectAll, window, cx);
9297 this.insert("", window, cx);
9298 });
9299 }
9300
9301 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9302 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9303 self.transact(window, cx, |this, window, cx| {
9304 this.select_autoclose_pair(window, cx);
9305 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9306 if !this.linked_edit_ranges.is_empty() {
9307 let selections = this.selections.all::<MultiBufferPoint>(cx);
9308 let snapshot = this.buffer.read(cx).snapshot(cx);
9309
9310 for selection in selections.iter() {
9311 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9312 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9313 if selection_start.buffer_id != selection_end.buffer_id {
9314 continue;
9315 }
9316 if let Some(ranges) =
9317 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9318 {
9319 for (buffer, entries) in ranges {
9320 linked_ranges.entry(buffer).or_default().extend(entries);
9321 }
9322 }
9323 }
9324 }
9325
9326 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9327 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9328 for selection in &mut selections {
9329 if selection.is_empty() {
9330 let old_head = selection.head();
9331 let mut new_head =
9332 movement::left(&display_map, old_head.to_display_point(&display_map))
9333 .to_point(&display_map);
9334 if let Some((buffer, line_buffer_range)) = display_map
9335 .buffer_snapshot
9336 .buffer_line_for_row(MultiBufferRow(old_head.row))
9337 {
9338 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9339 let indent_len = match indent_size.kind {
9340 IndentKind::Space => {
9341 buffer.settings_at(line_buffer_range.start, cx).tab_size
9342 }
9343 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9344 };
9345 if old_head.column <= indent_size.len && old_head.column > 0 {
9346 let indent_len = indent_len.get();
9347 new_head = cmp::min(
9348 new_head,
9349 MultiBufferPoint::new(
9350 old_head.row,
9351 ((old_head.column - 1) / indent_len) * indent_len,
9352 ),
9353 );
9354 }
9355 }
9356
9357 selection.set_head(new_head, SelectionGoal::None);
9358 }
9359 }
9360
9361 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9362 s.select(selections)
9363 });
9364 this.insert("", window, cx);
9365 let empty_str: Arc<str> = Arc::from("");
9366 for (buffer, edits) in linked_ranges {
9367 let snapshot = buffer.read(cx).snapshot();
9368 use text::ToPoint as TP;
9369
9370 let edits = edits
9371 .into_iter()
9372 .map(|range| {
9373 let end_point = TP::to_point(&range.end, &snapshot);
9374 let mut start_point = TP::to_point(&range.start, &snapshot);
9375
9376 if end_point == start_point {
9377 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9378 .saturating_sub(1);
9379 start_point =
9380 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9381 };
9382
9383 (start_point..end_point, empty_str.clone())
9384 })
9385 .sorted_by_key(|(range, _)| range.start)
9386 .collect::<Vec<_>>();
9387 buffer.update(cx, |this, cx| {
9388 this.edit(edits, None, cx);
9389 })
9390 }
9391 this.refresh_inline_completion(true, false, window, cx);
9392 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9393 });
9394 }
9395
9396 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9397 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9398 self.transact(window, cx, |this, window, cx| {
9399 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9400 s.move_with(|map, selection| {
9401 if selection.is_empty() {
9402 let cursor = movement::right(map, selection.head());
9403 selection.end = cursor;
9404 selection.reversed = true;
9405 selection.goal = SelectionGoal::None;
9406 }
9407 })
9408 });
9409 this.insert("", window, cx);
9410 this.refresh_inline_completion(true, false, window, cx);
9411 });
9412 }
9413
9414 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9415 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9416 if self.move_to_prev_snippet_tabstop(window, cx) {
9417 return;
9418 }
9419 self.outdent(&Outdent, window, cx);
9420 }
9421
9422 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9423 if self.move_to_next_snippet_tabstop(window, cx) {
9424 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9425 return;
9426 }
9427 if self.read_only(cx) {
9428 return;
9429 }
9430 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9431 let mut selections = self.selections.all_adjusted(cx);
9432 let buffer = self.buffer.read(cx);
9433 let snapshot = buffer.snapshot(cx);
9434 let rows_iter = selections.iter().map(|s| s.head().row);
9435 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9436
9437 let has_some_cursor_in_whitespace = selections
9438 .iter()
9439 .filter(|selection| selection.is_empty())
9440 .any(|selection| {
9441 let cursor = selection.head();
9442 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9443 cursor.column < current_indent.len
9444 });
9445
9446 let mut edits = Vec::new();
9447 let mut prev_edited_row = 0;
9448 let mut row_delta = 0;
9449 for selection in &mut selections {
9450 if selection.start.row != prev_edited_row {
9451 row_delta = 0;
9452 }
9453 prev_edited_row = selection.end.row;
9454
9455 // If the selection is non-empty, then increase the indentation of the selected lines.
9456 if !selection.is_empty() {
9457 row_delta =
9458 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9459 continue;
9460 }
9461
9462 let cursor = selection.head();
9463 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9464 if let Some(suggested_indent) =
9465 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9466 {
9467 // Don't do anything if already at suggested indent
9468 // and there is any other cursor which is not
9469 if has_some_cursor_in_whitespace
9470 && cursor.column == current_indent.len
9471 && current_indent.len == suggested_indent.len
9472 {
9473 continue;
9474 }
9475
9476 // Adjust line and move cursor to suggested indent
9477 // if cursor is not at suggested indent
9478 if cursor.column < suggested_indent.len
9479 && cursor.column <= current_indent.len
9480 && current_indent.len <= suggested_indent.len
9481 {
9482 selection.start = Point::new(cursor.row, suggested_indent.len);
9483 selection.end = selection.start;
9484 if row_delta == 0 {
9485 edits.extend(Buffer::edit_for_indent_size_adjustment(
9486 cursor.row,
9487 current_indent,
9488 suggested_indent,
9489 ));
9490 row_delta = suggested_indent.len - current_indent.len;
9491 }
9492 continue;
9493 }
9494
9495 // If current indent is more than suggested indent
9496 // only move cursor to current indent and skip indent
9497 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9498 selection.start = Point::new(cursor.row, current_indent.len);
9499 selection.end = selection.start;
9500 continue;
9501 }
9502 }
9503
9504 // Otherwise, insert a hard or soft tab.
9505 let settings = buffer.language_settings_at(cursor, cx);
9506 let tab_size = if settings.hard_tabs {
9507 IndentSize::tab()
9508 } else {
9509 let tab_size = settings.tab_size.get();
9510 let indent_remainder = snapshot
9511 .text_for_range(Point::new(cursor.row, 0)..cursor)
9512 .flat_map(str::chars)
9513 .fold(row_delta % tab_size, |counter: u32, c| {
9514 if c == '\t' {
9515 0
9516 } else {
9517 (counter + 1) % tab_size
9518 }
9519 });
9520
9521 let chars_to_next_tab_stop = tab_size - indent_remainder;
9522 IndentSize::spaces(chars_to_next_tab_stop)
9523 };
9524 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9525 selection.end = selection.start;
9526 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9527 row_delta += tab_size.len;
9528 }
9529
9530 self.transact(window, cx, |this, window, cx| {
9531 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9532 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9533 s.select(selections)
9534 });
9535 this.refresh_inline_completion(true, false, window, cx);
9536 });
9537 }
9538
9539 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9540 if self.read_only(cx) {
9541 return;
9542 }
9543 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9544 let mut selections = self.selections.all::<Point>(cx);
9545 let mut prev_edited_row = 0;
9546 let mut row_delta = 0;
9547 let mut edits = Vec::new();
9548 let buffer = self.buffer.read(cx);
9549 let snapshot = buffer.snapshot(cx);
9550 for selection in &mut selections {
9551 if selection.start.row != prev_edited_row {
9552 row_delta = 0;
9553 }
9554 prev_edited_row = selection.end.row;
9555
9556 row_delta =
9557 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9558 }
9559
9560 self.transact(window, cx, |this, window, cx| {
9561 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9562 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9563 s.select(selections)
9564 });
9565 });
9566 }
9567
9568 fn indent_selection(
9569 buffer: &MultiBuffer,
9570 snapshot: &MultiBufferSnapshot,
9571 selection: &mut Selection<Point>,
9572 edits: &mut Vec<(Range<Point>, String)>,
9573 delta_for_start_row: u32,
9574 cx: &App,
9575 ) -> u32 {
9576 let settings = buffer.language_settings_at(selection.start, cx);
9577 let tab_size = settings.tab_size.get();
9578 let indent_kind = if settings.hard_tabs {
9579 IndentKind::Tab
9580 } else {
9581 IndentKind::Space
9582 };
9583 let mut start_row = selection.start.row;
9584 let mut end_row = selection.end.row + 1;
9585
9586 // If a selection ends at the beginning of a line, don't indent
9587 // that last line.
9588 if selection.end.column == 0 && selection.end.row > selection.start.row {
9589 end_row -= 1;
9590 }
9591
9592 // Avoid re-indenting a row that has already been indented by a
9593 // previous selection, but still update this selection's column
9594 // to reflect that indentation.
9595 if delta_for_start_row > 0 {
9596 start_row += 1;
9597 selection.start.column += delta_for_start_row;
9598 if selection.end.row == selection.start.row {
9599 selection.end.column += delta_for_start_row;
9600 }
9601 }
9602
9603 let mut delta_for_end_row = 0;
9604 let has_multiple_rows = start_row + 1 != end_row;
9605 for row in start_row..end_row {
9606 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9607 let indent_delta = match (current_indent.kind, indent_kind) {
9608 (IndentKind::Space, IndentKind::Space) => {
9609 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9610 IndentSize::spaces(columns_to_next_tab_stop)
9611 }
9612 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9613 (_, IndentKind::Tab) => IndentSize::tab(),
9614 };
9615
9616 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9617 0
9618 } else {
9619 selection.start.column
9620 };
9621 let row_start = Point::new(row, start);
9622 edits.push((
9623 row_start..row_start,
9624 indent_delta.chars().collect::<String>(),
9625 ));
9626
9627 // Update this selection's endpoints to reflect the indentation.
9628 if row == selection.start.row {
9629 selection.start.column += indent_delta.len;
9630 }
9631 if row == selection.end.row {
9632 selection.end.column += indent_delta.len;
9633 delta_for_end_row = indent_delta.len;
9634 }
9635 }
9636
9637 if selection.start.row == selection.end.row {
9638 delta_for_start_row + delta_for_end_row
9639 } else {
9640 delta_for_end_row
9641 }
9642 }
9643
9644 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9645 if self.read_only(cx) {
9646 return;
9647 }
9648 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9650 let selections = self.selections.all::<Point>(cx);
9651 let mut deletion_ranges = Vec::new();
9652 let mut last_outdent = None;
9653 {
9654 let buffer = self.buffer.read(cx);
9655 let snapshot = buffer.snapshot(cx);
9656 for selection in &selections {
9657 let settings = buffer.language_settings_at(selection.start, cx);
9658 let tab_size = settings.tab_size.get();
9659 let mut rows = selection.spanned_rows(false, &display_map);
9660
9661 // Avoid re-outdenting a row that has already been outdented by a
9662 // previous selection.
9663 if let Some(last_row) = last_outdent {
9664 if last_row == rows.start {
9665 rows.start = rows.start.next_row();
9666 }
9667 }
9668 let has_multiple_rows = rows.len() > 1;
9669 for row in rows.iter_rows() {
9670 let indent_size = snapshot.indent_size_for_line(row);
9671 if indent_size.len > 0 {
9672 let deletion_len = match indent_size.kind {
9673 IndentKind::Space => {
9674 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9675 if columns_to_prev_tab_stop == 0 {
9676 tab_size
9677 } else {
9678 columns_to_prev_tab_stop
9679 }
9680 }
9681 IndentKind::Tab => 1,
9682 };
9683 let start = if has_multiple_rows
9684 || deletion_len > selection.start.column
9685 || indent_size.len < selection.start.column
9686 {
9687 0
9688 } else {
9689 selection.start.column - deletion_len
9690 };
9691 deletion_ranges.push(
9692 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9693 );
9694 last_outdent = Some(row);
9695 }
9696 }
9697 }
9698 }
9699
9700 self.transact(window, cx, |this, window, cx| {
9701 this.buffer.update(cx, |buffer, cx| {
9702 let empty_str: Arc<str> = Arc::default();
9703 buffer.edit(
9704 deletion_ranges
9705 .into_iter()
9706 .map(|range| (range, empty_str.clone())),
9707 None,
9708 cx,
9709 );
9710 });
9711 let selections = this.selections.all::<usize>(cx);
9712 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9713 s.select(selections)
9714 });
9715 });
9716 }
9717
9718 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9719 if self.read_only(cx) {
9720 return;
9721 }
9722 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9723 let selections = self
9724 .selections
9725 .all::<usize>(cx)
9726 .into_iter()
9727 .map(|s| s.range());
9728
9729 self.transact(window, cx, |this, window, cx| {
9730 this.buffer.update(cx, |buffer, cx| {
9731 buffer.autoindent_ranges(selections, cx);
9732 });
9733 let selections = this.selections.all::<usize>(cx);
9734 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9735 s.select(selections)
9736 });
9737 });
9738 }
9739
9740 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9741 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9743 let selections = self.selections.all::<Point>(cx);
9744
9745 let mut new_cursors = Vec::new();
9746 let mut edit_ranges = Vec::new();
9747 let mut selections = selections.iter().peekable();
9748 while let Some(selection) = selections.next() {
9749 let mut rows = selection.spanned_rows(false, &display_map);
9750 let goal_display_column = selection.head().to_display_point(&display_map).column();
9751
9752 // Accumulate contiguous regions of rows that we want to delete.
9753 while let Some(next_selection) = selections.peek() {
9754 let next_rows = next_selection.spanned_rows(false, &display_map);
9755 if next_rows.start <= rows.end {
9756 rows.end = next_rows.end;
9757 selections.next().unwrap();
9758 } else {
9759 break;
9760 }
9761 }
9762
9763 let buffer = &display_map.buffer_snapshot;
9764 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9765 let edit_end;
9766 let cursor_buffer_row;
9767 if buffer.max_point().row >= rows.end.0 {
9768 // If there's a line after the range, delete the \n from the end of the row range
9769 // and position the cursor on the next line.
9770 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9771 cursor_buffer_row = rows.end;
9772 } else {
9773 // If there isn't a line after the range, delete the \n from the line before the
9774 // start of the row range and position the cursor there.
9775 edit_start = edit_start.saturating_sub(1);
9776 edit_end = buffer.len();
9777 cursor_buffer_row = rows.start.previous_row();
9778 }
9779
9780 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9781 *cursor.column_mut() =
9782 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9783
9784 new_cursors.push((
9785 selection.id,
9786 buffer.anchor_after(cursor.to_point(&display_map)),
9787 ));
9788 edit_ranges.push(edit_start..edit_end);
9789 }
9790
9791 self.transact(window, cx, |this, window, cx| {
9792 let buffer = this.buffer.update(cx, |buffer, cx| {
9793 let empty_str: Arc<str> = Arc::default();
9794 buffer.edit(
9795 edit_ranges
9796 .into_iter()
9797 .map(|range| (range, empty_str.clone())),
9798 None,
9799 cx,
9800 );
9801 buffer.snapshot(cx)
9802 });
9803 let new_selections = new_cursors
9804 .into_iter()
9805 .map(|(id, cursor)| {
9806 let cursor = cursor.to_point(&buffer);
9807 Selection {
9808 id,
9809 start: cursor,
9810 end: cursor,
9811 reversed: false,
9812 goal: SelectionGoal::None,
9813 }
9814 })
9815 .collect();
9816
9817 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9818 s.select(new_selections);
9819 });
9820 });
9821 }
9822
9823 pub fn join_lines_impl(
9824 &mut self,
9825 insert_whitespace: bool,
9826 window: &mut Window,
9827 cx: &mut Context<Self>,
9828 ) {
9829 if self.read_only(cx) {
9830 return;
9831 }
9832 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9833 for selection in self.selections.all::<Point>(cx) {
9834 let start = MultiBufferRow(selection.start.row);
9835 // Treat single line selections as if they include the next line. Otherwise this action
9836 // would do nothing for single line selections individual cursors.
9837 let end = if selection.start.row == selection.end.row {
9838 MultiBufferRow(selection.start.row + 1)
9839 } else {
9840 MultiBufferRow(selection.end.row)
9841 };
9842
9843 if let Some(last_row_range) = row_ranges.last_mut() {
9844 if start <= last_row_range.end {
9845 last_row_range.end = end;
9846 continue;
9847 }
9848 }
9849 row_ranges.push(start..end);
9850 }
9851
9852 let snapshot = self.buffer.read(cx).snapshot(cx);
9853 let mut cursor_positions = Vec::new();
9854 for row_range in &row_ranges {
9855 let anchor = snapshot.anchor_before(Point::new(
9856 row_range.end.previous_row().0,
9857 snapshot.line_len(row_range.end.previous_row()),
9858 ));
9859 cursor_positions.push(anchor..anchor);
9860 }
9861
9862 self.transact(window, cx, |this, window, cx| {
9863 for row_range in row_ranges.into_iter().rev() {
9864 for row in row_range.iter_rows().rev() {
9865 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9866 let next_line_row = row.next_row();
9867 let indent = snapshot.indent_size_for_line(next_line_row);
9868 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9869
9870 let replace =
9871 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9872 " "
9873 } else {
9874 ""
9875 };
9876
9877 this.buffer.update(cx, |buffer, cx| {
9878 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9879 });
9880 }
9881 }
9882
9883 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9884 s.select_anchor_ranges(cursor_positions)
9885 });
9886 });
9887 }
9888
9889 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9890 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9891 self.join_lines_impl(true, window, cx);
9892 }
9893
9894 pub fn sort_lines_case_sensitive(
9895 &mut self,
9896 _: &SortLinesCaseSensitive,
9897 window: &mut Window,
9898 cx: &mut Context<Self>,
9899 ) {
9900 self.manipulate_lines(window, cx, |lines| lines.sort())
9901 }
9902
9903 pub fn sort_lines_case_insensitive(
9904 &mut self,
9905 _: &SortLinesCaseInsensitive,
9906 window: &mut Window,
9907 cx: &mut Context<Self>,
9908 ) {
9909 self.manipulate_lines(window, cx, |lines| {
9910 lines.sort_by_key(|line| line.to_lowercase())
9911 })
9912 }
9913
9914 pub fn unique_lines_case_insensitive(
9915 &mut self,
9916 _: &UniqueLinesCaseInsensitive,
9917 window: &mut Window,
9918 cx: &mut Context<Self>,
9919 ) {
9920 self.manipulate_lines(window, cx, |lines| {
9921 let mut seen = HashSet::default();
9922 lines.retain(|line| seen.insert(line.to_lowercase()));
9923 })
9924 }
9925
9926 pub fn unique_lines_case_sensitive(
9927 &mut self,
9928 _: &UniqueLinesCaseSensitive,
9929 window: &mut Window,
9930 cx: &mut Context<Self>,
9931 ) {
9932 self.manipulate_lines(window, cx, |lines| {
9933 let mut seen = HashSet::default();
9934 lines.retain(|line| seen.insert(*line));
9935 })
9936 }
9937
9938 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9939 let Some(project) = self.project.clone() else {
9940 return;
9941 };
9942 self.reload(project, window, cx)
9943 .detach_and_notify_err(window, cx);
9944 }
9945
9946 pub fn restore_file(
9947 &mut self,
9948 _: &::git::RestoreFile,
9949 window: &mut Window,
9950 cx: &mut Context<Self>,
9951 ) {
9952 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9953 let mut buffer_ids = HashSet::default();
9954 let snapshot = self.buffer().read(cx).snapshot(cx);
9955 for selection in self.selections.all::<usize>(cx) {
9956 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9957 }
9958
9959 let buffer = self.buffer().read(cx);
9960 let ranges = buffer_ids
9961 .into_iter()
9962 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9963 .collect::<Vec<_>>();
9964
9965 self.restore_hunks_in_ranges(ranges, window, cx);
9966 }
9967
9968 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9969 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9970 let selections = self
9971 .selections
9972 .all(cx)
9973 .into_iter()
9974 .map(|s| s.range())
9975 .collect();
9976 self.restore_hunks_in_ranges(selections, window, cx);
9977 }
9978
9979 pub fn restore_hunks_in_ranges(
9980 &mut self,
9981 ranges: Vec<Range<Point>>,
9982 window: &mut Window,
9983 cx: &mut Context<Editor>,
9984 ) {
9985 let mut revert_changes = HashMap::default();
9986 let chunk_by = self
9987 .snapshot(window, cx)
9988 .hunks_for_ranges(ranges)
9989 .into_iter()
9990 .chunk_by(|hunk| hunk.buffer_id);
9991 for (buffer_id, hunks) in &chunk_by {
9992 let hunks = hunks.collect::<Vec<_>>();
9993 for hunk in &hunks {
9994 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9995 }
9996 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9997 }
9998 drop(chunk_by);
9999 if !revert_changes.is_empty() {
10000 self.transact(window, cx, |editor, window, cx| {
10001 editor.restore(revert_changes, window, cx);
10002 });
10003 }
10004 }
10005
10006 pub fn open_active_item_in_terminal(
10007 &mut self,
10008 _: &OpenInTerminal,
10009 window: &mut Window,
10010 cx: &mut Context<Self>,
10011 ) {
10012 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10013 let project_path = buffer.read(cx).project_path(cx)?;
10014 let project = self.project.as_ref()?.read(cx);
10015 let entry = project.entry_for_path(&project_path, cx)?;
10016 let parent = match &entry.canonical_path {
10017 Some(canonical_path) => canonical_path.to_path_buf(),
10018 None => project.absolute_path(&project_path, cx)?,
10019 }
10020 .parent()?
10021 .to_path_buf();
10022 Some(parent)
10023 }) {
10024 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10025 }
10026 }
10027
10028 fn set_breakpoint_context_menu(
10029 &mut self,
10030 display_row: DisplayRow,
10031 position: Option<Anchor>,
10032 clicked_point: gpui::Point<Pixels>,
10033 window: &mut Window,
10034 cx: &mut Context<Self>,
10035 ) {
10036 if !cx.has_flag::<DebuggerFeatureFlag>() {
10037 return;
10038 }
10039 let source = self
10040 .buffer
10041 .read(cx)
10042 .snapshot(cx)
10043 .anchor_before(Point::new(display_row.0, 0u32));
10044
10045 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10046
10047 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10048 self,
10049 source,
10050 clicked_point,
10051 context_menu,
10052 window,
10053 cx,
10054 );
10055 }
10056
10057 fn add_edit_breakpoint_block(
10058 &mut self,
10059 anchor: Anchor,
10060 breakpoint: &Breakpoint,
10061 edit_action: BreakpointPromptEditAction,
10062 window: &mut Window,
10063 cx: &mut Context<Self>,
10064 ) {
10065 let weak_editor = cx.weak_entity();
10066 let bp_prompt = cx.new(|cx| {
10067 BreakpointPromptEditor::new(
10068 weak_editor,
10069 anchor,
10070 breakpoint.clone(),
10071 edit_action,
10072 window,
10073 cx,
10074 )
10075 });
10076
10077 let height = bp_prompt.update(cx, |this, cx| {
10078 this.prompt
10079 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10080 });
10081 let cloned_prompt = bp_prompt.clone();
10082 let blocks = vec![BlockProperties {
10083 style: BlockStyle::Sticky,
10084 placement: BlockPlacement::Above(anchor),
10085 height: Some(height),
10086 render: Arc::new(move |cx| {
10087 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10088 cloned_prompt.clone().into_any_element()
10089 }),
10090 priority: 0,
10091 render_in_minimap: true,
10092 }];
10093
10094 let focus_handle = bp_prompt.focus_handle(cx);
10095 window.focus(&focus_handle);
10096
10097 let block_ids = self.insert_blocks(blocks, None, cx);
10098 bp_prompt.update(cx, |prompt, _| {
10099 prompt.add_block_ids(block_ids);
10100 });
10101 }
10102
10103 pub(crate) fn breakpoint_at_row(
10104 &self,
10105 row: u32,
10106 window: &mut Window,
10107 cx: &mut Context<Self>,
10108 ) -> Option<(Anchor, Breakpoint)> {
10109 let snapshot = self.snapshot(window, cx);
10110 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10111
10112 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10113 }
10114
10115 pub(crate) fn breakpoint_at_anchor(
10116 &self,
10117 breakpoint_position: Anchor,
10118 snapshot: &EditorSnapshot,
10119 cx: &mut Context<Self>,
10120 ) -> Option<(Anchor, Breakpoint)> {
10121 let project = self.project.clone()?;
10122
10123 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10124 snapshot
10125 .buffer_snapshot
10126 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10127 })?;
10128
10129 let enclosing_excerpt = breakpoint_position.excerpt_id;
10130 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10131 let buffer_snapshot = buffer.read(cx).snapshot();
10132
10133 let row = buffer_snapshot
10134 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10135 .row;
10136
10137 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10138 let anchor_end = snapshot
10139 .buffer_snapshot
10140 .anchor_after(Point::new(row, line_len));
10141
10142 let bp = self
10143 .breakpoint_store
10144 .as_ref()?
10145 .read_with(cx, |breakpoint_store, cx| {
10146 breakpoint_store
10147 .breakpoints(
10148 &buffer,
10149 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10150 &buffer_snapshot,
10151 cx,
10152 )
10153 .next()
10154 .and_then(|(bp, _)| {
10155 let breakpoint_row = buffer_snapshot
10156 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10157 .row;
10158
10159 if breakpoint_row == row {
10160 snapshot
10161 .buffer_snapshot
10162 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10163 .map(|position| (position, bp.bp.clone()))
10164 } else {
10165 None
10166 }
10167 })
10168 });
10169 bp
10170 }
10171
10172 pub fn edit_log_breakpoint(
10173 &mut self,
10174 _: &EditLogBreakpoint,
10175 window: &mut Window,
10176 cx: &mut Context<Self>,
10177 ) {
10178 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10179 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10180 message: None,
10181 state: BreakpointState::Enabled,
10182 condition: None,
10183 hit_condition: None,
10184 });
10185
10186 self.add_edit_breakpoint_block(
10187 anchor,
10188 &breakpoint,
10189 BreakpointPromptEditAction::Log,
10190 window,
10191 cx,
10192 );
10193 }
10194 }
10195
10196 fn breakpoints_at_cursors(
10197 &self,
10198 window: &mut Window,
10199 cx: &mut Context<Self>,
10200 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10201 let snapshot = self.snapshot(window, cx);
10202 let cursors = self
10203 .selections
10204 .disjoint_anchors()
10205 .into_iter()
10206 .map(|selection| {
10207 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10208
10209 let breakpoint_position = self
10210 .breakpoint_at_row(cursor_position.row, window, cx)
10211 .map(|bp| bp.0)
10212 .unwrap_or_else(|| {
10213 snapshot
10214 .display_snapshot
10215 .buffer_snapshot
10216 .anchor_after(Point::new(cursor_position.row, 0))
10217 });
10218
10219 let breakpoint = self
10220 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10221 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10222
10223 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10224 })
10225 // 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.
10226 .collect::<HashMap<Anchor, _>>();
10227
10228 cursors.into_iter().collect()
10229 }
10230
10231 pub fn enable_breakpoint(
10232 &mut self,
10233 _: &crate::actions::EnableBreakpoint,
10234 window: &mut Window,
10235 cx: &mut Context<Self>,
10236 ) {
10237 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10238 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10239 continue;
10240 };
10241 self.edit_breakpoint_at_anchor(
10242 anchor,
10243 breakpoint,
10244 BreakpointEditAction::InvertState,
10245 cx,
10246 );
10247 }
10248 }
10249
10250 pub fn disable_breakpoint(
10251 &mut self,
10252 _: &crate::actions::DisableBreakpoint,
10253 window: &mut Window,
10254 cx: &mut Context<Self>,
10255 ) {
10256 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10257 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10258 continue;
10259 };
10260 self.edit_breakpoint_at_anchor(
10261 anchor,
10262 breakpoint,
10263 BreakpointEditAction::InvertState,
10264 cx,
10265 );
10266 }
10267 }
10268
10269 pub fn toggle_breakpoint(
10270 &mut self,
10271 _: &crate::actions::ToggleBreakpoint,
10272 window: &mut Window,
10273 cx: &mut Context<Self>,
10274 ) {
10275 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10276 if let Some(breakpoint) = breakpoint {
10277 self.edit_breakpoint_at_anchor(
10278 anchor,
10279 breakpoint,
10280 BreakpointEditAction::Toggle,
10281 cx,
10282 );
10283 } else {
10284 self.edit_breakpoint_at_anchor(
10285 anchor,
10286 Breakpoint::new_standard(),
10287 BreakpointEditAction::Toggle,
10288 cx,
10289 );
10290 }
10291 }
10292 }
10293
10294 pub fn edit_breakpoint_at_anchor(
10295 &mut self,
10296 breakpoint_position: Anchor,
10297 breakpoint: Breakpoint,
10298 edit_action: BreakpointEditAction,
10299 cx: &mut Context<Self>,
10300 ) {
10301 let Some(breakpoint_store) = &self.breakpoint_store else {
10302 return;
10303 };
10304
10305 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10306 if breakpoint_position == Anchor::min() {
10307 self.buffer()
10308 .read(cx)
10309 .excerpt_buffer_ids()
10310 .into_iter()
10311 .next()
10312 } else {
10313 None
10314 }
10315 }) else {
10316 return;
10317 };
10318
10319 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10320 return;
10321 };
10322
10323 breakpoint_store.update(cx, |breakpoint_store, cx| {
10324 breakpoint_store.toggle_breakpoint(
10325 buffer,
10326 BreakpointWithPosition {
10327 position: breakpoint_position.text_anchor,
10328 bp: breakpoint,
10329 },
10330 edit_action,
10331 cx,
10332 );
10333 });
10334
10335 cx.notify();
10336 }
10337
10338 #[cfg(any(test, feature = "test-support"))]
10339 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10340 self.breakpoint_store.clone()
10341 }
10342
10343 pub fn prepare_restore_change(
10344 &self,
10345 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10346 hunk: &MultiBufferDiffHunk,
10347 cx: &mut App,
10348 ) -> Option<()> {
10349 if hunk.is_created_file() {
10350 return None;
10351 }
10352 let buffer = self.buffer.read(cx);
10353 let diff = buffer.diff_for(hunk.buffer_id)?;
10354 let buffer = buffer.buffer(hunk.buffer_id)?;
10355 let buffer = buffer.read(cx);
10356 let original_text = diff
10357 .read(cx)
10358 .base_text()
10359 .as_rope()
10360 .slice(hunk.diff_base_byte_range.clone());
10361 let buffer_snapshot = buffer.snapshot();
10362 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10363 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10364 probe
10365 .0
10366 .start
10367 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10368 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10369 }) {
10370 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10371 Some(())
10372 } else {
10373 None
10374 }
10375 }
10376
10377 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10378 self.manipulate_lines(window, cx, |lines| lines.reverse())
10379 }
10380
10381 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10382 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10383 }
10384
10385 fn manipulate_lines<Fn>(
10386 &mut self,
10387 window: &mut Window,
10388 cx: &mut Context<Self>,
10389 mut callback: Fn,
10390 ) where
10391 Fn: FnMut(&mut Vec<&str>),
10392 {
10393 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10394
10395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10396 let buffer = self.buffer.read(cx).snapshot(cx);
10397
10398 let mut edits = Vec::new();
10399
10400 let selections = self.selections.all::<Point>(cx);
10401 let mut selections = selections.iter().peekable();
10402 let mut contiguous_row_selections = Vec::new();
10403 let mut new_selections = Vec::new();
10404 let mut added_lines = 0;
10405 let mut removed_lines = 0;
10406
10407 while let Some(selection) = selections.next() {
10408 let (start_row, end_row) = consume_contiguous_rows(
10409 &mut contiguous_row_selections,
10410 selection,
10411 &display_map,
10412 &mut selections,
10413 );
10414
10415 let start_point = Point::new(start_row.0, 0);
10416 let end_point = Point::new(
10417 end_row.previous_row().0,
10418 buffer.line_len(end_row.previous_row()),
10419 );
10420 let text = buffer
10421 .text_for_range(start_point..end_point)
10422 .collect::<String>();
10423
10424 let mut lines = text.split('\n').collect_vec();
10425
10426 let lines_before = lines.len();
10427 callback(&mut lines);
10428 let lines_after = lines.len();
10429
10430 edits.push((start_point..end_point, lines.join("\n")));
10431
10432 // Selections must change based on added and removed line count
10433 let start_row =
10434 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10435 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10436 new_selections.push(Selection {
10437 id: selection.id,
10438 start: start_row,
10439 end: end_row,
10440 goal: SelectionGoal::None,
10441 reversed: selection.reversed,
10442 });
10443
10444 if lines_after > lines_before {
10445 added_lines += lines_after - lines_before;
10446 } else if lines_before > lines_after {
10447 removed_lines += lines_before - lines_after;
10448 }
10449 }
10450
10451 self.transact(window, cx, |this, window, cx| {
10452 let buffer = this.buffer.update(cx, |buffer, cx| {
10453 buffer.edit(edits, None, cx);
10454 buffer.snapshot(cx)
10455 });
10456
10457 // Recalculate offsets on newly edited buffer
10458 let new_selections = new_selections
10459 .iter()
10460 .map(|s| {
10461 let start_point = Point::new(s.start.0, 0);
10462 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10463 Selection {
10464 id: s.id,
10465 start: buffer.point_to_offset(start_point),
10466 end: buffer.point_to_offset(end_point),
10467 goal: s.goal,
10468 reversed: s.reversed,
10469 }
10470 })
10471 .collect();
10472
10473 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10474 s.select(new_selections);
10475 });
10476
10477 this.request_autoscroll(Autoscroll::fit(), cx);
10478 });
10479 }
10480
10481 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10482 self.manipulate_text(window, cx, |text| {
10483 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10484 if has_upper_case_characters {
10485 text.to_lowercase()
10486 } else {
10487 text.to_uppercase()
10488 }
10489 })
10490 }
10491
10492 pub fn convert_to_upper_case(
10493 &mut self,
10494 _: &ConvertToUpperCase,
10495 window: &mut Window,
10496 cx: &mut Context<Self>,
10497 ) {
10498 self.manipulate_text(window, cx, |text| text.to_uppercase())
10499 }
10500
10501 pub fn convert_to_lower_case(
10502 &mut self,
10503 _: &ConvertToLowerCase,
10504 window: &mut Window,
10505 cx: &mut Context<Self>,
10506 ) {
10507 self.manipulate_text(window, cx, |text| text.to_lowercase())
10508 }
10509
10510 pub fn convert_to_title_case(
10511 &mut self,
10512 _: &ConvertToTitleCase,
10513 window: &mut Window,
10514 cx: &mut Context<Self>,
10515 ) {
10516 self.manipulate_text(window, cx, |text| {
10517 text.split('\n')
10518 .map(|line| line.to_case(Case::Title))
10519 .join("\n")
10520 })
10521 }
10522
10523 pub fn convert_to_snake_case(
10524 &mut self,
10525 _: &ConvertToSnakeCase,
10526 window: &mut Window,
10527 cx: &mut Context<Self>,
10528 ) {
10529 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10530 }
10531
10532 pub fn convert_to_kebab_case(
10533 &mut self,
10534 _: &ConvertToKebabCase,
10535 window: &mut Window,
10536 cx: &mut Context<Self>,
10537 ) {
10538 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10539 }
10540
10541 pub fn convert_to_upper_camel_case(
10542 &mut self,
10543 _: &ConvertToUpperCamelCase,
10544 window: &mut Window,
10545 cx: &mut Context<Self>,
10546 ) {
10547 self.manipulate_text(window, cx, |text| {
10548 text.split('\n')
10549 .map(|line| line.to_case(Case::UpperCamel))
10550 .join("\n")
10551 })
10552 }
10553
10554 pub fn convert_to_lower_camel_case(
10555 &mut self,
10556 _: &ConvertToLowerCamelCase,
10557 window: &mut Window,
10558 cx: &mut Context<Self>,
10559 ) {
10560 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10561 }
10562
10563 pub fn convert_to_opposite_case(
10564 &mut self,
10565 _: &ConvertToOppositeCase,
10566 window: &mut Window,
10567 cx: &mut Context<Self>,
10568 ) {
10569 self.manipulate_text(window, cx, |text| {
10570 text.chars()
10571 .fold(String::with_capacity(text.len()), |mut t, c| {
10572 if c.is_uppercase() {
10573 t.extend(c.to_lowercase());
10574 } else {
10575 t.extend(c.to_uppercase());
10576 }
10577 t
10578 })
10579 })
10580 }
10581
10582 pub fn convert_to_rot13(
10583 &mut self,
10584 _: &ConvertToRot13,
10585 window: &mut Window,
10586 cx: &mut Context<Self>,
10587 ) {
10588 self.manipulate_text(window, cx, |text| {
10589 text.chars()
10590 .map(|c| match c {
10591 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10592 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10593 _ => c,
10594 })
10595 .collect()
10596 })
10597 }
10598
10599 pub fn convert_to_rot47(
10600 &mut self,
10601 _: &ConvertToRot47,
10602 window: &mut Window,
10603 cx: &mut Context<Self>,
10604 ) {
10605 self.manipulate_text(window, cx, |text| {
10606 text.chars()
10607 .map(|c| {
10608 let code_point = c as u32;
10609 if code_point >= 33 && code_point <= 126 {
10610 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10611 }
10612 c
10613 })
10614 .collect()
10615 })
10616 }
10617
10618 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10619 where
10620 Fn: FnMut(&str) -> String,
10621 {
10622 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10623 let buffer = self.buffer.read(cx).snapshot(cx);
10624
10625 let mut new_selections = Vec::new();
10626 let mut edits = Vec::new();
10627 let mut selection_adjustment = 0i32;
10628
10629 for selection in self.selections.all::<usize>(cx) {
10630 let selection_is_empty = selection.is_empty();
10631
10632 let (start, end) = if selection_is_empty {
10633 let word_range = movement::surrounding_word(
10634 &display_map,
10635 selection.start.to_display_point(&display_map),
10636 );
10637 let start = word_range.start.to_offset(&display_map, Bias::Left);
10638 let end = word_range.end.to_offset(&display_map, Bias::Left);
10639 (start, end)
10640 } else {
10641 (selection.start, selection.end)
10642 };
10643
10644 let text = buffer.text_for_range(start..end).collect::<String>();
10645 let old_length = text.len() as i32;
10646 let text = callback(&text);
10647
10648 new_selections.push(Selection {
10649 start: (start as i32 - selection_adjustment) as usize,
10650 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10651 goal: SelectionGoal::None,
10652 ..selection
10653 });
10654
10655 selection_adjustment += old_length - text.len() as i32;
10656
10657 edits.push((start..end, text));
10658 }
10659
10660 self.transact(window, cx, |this, window, cx| {
10661 this.buffer.update(cx, |buffer, cx| {
10662 buffer.edit(edits, None, cx);
10663 });
10664
10665 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10666 s.select(new_selections);
10667 });
10668
10669 this.request_autoscroll(Autoscroll::fit(), cx);
10670 });
10671 }
10672
10673 pub fn move_selection_on_drop(
10674 &mut self,
10675 selection: &Selection<Anchor>,
10676 target: DisplayPoint,
10677 is_cut: bool,
10678 window: &mut Window,
10679 cx: &mut Context<Self>,
10680 ) {
10681 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10682 let buffer = &display_map.buffer_snapshot;
10683 let mut edits = Vec::new();
10684 let insert_point = display_map
10685 .clip_point(target, Bias::Left)
10686 .to_point(&display_map);
10687 let text = buffer
10688 .text_for_range(selection.start..selection.end)
10689 .collect::<String>();
10690 if is_cut {
10691 edits.push(((selection.start..selection.end), String::new()));
10692 }
10693 let insert_anchor = buffer.anchor_before(insert_point);
10694 edits.push(((insert_anchor..insert_anchor), text));
10695 let last_edit_start = insert_anchor.bias_left(buffer);
10696 let last_edit_end = insert_anchor.bias_right(buffer);
10697 self.transact(window, cx, |this, window, cx| {
10698 this.buffer.update(cx, |buffer, cx| {
10699 buffer.edit(edits, None, cx);
10700 });
10701 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10702 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10703 });
10704 });
10705 }
10706
10707 pub fn clear_selection_drag_state(&mut self) {
10708 self.selection_drag_state = SelectionDragState::None;
10709 }
10710
10711 pub fn duplicate(
10712 &mut self,
10713 upwards: bool,
10714 whole_lines: bool,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) {
10718 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10719
10720 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10721 let buffer = &display_map.buffer_snapshot;
10722 let selections = self.selections.all::<Point>(cx);
10723
10724 let mut edits = Vec::new();
10725 let mut selections_iter = selections.iter().peekable();
10726 while let Some(selection) = selections_iter.next() {
10727 let mut rows = selection.spanned_rows(false, &display_map);
10728 // duplicate line-wise
10729 if whole_lines || selection.start == selection.end {
10730 // Avoid duplicating the same lines twice.
10731 while let Some(next_selection) = selections_iter.peek() {
10732 let next_rows = next_selection.spanned_rows(false, &display_map);
10733 if next_rows.start < rows.end {
10734 rows.end = next_rows.end;
10735 selections_iter.next().unwrap();
10736 } else {
10737 break;
10738 }
10739 }
10740
10741 // Copy the text from the selected row region and splice it either at the start
10742 // or end of the region.
10743 let start = Point::new(rows.start.0, 0);
10744 let end = Point::new(
10745 rows.end.previous_row().0,
10746 buffer.line_len(rows.end.previous_row()),
10747 );
10748 let text = buffer
10749 .text_for_range(start..end)
10750 .chain(Some("\n"))
10751 .collect::<String>();
10752 let insert_location = if upwards {
10753 Point::new(rows.end.0, 0)
10754 } else {
10755 start
10756 };
10757 edits.push((insert_location..insert_location, text));
10758 } else {
10759 // duplicate character-wise
10760 let start = selection.start;
10761 let end = selection.end;
10762 let text = buffer.text_for_range(start..end).collect::<String>();
10763 edits.push((selection.end..selection.end, text));
10764 }
10765 }
10766
10767 self.transact(window, cx, |this, _, cx| {
10768 this.buffer.update(cx, |buffer, cx| {
10769 buffer.edit(edits, None, cx);
10770 });
10771
10772 this.request_autoscroll(Autoscroll::fit(), cx);
10773 });
10774 }
10775
10776 pub fn duplicate_line_up(
10777 &mut self,
10778 _: &DuplicateLineUp,
10779 window: &mut Window,
10780 cx: &mut Context<Self>,
10781 ) {
10782 self.duplicate(true, true, window, cx);
10783 }
10784
10785 pub fn duplicate_line_down(
10786 &mut self,
10787 _: &DuplicateLineDown,
10788 window: &mut Window,
10789 cx: &mut Context<Self>,
10790 ) {
10791 self.duplicate(false, true, window, cx);
10792 }
10793
10794 pub fn duplicate_selection(
10795 &mut self,
10796 _: &DuplicateSelection,
10797 window: &mut Window,
10798 cx: &mut Context<Self>,
10799 ) {
10800 self.duplicate(false, false, window, cx);
10801 }
10802
10803 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10804 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10805
10806 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10807 let buffer = self.buffer.read(cx).snapshot(cx);
10808
10809 let mut edits = Vec::new();
10810 let mut unfold_ranges = Vec::new();
10811 let mut refold_creases = Vec::new();
10812
10813 let selections = self.selections.all::<Point>(cx);
10814 let mut selections = selections.iter().peekable();
10815 let mut contiguous_row_selections = Vec::new();
10816 let mut new_selections = Vec::new();
10817
10818 while let Some(selection) = selections.next() {
10819 // Find all the selections that span a contiguous row range
10820 let (start_row, end_row) = consume_contiguous_rows(
10821 &mut contiguous_row_selections,
10822 selection,
10823 &display_map,
10824 &mut selections,
10825 );
10826
10827 // Move the text spanned by the row range to be before the line preceding the row range
10828 if start_row.0 > 0 {
10829 let range_to_move = Point::new(
10830 start_row.previous_row().0,
10831 buffer.line_len(start_row.previous_row()),
10832 )
10833 ..Point::new(
10834 end_row.previous_row().0,
10835 buffer.line_len(end_row.previous_row()),
10836 );
10837 let insertion_point = display_map
10838 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10839 .0;
10840
10841 // Don't move lines across excerpts
10842 if buffer
10843 .excerpt_containing(insertion_point..range_to_move.end)
10844 .is_some()
10845 {
10846 let text = buffer
10847 .text_for_range(range_to_move.clone())
10848 .flat_map(|s| s.chars())
10849 .skip(1)
10850 .chain(['\n'])
10851 .collect::<String>();
10852
10853 edits.push((
10854 buffer.anchor_after(range_to_move.start)
10855 ..buffer.anchor_before(range_to_move.end),
10856 String::new(),
10857 ));
10858 let insertion_anchor = buffer.anchor_after(insertion_point);
10859 edits.push((insertion_anchor..insertion_anchor, text));
10860
10861 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10862
10863 // Move selections up
10864 new_selections.extend(contiguous_row_selections.drain(..).map(
10865 |mut selection| {
10866 selection.start.row -= row_delta;
10867 selection.end.row -= row_delta;
10868 selection
10869 },
10870 ));
10871
10872 // Move folds up
10873 unfold_ranges.push(range_to_move.clone());
10874 for fold in display_map.folds_in_range(
10875 buffer.anchor_before(range_to_move.start)
10876 ..buffer.anchor_after(range_to_move.end),
10877 ) {
10878 let mut start = fold.range.start.to_point(&buffer);
10879 let mut end = fold.range.end.to_point(&buffer);
10880 start.row -= row_delta;
10881 end.row -= row_delta;
10882 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10883 }
10884 }
10885 }
10886
10887 // If we didn't move line(s), preserve the existing selections
10888 new_selections.append(&mut contiguous_row_selections);
10889 }
10890
10891 self.transact(window, cx, |this, window, cx| {
10892 this.unfold_ranges(&unfold_ranges, true, true, cx);
10893 this.buffer.update(cx, |buffer, cx| {
10894 for (range, text) in edits {
10895 buffer.edit([(range, text)], None, cx);
10896 }
10897 });
10898 this.fold_creases(refold_creases, true, window, cx);
10899 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10900 s.select(new_selections);
10901 })
10902 });
10903 }
10904
10905 pub fn move_line_down(
10906 &mut self,
10907 _: &MoveLineDown,
10908 window: &mut Window,
10909 cx: &mut Context<Self>,
10910 ) {
10911 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10912
10913 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10914 let buffer = self.buffer.read(cx).snapshot(cx);
10915
10916 let mut edits = Vec::new();
10917 let mut unfold_ranges = Vec::new();
10918 let mut refold_creases = Vec::new();
10919
10920 let selections = self.selections.all::<Point>(cx);
10921 let mut selections = selections.iter().peekable();
10922 let mut contiguous_row_selections = Vec::new();
10923 let mut new_selections = Vec::new();
10924
10925 while let Some(selection) = selections.next() {
10926 // Find all the selections that span a contiguous row range
10927 let (start_row, end_row) = consume_contiguous_rows(
10928 &mut contiguous_row_selections,
10929 selection,
10930 &display_map,
10931 &mut selections,
10932 );
10933
10934 // Move the text spanned by the row range to be after the last line of the row range
10935 if end_row.0 <= buffer.max_point().row {
10936 let range_to_move =
10937 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10938 let insertion_point = display_map
10939 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10940 .0;
10941
10942 // Don't move lines across excerpt boundaries
10943 if buffer
10944 .excerpt_containing(range_to_move.start..insertion_point)
10945 .is_some()
10946 {
10947 let mut text = String::from("\n");
10948 text.extend(buffer.text_for_range(range_to_move.clone()));
10949 text.pop(); // Drop trailing newline
10950 edits.push((
10951 buffer.anchor_after(range_to_move.start)
10952 ..buffer.anchor_before(range_to_move.end),
10953 String::new(),
10954 ));
10955 let insertion_anchor = buffer.anchor_after(insertion_point);
10956 edits.push((insertion_anchor..insertion_anchor, text));
10957
10958 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10959
10960 // Move selections down
10961 new_selections.extend(contiguous_row_selections.drain(..).map(
10962 |mut selection| {
10963 selection.start.row += row_delta;
10964 selection.end.row += row_delta;
10965 selection
10966 },
10967 ));
10968
10969 // Move folds down
10970 unfold_ranges.push(range_to_move.clone());
10971 for fold in display_map.folds_in_range(
10972 buffer.anchor_before(range_to_move.start)
10973 ..buffer.anchor_after(range_to_move.end),
10974 ) {
10975 let mut start = fold.range.start.to_point(&buffer);
10976 let mut end = fold.range.end.to_point(&buffer);
10977 start.row += row_delta;
10978 end.row += row_delta;
10979 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10980 }
10981 }
10982 }
10983
10984 // If we didn't move line(s), preserve the existing selections
10985 new_selections.append(&mut contiguous_row_selections);
10986 }
10987
10988 self.transact(window, cx, |this, window, cx| {
10989 this.unfold_ranges(&unfold_ranges, true, true, cx);
10990 this.buffer.update(cx, |buffer, cx| {
10991 for (range, text) in edits {
10992 buffer.edit([(range, text)], None, cx);
10993 }
10994 });
10995 this.fold_creases(refold_creases, true, window, cx);
10996 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10997 s.select(new_selections)
10998 });
10999 });
11000 }
11001
11002 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11003 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11004 let text_layout_details = &self.text_layout_details(window);
11005 self.transact(window, cx, |this, window, cx| {
11006 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11007 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11008 s.move_with(|display_map, selection| {
11009 if !selection.is_empty() {
11010 return;
11011 }
11012
11013 let mut head = selection.head();
11014 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11015 if head.column() == display_map.line_len(head.row()) {
11016 transpose_offset = display_map
11017 .buffer_snapshot
11018 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11019 }
11020
11021 if transpose_offset == 0 {
11022 return;
11023 }
11024
11025 *head.column_mut() += 1;
11026 head = display_map.clip_point(head, Bias::Right);
11027 let goal = SelectionGoal::HorizontalPosition(
11028 display_map
11029 .x_for_display_point(head, text_layout_details)
11030 .into(),
11031 );
11032 selection.collapse_to(head, goal);
11033
11034 let transpose_start = display_map
11035 .buffer_snapshot
11036 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11037 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11038 let transpose_end = display_map
11039 .buffer_snapshot
11040 .clip_offset(transpose_offset + 1, Bias::Right);
11041 if let Some(ch) =
11042 display_map.buffer_snapshot.chars_at(transpose_start).next()
11043 {
11044 edits.push((transpose_start..transpose_offset, String::new()));
11045 edits.push((transpose_end..transpose_end, ch.to_string()));
11046 }
11047 }
11048 });
11049 edits
11050 });
11051 this.buffer
11052 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11053 let selections = this.selections.all::<usize>(cx);
11054 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11055 s.select(selections);
11056 });
11057 });
11058 }
11059
11060 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11061 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11062 self.rewrap_impl(RewrapOptions::default(), cx)
11063 }
11064
11065 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11066 let buffer = self.buffer.read(cx).snapshot(cx);
11067 let selections = self.selections.all::<Point>(cx);
11068
11069 // Shrink and split selections to respect paragraph boundaries.
11070 let ranges = selections.into_iter().flat_map(|selection| {
11071 let language_settings = buffer.language_settings_at(selection.head(), cx);
11072 let language_scope = buffer.language_scope_at(selection.head());
11073
11074 let Some(start_row) = (selection.start.row..=selection.end.row)
11075 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11076 else {
11077 return vec![];
11078 };
11079 let Some(end_row) = (selection.start.row..=selection.end.row)
11080 .rev()
11081 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11082 else {
11083 return vec![];
11084 };
11085
11086 let mut row = start_row;
11087 let mut ranges = Vec::new();
11088 while let Some(blank_row) =
11089 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11090 {
11091 let next_paragraph_start = (blank_row + 1..=end_row)
11092 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11093 .unwrap();
11094 ranges.push((
11095 language_settings.clone(),
11096 language_scope.clone(),
11097 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11098 ));
11099 row = next_paragraph_start;
11100 }
11101 ranges.push((
11102 language_settings.clone(),
11103 language_scope.clone(),
11104 Point::new(row, 0)..Point::new(end_row, 0),
11105 ));
11106
11107 ranges
11108 });
11109
11110 let mut edits = Vec::new();
11111 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11112
11113 for (language_settings, language_scope, range) in ranges {
11114 let mut start_row = range.start.row;
11115 let mut end_row = range.end.row;
11116
11117 // Skip selections that overlap with a range that has already been rewrapped.
11118 let selection_range = start_row..end_row;
11119 if rewrapped_row_ranges
11120 .iter()
11121 .any(|range| range.overlaps(&selection_range))
11122 {
11123 continue;
11124 }
11125
11126 let tab_size = language_settings.tab_size;
11127
11128 // Since not all lines in the selection may be at the same indent
11129 // level, choose the indent size that is the most common between all
11130 // of the lines.
11131 //
11132 // If there is a tie, we use the deepest indent.
11133 let (indent_size, indent_end) = {
11134 let mut indent_size_occurrences = HashMap::default();
11135 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11136
11137 for row in start_row..=end_row {
11138 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11139 rows_by_indent_size.entry(indent).or_default().push(row);
11140 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11141 }
11142
11143 let indent_size = indent_size_occurrences
11144 .into_iter()
11145 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11146 .map(|(indent, _)| indent)
11147 .unwrap_or_default();
11148 let row = rows_by_indent_size[&indent_size][0];
11149 let indent_end = Point::new(row, indent_size.len);
11150
11151 (indent_size, indent_end)
11152 };
11153
11154 let mut line_prefix = indent_size.chars().collect::<String>();
11155
11156 let mut inside_comment = false;
11157 if let Some(comment_prefix) = language_scope.and_then(|language| {
11158 language
11159 .line_comment_prefixes()
11160 .iter()
11161 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11162 .cloned()
11163 }) {
11164 line_prefix.push_str(&comment_prefix);
11165 inside_comment = true;
11166 }
11167
11168 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11169 RewrapBehavior::InComments => inside_comment,
11170 RewrapBehavior::InSelections => !range.is_empty(),
11171 RewrapBehavior::Anywhere => true,
11172 };
11173
11174 let should_rewrap = options.override_language_settings
11175 || allow_rewrap_based_on_language
11176 || self.hard_wrap.is_some();
11177 if !should_rewrap {
11178 continue;
11179 }
11180
11181 if range.is_empty() {
11182 'expand_upwards: while start_row > 0 {
11183 let prev_row = start_row - 1;
11184 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11185 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11186 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11187 {
11188 start_row = prev_row;
11189 } else {
11190 break 'expand_upwards;
11191 }
11192 }
11193
11194 'expand_downwards: while end_row < buffer.max_point().row {
11195 let next_row = end_row + 1;
11196 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11197 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11198 && !buffer.is_line_blank(MultiBufferRow(next_row))
11199 {
11200 end_row = next_row;
11201 } else {
11202 break 'expand_downwards;
11203 }
11204 }
11205 }
11206
11207 let start = Point::new(start_row, 0);
11208 let start_offset = start.to_offset(&buffer);
11209 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11210 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11211 let Some(lines_without_prefixes) = selection_text
11212 .lines()
11213 .map(|line| {
11214 line.strip_prefix(&line_prefix)
11215 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11216 .with_context(|| {
11217 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11218 })
11219 })
11220 .collect::<Result<Vec<_>, _>>()
11221 .log_err()
11222 else {
11223 continue;
11224 };
11225
11226 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11227 buffer
11228 .language_settings_at(Point::new(start_row, 0), cx)
11229 .preferred_line_length as usize
11230 });
11231 let wrapped_text = wrap_with_prefix(
11232 line_prefix,
11233 lines_without_prefixes.join("\n"),
11234 wrap_column,
11235 tab_size,
11236 options.preserve_existing_whitespace,
11237 );
11238
11239 // TODO: should always use char-based diff while still supporting cursor behavior that
11240 // matches vim.
11241 let mut diff_options = DiffOptions::default();
11242 if options.override_language_settings {
11243 diff_options.max_word_diff_len = 0;
11244 diff_options.max_word_diff_line_count = 0;
11245 } else {
11246 diff_options.max_word_diff_len = usize::MAX;
11247 diff_options.max_word_diff_line_count = usize::MAX;
11248 }
11249
11250 for (old_range, new_text) in
11251 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11252 {
11253 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11254 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11255 edits.push((edit_start..edit_end, new_text));
11256 }
11257
11258 rewrapped_row_ranges.push(start_row..=end_row);
11259 }
11260
11261 self.buffer
11262 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11263 }
11264
11265 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11266 let mut text = String::new();
11267 let buffer = self.buffer.read(cx).snapshot(cx);
11268 let mut selections = self.selections.all::<Point>(cx);
11269 let mut clipboard_selections = Vec::with_capacity(selections.len());
11270 {
11271 let max_point = buffer.max_point();
11272 let mut is_first = true;
11273 for selection in &mut selections {
11274 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11275 if is_entire_line {
11276 selection.start = Point::new(selection.start.row, 0);
11277 if !selection.is_empty() && selection.end.column == 0 {
11278 selection.end = cmp::min(max_point, selection.end);
11279 } else {
11280 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11281 }
11282 selection.goal = SelectionGoal::None;
11283 }
11284 if is_first {
11285 is_first = false;
11286 } else {
11287 text += "\n";
11288 }
11289 let mut len = 0;
11290 for chunk in buffer.text_for_range(selection.start..selection.end) {
11291 text.push_str(chunk);
11292 len += chunk.len();
11293 }
11294 clipboard_selections.push(ClipboardSelection {
11295 len,
11296 is_entire_line,
11297 first_line_indent: buffer
11298 .indent_size_for_line(MultiBufferRow(selection.start.row))
11299 .len,
11300 });
11301 }
11302 }
11303
11304 self.transact(window, cx, |this, window, cx| {
11305 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11306 s.select(selections);
11307 });
11308 this.insert("", window, cx);
11309 });
11310 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11311 }
11312
11313 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11314 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11315 let item = self.cut_common(window, cx);
11316 cx.write_to_clipboard(item);
11317 }
11318
11319 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11320 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11321 self.change_selections(None, window, cx, |s| {
11322 s.move_with(|snapshot, sel| {
11323 if sel.is_empty() {
11324 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11325 }
11326 });
11327 });
11328 let item = self.cut_common(window, cx);
11329 cx.set_global(KillRing(item))
11330 }
11331
11332 pub fn kill_ring_yank(
11333 &mut self,
11334 _: &KillRingYank,
11335 window: &mut Window,
11336 cx: &mut Context<Self>,
11337 ) {
11338 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11339 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11340 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11341 (kill_ring.text().to_string(), kill_ring.metadata_json())
11342 } else {
11343 return;
11344 }
11345 } else {
11346 return;
11347 };
11348 self.do_paste(&text, metadata, false, window, cx);
11349 }
11350
11351 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11352 self.do_copy(true, cx);
11353 }
11354
11355 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11356 self.do_copy(false, cx);
11357 }
11358
11359 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11360 let selections = self.selections.all::<Point>(cx);
11361 let buffer = self.buffer.read(cx).read(cx);
11362 let mut text = String::new();
11363
11364 let mut clipboard_selections = Vec::with_capacity(selections.len());
11365 {
11366 let max_point = buffer.max_point();
11367 let mut is_first = true;
11368 for selection in &selections {
11369 let mut start = selection.start;
11370 let mut end = selection.end;
11371 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11372 if is_entire_line {
11373 start = Point::new(start.row, 0);
11374 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11375 }
11376
11377 let mut trimmed_selections = Vec::new();
11378 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11379 let row = MultiBufferRow(start.row);
11380 let first_indent = buffer.indent_size_for_line(row);
11381 if first_indent.len == 0 || start.column > first_indent.len {
11382 trimmed_selections.push(start..end);
11383 } else {
11384 trimmed_selections.push(
11385 Point::new(row.0, first_indent.len)
11386 ..Point::new(row.0, buffer.line_len(row)),
11387 );
11388 for row in start.row + 1..=end.row {
11389 let mut line_len = buffer.line_len(MultiBufferRow(row));
11390 if row == end.row {
11391 line_len = end.column;
11392 }
11393 if line_len == 0 {
11394 trimmed_selections
11395 .push(Point::new(row, 0)..Point::new(row, line_len));
11396 continue;
11397 }
11398 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11399 if row_indent_size.len >= first_indent.len {
11400 trimmed_selections.push(
11401 Point::new(row, first_indent.len)..Point::new(row, line_len),
11402 );
11403 } else {
11404 trimmed_selections.clear();
11405 trimmed_selections.push(start..end);
11406 break;
11407 }
11408 }
11409 }
11410 } else {
11411 trimmed_selections.push(start..end);
11412 }
11413
11414 for trimmed_range in trimmed_selections {
11415 if is_first {
11416 is_first = false;
11417 } else {
11418 text += "\n";
11419 }
11420 let mut len = 0;
11421 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11422 text.push_str(chunk);
11423 len += chunk.len();
11424 }
11425 clipboard_selections.push(ClipboardSelection {
11426 len,
11427 is_entire_line,
11428 first_line_indent: buffer
11429 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11430 .len,
11431 });
11432 }
11433 }
11434 }
11435
11436 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11437 text,
11438 clipboard_selections,
11439 ));
11440 }
11441
11442 pub fn do_paste(
11443 &mut self,
11444 text: &String,
11445 clipboard_selections: Option<Vec<ClipboardSelection>>,
11446 handle_entire_lines: bool,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 if self.read_only(cx) {
11451 return;
11452 }
11453
11454 let clipboard_text = Cow::Borrowed(text);
11455
11456 self.transact(window, cx, |this, window, cx| {
11457 if let Some(mut clipboard_selections) = clipboard_selections {
11458 let old_selections = this.selections.all::<usize>(cx);
11459 let all_selections_were_entire_line =
11460 clipboard_selections.iter().all(|s| s.is_entire_line);
11461 let first_selection_indent_column =
11462 clipboard_selections.first().map(|s| s.first_line_indent);
11463 if clipboard_selections.len() != old_selections.len() {
11464 clipboard_selections.drain(..);
11465 }
11466 let cursor_offset = this.selections.last::<usize>(cx).head();
11467 let mut auto_indent_on_paste = true;
11468
11469 this.buffer.update(cx, |buffer, cx| {
11470 let snapshot = buffer.read(cx);
11471 auto_indent_on_paste = snapshot
11472 .language_settings_at(cursor_offset, cx)
11473 .auto_indent_on_paste;
11474
11475 let mut start_offset = 0;
11476 let mut edits = Vec::new();
11477 let mut original_indent_columns = Vec::new();
11478 for (ix, selection) in old_selections.iter().enumerate() {
11479 let to_insert;
11480 let entire_line;
11481 let original_indent_column;
11482 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11483 let end_offset = start_offset + clipboard_selection.len;
11484 to_insert = &clipboard_text[start_offset..end_offset];
11485 entire_line = clipboard_selection.is_entire_line;
11486 start_offset = end_offset + 1;
11487 original_indent_column = Some(clipboard_selection.first_line_indent);
11488 } else {
11489 to_insert = clipboard_text.as_str();
11490 entire_line = all_selections_were_entire_line;
11491 original_indent_column = first_selection_indent_column
11492 }
11493
11494 // If the corresponding selection was empty when this slice of the
11495 // clipboard text was written, then the entire line containing the
11496 // selection was copied. If this selection is also currently empty,
11497 // then paste the line before the current line of the buffer.
11498 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11499 let column = selection.start.to_point(&snapshot).column as usize;
11500 let line_start = selection.start - column;
11501 line_start..line_start
11502 } else {
11503 selection.range()
11504 };
11505
11506 edits.push((range, to_insert));
11507 original_indent_columns.push(original_indent_column);
11508 }
11509 drop(snapshot);
11510
11511 buffer.edit(
11512 edits,
11513 if auto_indent_on_paste {
11514 Some(AutoindentMode::Block {
11515 original_indent_columns,
11516 })
11517 } else {
11518 None
11519 },
11520 cx,
11521 );
11522 });
11523
11524 let selections = this.selections.all::<usize>(cx);
11525 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11526 s.select(selections)
11527 });
11528 } else {
11529 this.insert(&clipboard_text, window, cx);
11530 }
11531 });
11532 }
11533
11534 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11535 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11536 if let Some(item) = cx.read_from_clipboard() {
11537 let entries = item.entries();
11538
11539 match entries.first() {
11540 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11541 // of all the pasted entries.
11542 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11543 .do_paste(
11544 clipboard_string.text(),
11545 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11546 true,
11547 window,
11548 cx,
11549 ),
11550 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11551 }
11552 }
11553 }
11554
11555 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11556 if self.read_only(cx) {
11557 return;
11558 }
11559
11560 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11561
11562 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11563 if let Some((selections, _)) =
11564 self.selection_history.transaction(transaction_id).cloned()
11565 {
11566 self.change_selections(None, window, cx, |s| {
11567 s.select_anchors(selections.to_vec());
11568 });
11569 } else {
11570 log::error!(
11571 "No entry in selection_history found for undo. \
11572 This may correspond to a bug where undo does not update the selection. \
11573 If this is occurring, please add details to \
11574 https://github.com/zed-industries/zed/issues/22692"
11575 );
11576 }
11577 self.request_autoscroll(Autoscroll::fit(), cx);
11578 self.unmark_text(window, cx);
11579 self.refresh_inline_completion(true, false, window, cx);
11580 cx.emit(EditorEvent::Edited { transaction_id });
11581 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11582 }
11583 }
11584
11585 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11586 if self.read_only(cx) {
11587 return;
11588 }
11589
11590 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11591
11592 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11593 if let Some((_, Some(selections))) =
11594 self.selection_history.transaction(transaction_id).cloned()
11595 {
11596 self.change_selections(None, window, cx, |s| {
11597 s.select_anchors(selections.to_vec());
11598 });
11599 } else {
11600 log::error!(
11601 "No entry in selection_history found for redo. \
11602 This may correspond to a bug where undo does not update the selection. \
11603 If this is occurring, please add details to \
11604 https://github.com/zed-industries/zed/issues/22692"
11605 );
11606 }
11607 self.request_autoscroll(Autoscroll::fit(), cx);
11608 self.unmark_text(window, cx);
11609 self.refresh_inline_completion(true, false, window, cx);
11610 cx.emit(EditorEvent::Edited { transaction_id });
11611 }
11612 }
11613
11614 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11615 self.buffer
11616 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11617 }
11618
11619 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11620 self.buffer
11621 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11622 }
11623
11624 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11626 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11627 s.move_with(|map, selection| {
11628 let cursor = if selection.is_empty() {
11629 movement::left(map, selection.start)
11630 } else {
11631 selection.start
11632 };
11633 selection.collapse_to(cursor, SelectionGoal::None);
11634 });
11635 })
11636 }
11637
11638 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11639 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11640 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11641 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11642 })
11643 }
11644
11645 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11646 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11647 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11648 s.move_with(|map, selection| {
11649 let cursor = if selection.is_empty() {
11650 movement::right(map, selection.end)
11651 } else {
11652 selection.end
11653 };
11654 selection.collapse_to(cursor, SelectionGoal::None)
11655 });
11656 })
11657 }
11658
11659 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11660 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11662 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11663 })
11664 }
11665
11666 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11667 if self.take_rename(true, window, cx).is_some() {
11668 return;
11669 }
11670
11671 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11672 cx.propagate();
11673 return;
11674 }
11675
11676 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11677
11678 let text_layout_details = &self.text_layout_details(window);
11679 let selection_count = self.selections.count();
11680 let first_selection = self.selections.first_anchor();
11681
11682 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11683 s.move_with(|map, selection| {
11684 if !selection.is_empty() {
11685 selection.goal = SelectionGoal::None;
11686 }
11687 let (cursor, goal) = movement::up(
11688 map,
11689 selection.start,
11690 selection.goal,
11691 false,
11692 text_layout_details,
11693 );
11694 selection.collapse_to(cursor, goal);
11695 });
11696 });
11697
11698 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11699 {
11700 cx.propagate();
11701 }
11702 }
11703
11704 pub fn move_up_by_lines(
11705 &mut self,
11706 action: &MoveUpByLines,
11707 window: &mut Window,
11708 cx: &mut Context<Self>,
11709 ) {
11710 if self.take_rename(true, window, cx).is_some() {
11711 return;
11712 }
11713
11714 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11715 cx.propagate();
11716 return;
11717 }
11718
11719 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11720
11721 let text_layout_details = &self.text_layout_details(window);
11722
11723 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11724 s.move_with(|map, selection| {
11725 if !selection.is_empty() {
11726 selection.goal = SelectionGoal::None;
11727 }
11728 let (cursor, goal) = movement::up_by_rows(
11729 map,
11730 selection.start,
11731 action.lines,
11732 selection.goal,
11733 false,
11734 text_layout_details,
11735 );
11736 selection.collapse_to(cursor, goal);
11737 });
11738 })
11739 }
11740
11741 pub fn move_down_by_lines(
11742 &mut self,
11743 action: &MoveDownByLines,
11744 window: &mut Window,
11745 cx: &mut Context<Self>,
11746 ) {
11747 if self.take_rename(true, window, cx).is_some() {
11748 return;
11749 }
11750
11751 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11752 cx.propagate();
11753 return;
11754 }
11755
11756 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11757
11758 let text_layout_details = &self.text_layout_details(window);
11759
11760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11761 s.move_with(|map, selection| {
11762 if !selection.is_empty() {
11763 selection.goal = SelectionGoal::None;
11764 }
11765 let (cursor, goal) = movement::down_by_rows(
11766 map,
11767 selection.start,
11768 action.lines,
11769 selection.goal,
11770 false,
11771 text_layout_details,
11772 );
11773 selection.collapse_to(cursor, goal);
11774 });
11775 })
11776 }
11777
11778 pub fn select_down_by_lines(
11779 &mut self,
11780 action: &SelectDownByLines,
11781 window: &mut Window,
11782 cx: &mut Context<Self>,
11783 ) {
11784 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11785 let text_layout_details = &self.text_layout_details(window);
11786 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11787 s.move_heads_with(|map, head, goal| {
11788 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11789 })
11790 })
11791 }
11792
11793 pub fn select_up_by_lines(
11794 &mut self,
11795 action: &SelectUpByLines,
11796 window: &mut Window,
11797 cx: &mut Context<Self>,
11798 ) {
11799 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11800 let text_layout_details = &self.text_layout_details(window);
11801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11802 s.move_heads_with(|map, head, goal| {
11803 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11804 })
11805 })
11806 }
11807
11808 pub fn select_page_up(
11809 &mut self,
11810 _: &SelectPageUp,
11811 window: &mut Window,
11812 cx: &mut Context<Self>,
11813 ) {
11814 let Some(row_count) = self.visible_row_count() else {
11815 return;
11816 };
11817
11818 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11819
11820 let text_layout_details = &self.text_layout_details(window);
11821
11822 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11823 s.move_heads_with(|map, head, goal| {
11824 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11825 })
11826 })
11827 }
11828
11829 pub fn move_page_up(
11830 &mut self,
11831 action: &MovePageUp,
11832 window: &mut Window,
11833 cx: &mut Context<Self>,
11834 ) {
11835 if self.take_rename(true, window, cx).is_some() {
11836 return;
11837 }
11838
11839 if self
11840 .context_menu
11841 .borrow_mut()
11842 .as_mut()
11843 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11844 .unwrap_or(false)
11845 {
11846 return;
11847 }
11848
11849 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11850 cx.propagate();
11851 return;
11852 }
11853
11854 let Some(row_count) = self.visible_row_count() else {
11855 return;
11856 };
11857
11858 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11859
11860 let autoscroll = if action.center_cursor {
11861 Autoscroll::center()
11862 } else {
11863 Autoscroll::fit()
11864 };
11865
11866 let text_layout_details = &self.text_layout_details(window);
11867
11868 self.change_selections(Some(autoscroll), window, cx, |s| {
11869 s.move_with(|map, selection| {
11870 if !selection.is_empty() {
11871 selection.goal = SelectionGoal::None;
11872 }
11873 let (cursor, goal) = movement::up_by_rows(
11874 map,
11875 selection.end,
11876 row_count,
11877 selection.goal,
11878 false,
11879 text_layout_details,
11880 );
11881 selection.collapse_to(cursor, goal);
11882 });
11883 });
11884 }
11885
11886 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11887 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11888 let text_layout_details = &self.text_layout_details(window);
11889 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11890 s.move_heads_with(|map, head, goal| {
11891 movement::up(map, head, goal, false, text_layout_details)
11892 })
11893 })
11894 }
11895
11896 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11897 self.take_rename(true, window, cx);
11898
11899 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11900 cx.propagate();
11901 return;
11902 }
11903
11904 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11905
11906 let text_layout_details = &self.text_layout_details(window);
11907 let selection_count = self.selections.count();
11908 let first_selection = self.selections.first_anchor();
11909
11910 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11911 s.move_with(|map, selection| {
11912 if !selection.is_empty() {
11913 selection.goal = SelectionGoal::None;
11914 }
11915 let (cursor, goal) = movement::down(
11916 map,
11917 selection.end,
11918 selection.goal,
11919 false,
11920 text_layout_details,
11921 );
11922 selection.collapse_to(cursor, goal);
11923 });
11924 });
11925
11926 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11927 {
11928 cx.propagate();
11929 }
11930 }
11931
11932 pub fn select_page_down(
11933 &mut self,
11934 _: &SelectPageDown,
11935 window: &mut Window,
11936 cx: &mut Context<Self>,
11937 ) {
11938 let Some(row_count) = self.visible_row_count() else {
11939 return;
11940 };
11941
11942 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11943
11944 let text_layout_details = &self.text_layout_details(window);
11945
11946 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11947 s.move_heads_with(|map, head, goal| {
11948 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11949 })
11950 })
11951 }
11952
11953 pub fn move_page_down(
11954 &mut self,
11955 action: &MovePageDown,
11956 window: &mut Window,
11957 cx: &mut Context<Self>,
11958 ) {
11959 if self.take_rename(true, window, cx).is_some() {
11960 return;
11961 }
11962
11963 if self
11964 .context_menu
11965 .borrow_mut()
11966 .as_mut()
11967 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11968 .unwrap_or(false)
11969 {
11970 return;
11971 }
11972
11973 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11974 cx.propagate();
11975 return;
11976 }
11977
11978 let Some(row_count) = self.visible_row_count() else {
11979 return;
11980 };
11981
11982 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11983
11984 let autoscroll = if action.center_cursor {
11985 Autoscroll::center()
11986 } else {
11987 Autoscroll::fit()
11988 };
11989
11990 let text_layout_details = &self.text_layout_details(window);
11991 self.change_selections(Some(autoscroll), window, cx, |s| {
11992 s.move_with(|map, selection| {
11993 if !selection.is_empty() {
11994 selection.goal = SelectionGoal::None;
11995 }
11996 let (cursor, goal) = movement::down_by_rows(
11997 map,
11998 selection.end,
11999 row_count,
12000 selection.goal,
12001 false,
12002 text_layout_details,
12003 );
12004 selection.collapse_to(cursor, goal);
12005 });
12006 });
12007 }
12008
12009 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12010 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12011 let text_layout_details = &self.text_layout_details(window);
12012 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12013 s.move_heads_with(|map, head, goal| {
12014 movement::down(map, head, goal, false, text_layout_details)
12015 })
12016 });
12017 }
12018
12019 pub fn context_menu_first(
12020 &mut self,
12021 _: &ContextMenuFirst,
12022 window: &mut Window,
12023 cx: &mut Context<Self>,
12024 ) {
12025 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12026 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12027 }
12028 }
12029
12030 pub fn context_menu_prev(
12031 &mut self,
12032 _: &ContextMenuPrevious,
12033 window: &mut Window,
12034 cx: &mut Context<Self>,
12035 ) {
12036 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12037 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12038 }
12039 }
12040
12041 pub fn context_menu_next(
12042 &mut self,
12043 _: &ContextMenuNext,
12044 window: &mut Window,
12045 cx: &mut Context<Self>,
12046 ) {
12047 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12048 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12049 }
12050 }
12051
12052 pub fn context_menu_last(
12053 &mut self,
12054 _: &ContextMenuLast,
12055 window: &mut Window,
12056 cx: &mut Context<Self>,
12057 ) {
12058 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12059 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12060 }
12061 }
12062
12063 pub fn move_to_previous_word_start(
12064 &mut self,
12065 _: &MoveToPreviousWordStart,
12066 window: &mut Window,
12067 cx: &mut Context<Self>,
12068 ) {
12069 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12070 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12071 s.move_cursors_with(|map, head, _| {
12072 (
12073 movement::previous_word_start(map, head),
12074 SelectionGoal::None,
12075 )
12076 });
12077 })
12078 }
12079
12080 pub fn move_to_previous_subword_start(
12081 &mut self,
12082 _: &MoveToPreviousSubwordStart,
12083 window: &mut Window,
12084 cx: &mut Context<Self>,
12085 ) {
12086 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12087 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12088 s.move_cursors_with(|map, head, _| {
12089 (
12090 movement::previous_subword_start(map, head),
12091 SelectionGoal::None,
12092 )
12093 });
12094 })
12095 }
12096
12097 pub fn select_to_previous_word_start(
12098 &mut self,
12099 _: &SelectToPreviousWordStart,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12104 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12105 s.move_heads_with(|map, head, _| {
12106 (
12107 movement::previous_word_start(map, head),
12108 SelectionGoal::None,
12109 )
12110 });
12111 })
12112 }
12113
12114 pub fn select_to_previous_subword_start(
12115 &mut self,
12116 _: &SelectToPreviousSubwordStart,
12117 window: &mut Window,
12118 cx: &mut Context<Self>,
12119 ) {
12120 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12121 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12122 s.move_heads_with(|map, head, _| {
12123 (
12124 movement::previous_subword_start(map, head),
12125 SelectionGoal::None,
12126 )
12127 });
12128 })
12129 }
12130
12131 pub fn delete_to_previous_word_start(
12132 &mut self,
12133 action: &DeleteToPreviousWordStart,
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 = if action.ignore_newlines {
12144 movement::previous_word_start(map, selection.head())
12145 } else {
12146 movement::previous_word_start_or_newline(map, selection.head())
12147 };
12148 selection.set_head(cursor, SelectionGoal::None);
12149 }
12150 });
12151 });
12152 this.insert("", window, cx);
12153 });
12154 }
12155
12156 pub fn delete_to_previous_subword_start(
12157 &mut self,
12158 _: &DeleteToPreviousSubwordStart,
12159 window: &mut Window,
12160 cx: &mut Context<Self>,
12161 ) {
12162 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12163 self.transact(window, cx, |this, window, cx| {
12164 this.select_autoclose_pair(window, cx);
12165 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12166 s.move_with(|map, selection| {
12167 if selection.is_empty() {
12168 let cursor = movement::previous_subword_start(map, selection.head());
12169 selection.set_head(cursor, SelectionGoal::None);
12170 }
12171 });
12172 });
12173 this.insert("", window, cx);
12174 });
12175 }
12176
12177 pub fn move_to_next_word_end(
12178 &mut self,
12179 _: &MoveToNextWordEnd,
12180 window: &mut Window,
12181 cx: &mut Context<Self>,
12182 ) {
12183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12184 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12185 s.move_cursors_with(|map, head, _| {
12186 (movement::next_word_end(map, head), SelectionGoal::None)
12187 });
12188 })
12189 }
12190
12191 pub fn move_to_next_subword_end(
12192 &mut self,
12193 _: &MoveToNextSubwordEnd,
12194 window: &mut Window,
12195 cx: &mut Context<Self>,
12196 ) {
12197 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12198 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12199 s.move_cursors_with(|map, head, _| {
12200 (movement::next_subword_end(map, head), SelectionGoal::None)
12201 });
12202 })
12203 }
12204
12205 pub fn select_to_next_word_end(
12206 &mut self,
12207 _: &SelectToNextWordEnd,
12208 window: &mut Window,
12209 cx: &mut Context<Self>,
12210 ) {
12211 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12212 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12213 s.move_heads_with(|map, head, _| {
12214 (movement::next_word_end(map, head), SelectionGoal::None)
12215 });
12216 })
12217 }
12218
12219 pub fn select_to_next_subword_end(
12220 &mut self,
12221 _: &SelectToNextSubwordEnd,
12222 window: &mut Window,
12223 cx: &mut Context<Self>,
12224 ) {
12225 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12227 s.move_heads_with(|map, head, _| {
12228 (movement::next_subword_end(map, head), SelectionGoal::None)
12229 });
12230 })
12231 }
12232
12233 pub fn delete_to_next_word_end(
12234 &mut self,
12235 action: &DeleteToNextWordEnd,
12236 window: &mut Window,
12237 cx: &mut Context<Self>,
12238 ) {
12239 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12240 self.transact(window, cx, |this, window, cx| {
12241 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12242 s.move_with(|map, selection| {
12243 if selection.is_empty() {
12244 let cursor = if action.ignore_newlines {
12245 movement::next_word_end(map, selection.head())
12246 } else {
12247 movement::next_word_end_or_newline(map, selection.head())
12248 };
12249 selection.set_head(cursor, SelectionGoal::None);
12250 }
12251 });
12252 });
12253 this.insert("", window, cx);
12254 });
12255 }
12256
12257 pub fn delete_to_next_subword_end(
12258 &mut self,
12259 _: &DeleteToNextSubwordEnd,
12260 window: &mut Window,
12261 cx: &mut Context<Self>,
12262 ) {
12263 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12264 self.transact(window, cx, |this, window, cx| {
12265 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12266 s.move_with(|map, selection| {
12267 if selection.is_empty() {
12268 let cursor = movement::next_subword_end(map, selection.head());
12269 selection.set_head(cursor, SelectionGoal::None);
12270 }
12271 });
12272 });
12273 this.insert("", window, cx);
12274 });
12275 }
12276
12277 pub fn move_to_beginning_of_line(
12278 &mut self,
12279 action: &MoveToBeginningOfLine,
12280 window: &mut Window,
12281 cx: &mut Context<Self>,
12282 ) {
12283 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12284 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12285 s.move_cursors_with(|map, head, _| {
12286 (
12287 movement::indented_line_beginning(
12288 map,
12289 head,
12290 action.stop_at_soft_wraps,
12291 action.stop_at_indent,
12292 ),
12293 SelectionGoal::None,
12294 )
12295 });
12296 })
12297 }
12298
12299 pub fn select_to_beginning_of_line(
12300 &mut self,
12301 action: &SelectToBeginningOfLine,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12307 s.move_heads_with(|map, head, _| {
12308 (
12309 movement::indented_line_beginning(
12310 map,
12311 head,
12312 action.stop_at_soft_wraps,
12313 action.stop_at_indent,
12314 ),
12315 SelectionGoal::None,
12316 )
12317 });
12318 });
12319 }
12320
12321 pub fn delete_to_beginning_of_line(
12322 &mut self,
12323 action: &DeleteToBeginningOfLine,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) {
12327 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12328 self.transact(window, cx, |this, window, cx| {
12329 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12330 s.move_with(|_, selection| {
12331 selection.reversed = true;
12332 });
12333 });
12334
12335 this.select_to_beginning_of_line(
12336 &SelectToBeginningOfLine {
12337 stop_at_soft_wraps: false,
12338 stop_at_indent: action.stop_at_indent,
12339 },
12340 window,
12341 cx,
12342 );
12343 this.backspace(&Backspace, window, cx);
12344 });
12345 }
12346
12347 pub fn move_to_end_of_line(
12348 &mut self,
12349 action: &MoveToEndOfLine,
12350 window: &mut Window,
12351 cx: &mut Context<Self>,
12352 ) {
12353 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12355 s.move_cursors_with(|map, head, _| {
12356 (
12357 movement::line_end(map, head, action.stop_at_soft_wraps),
12358 SelectionGoal::None,
12359 )
12360 });
12361 })
12362 }
12363
12364 pub fn select_to_end_of_line(
12365 &mut self,
12366 action: &SelectToEndOfLine,
12367 window: &mut Window,
12368 cx: &mut Context<Self>,
12369 ) {
12370 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12371 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12372 s.move_heads_with(|map, head, _| {
12373 (
12374 movement::line_end(map, head, action.stop_at_soft_wraps),
12375 SelectionGoal::None,
12376 )
12377 });
12378 })
12379 }
12380
12381 pub fn delete_to_end_of_line(
12382 &mut self,
12383 _: &DeleteToEndOfLine,
12384 window: &mut Window,
12385 cx: &mut Context<Self>,
12386 ) {
12387 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12388 self.transact(window, cx, |this, window, cx| {
12389 this.select_to_end_of_line(
12390 &SelectToEndOfLine {
12391 stop_at_soft_wraps: false,
12392 },
12393 window,
12394 cx,
12395 );
12396 this.delete(&Delete, window, cx);
12397 });
12398 }
12399
12400 pub fn cut_to_end_of_line(
12401 &mut self,
12402 _: &CutToEndOfLine,
12403 window: &mut Window,
12404 cx: &mut Context<Self>,
12405 ) {
12406 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12407 self.transact(window, cx, |this, window, cx| {
12408 this.select_to_end_of_line(
12409 &SelectToEndOfLine {
12410 stop_at_soft_wraps: false,
12411 },
12412 window,
12413 cx,
12414 );
12415 this.cut(&Cut, window, cx);
12416 });
12417 }
12418
12419 pub fn move_to_start_of_paragraph(
12420 &mut self,
12421 _: &MoveToStartOfParagraph,
12422 window: &mut Window,
12423 cx: &mut Context<Self>,
12424 ) {
12425 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12426 cx.propagate();
12427 return;
12428 }
12429 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12430 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12431 s.move_with(|map, selection| {
12432 selection.collapse_to(
12433 movement::start_of_paragraph(map, selection.head(), 1),
12434 SelectionGoal::None,
12435 )
12436 });
12437 })
12438 }
12439
12440 pub fn move_to_end_of_paragraph(
12441 &mut self,
12442 _: &MoveToEndOfParagraph,
12443 window: &mut Window,
12444 cx: &mut Context<Self>,
12445 ) {
12446 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12447 cx.propagate();
12448 return;
12449 }
12450 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12451 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12452 s.move_with(|map, selection| {
12453 selection.collapse_to(
12454 movement::end_of_paragraph(map, selection.head(), 1),
12455 SelectionGoal::None,
12456 )
12457 });
12458 })
12459 }
12460
12461 pub fn select_to_start_of_paragraph(
12462 &mut self,
12463 _: &SelectToStartOfParagraph,
12464 window: &mut Window,
12465 cx: &mut Context<Self>,
12466 ) {
12467 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12468 cx.propagate();
12469 return;
12470 }
12471 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12472 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12473 s.move_heads_with(|map, head, _| {
12474 (
12475 movement::start_of_paragraph(map, head, 1),
12476 SelectionGoal::None,
12477 )
12478 });
12479 })
12480 }
12481
12482 pub fn select_to_end_of_paragraph(
12483 &mut self,
12484 _: &SelectToEndOfParagraph,
12485 window: &mut Window,
12486 cx: &mut Context<Self>,
12487 ) {
12488 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12489 cx.propagate();
12490 return;
12491 }
12492 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12493 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12494 s.move_heads_with(|map, head, _| {
12495 (
12496 movement::end_of_paragraph(map, head, 1),
12497 SelectionGoal::None,
12498 )
12499 });
12500 })
12501 }
12502
12503 pub fn move_to_start_of_excerpt(
12504 &mut self,
12505 _: &MoveToStartOfExcerpt,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) {
12509 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12510 cx.propagate();
12511 return;
12512 }
12513 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
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::Prev,
12521 ),
12522 SelectionGoal::None,
12523 )
12524 });
12525 })
12526 }
12527
12528 pub fn move_to_start_of_next_excerpt(
12529 &mut self,
12530 _: &MoveToStartOfNextExcerpt,
12531 window: &mut Window,
12532 cx: &mut Context<Self>,
12533 ) {
12534 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12535 cx.propagate();
12536 return;
12537 }
12538
12539 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12540 s.move_with(|map, selection| {
12541 selection.collapse_to(
12542 movement::start_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_excerpt(
12554 &mut self,
12555 _: &MoveToEndOfExcerpt,
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::Next,
12571 ),
12572 SelectionGoal::None,
12573 )
12574 });
12575 })
12576 }
12577
12578 pub fn move_to_end_of_previous_excerpt(
12579 &mut self,
12580 _: &MoveToEndOfPreviousExcerpt,
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_with(|map, selection| {
12591 selection.collapse_to(
12592 movement::end_of_excerpt(
12593 map,
12594 selection.head(),
12595 workspace::searchable::Direction::Prev,
12596 ),
12597 SelectionGoal::None,
12598 )
12599 });
12600 })
12601 }
12602
12603 pub fn select_to_start_of_excerpt(
12604 &mut self,
12605 _: &SelectToStartOfExcerpt,
12606 window: &mut Window,
12607 cx: &mut Context<Self>,
12608 ) {
12609 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12610 cx.propagate();
12611 return;
12612 }
12613 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12615 s.move_heads_with(|map, head, _| {
12616 (
12617 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12618 SelectionGoal::None,
12619 )
12620 });
12621 })
12622 }
12623
12624 pub fn select_to_start_of_next_excerpt(
12625 &mut self,
12626 _: &SelectToStartOfNextExcerpt,
12627 window: &mut Window,
12628 cx: &mut Context<Self>,
12629 ) {
12630 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12631 cx.propagate();
12632 return;
12633 }
12634 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12635 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12636 s.move_heads_with(|map, head, _| {
12637 (
12638 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12639 SelectionGoal::None,
12640 )
12641 });
12642 })
12643 }
12644
12645 pub fn select_to_end_of_excerpt(
12646 &mut self,
12647 _: &SelectToEndOfExcerpt,
12648 window: &mut Window,
12649 cx: &mut Context<Self>,
12650 ) {
12651 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12652 cx.propagate();
12653 return;
12654 }
12655 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12656 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12657 s.move_heads_with(|map, head, _| {
12658 (
12659 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12660 SelectionGoal::None,
12661 )
12662 });
12663 })
12664 }
12665
12666 pub fn select_to_end_of_previous_excerpt(
12667 &mut self,
12668 _: &SelectToEndOfPreviousExcerpt,
12669 window: &mut Window,
12670 cx: &mut Context<Self>,
12671 ) {
12672 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12673 cx.propagate();
12674 return;
12675 }
12676 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12677 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12678 s.move_heads_with(|map, head, _| {
12679 (
12680 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12681 SelectionGoal::None,
12682 )
12683 });
12684 })
12685 }
12686
12687 pub fn move_to_beginning(
12688 &mut self,
12689 _: &MoveToBeginning,
12690 window: &mut Window,
12691 cx: &mut Context<Self>,
12692 ) {
12693 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12694 cx.propagate();
12695 return;
12696 }
12697 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12698 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12699 s.select_ranges(vec![0..0]);
12700 });
12701 }
12702
12703 pub fn select_to_beginning(
12704 &mut self,
12705 _: &SelectToBeginning,
12706 window: &mut Window,
12707 cx: &mut Context<Self>,
12708 ) {
12709 let mut selection = self.selections.last::<Point>(cx);
12710 selection.set_head(Point::zero(), SelectionGoal::None);
12711 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12712 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12713 s.select(vec![selection]);
12714 });
12715 }
12716
12717 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12718 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12719 cx.propagate();
12720 return;
12721 }
12722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12723 let cursor = self.buffer.read(cx).read(cx).len();
12724 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12725 s.select_ranges(vec![cursor..cursor])
12726 });
12727 }
12728
12729 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12730 self.nav_history = nav_history;
12731 }
12732
12733 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12734 self.nav_history.as_ref()
12735 }
12736
12737 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12738 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12739 }
12740
12741 fn push_to_nav_history(
12742 &mut self,
12743 cursor_anchor: Anchor,
12744 new_position: Option<Point>,
12745 is_deactivate: bool,
12746 cx: &mut Context<Self>,
12747 ) {
12748 if let Some(nav_history) = self.nav_history.as_mut() {
12749 let buffer = self.buffer.read(cx).read(cx);
12750 let cursor_position = cursor_anchor.to_point(&buffer);
12751 let scroll_state = self.scroll_manager.anchor();
12752 let scroll_top_row = scroll_state.top_row(&buffer);
12753 drop(buffer);
12754
12755 if let Some(new_position) = new_position {
12756 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12757 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12758 return;
12759 }
12760 }
12761
12762 nav_history.push(
12763 Some(NavigationData {
12764 cursor_anchor,
12765 cursor_position,
12766 scroll_anchor: scroll_state,
12767 scroll_top_row,
12768 }),
12769 cx,
12770 );
12771 cx.emit(EditorEvent::PushedToNavHistory {
12772 anchor: cursor_anchor,
12773 is_deactivate,
12774 })
12775 }
12776 }
12777
12778 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12779 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12780 let buffer = self.buffer.read(cx).snapshot(cx);
12781 let mut selection = self.selections.first::<usize>(cx);
12782 selection.set_head(buffer.len(), SelectionGoal::None);
12783 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12784 s.select(vec![selection]);
12785 });
12786 }
12787
12788 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12789 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12790 let end = self.buffer.read(cx).read(cx).len();
12791 self.change_selections(None, window, cx, |s| {
12792 s.select_ranges(vec![0..end]);
12793 });
12794 }
12795
12796 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12797 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12798 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12799 let mut selections = self.selections.all::<Point>(cx);
12800 let max_point = display_map.buffer_snapshot.max_point();
12801 for selection in &mut selections {
12802 let rows = selection.spanned_rows(true, &display_map);
12803 selection.start = Point::new(rows.start.0, 0);
12804 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12805 selection.reversed = false;
12806 }
12807 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12808 s.select(selections);
12809 });
12810 }
12811
12812 pub fn split_selection_into_lines(
12813 &mut self,
12814 _: &SplitSelectionIntoLines,
12815 window: &mut Window,
12816 cx: &mut Context<Self>,
12817 ) {
12818 let selections = self
12819 .selections
12820 .all::<Point>(cx)
12821 .into_iter()
12822 .map(|selection| selection.start..selection.end)
12823 .collect::<Vec<_>>();
12824 self.unfold_ranges(&selections, true, true, cx);
12825
12826 let mut new_selection_ranges = Vec::new();
12827 {
12828 let buffer = self.buffer.read(cx).read(cx);
12829 for selection in selections {
12830 for row in selection.start.row..selection.end.row {
12831 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12832 new_selection_ranges.push(cursor..cursor);
12833 }
12834
12835 let is_multiline_selection = selection.start.row != selection.end.row;
12836 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12837 // so this action feels more ergonomic when paired with other selection operations
12838 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12839 if !should_skip_last {
12840 new_selection_ranges.push(selection.end..selection.end);
12841 }
12842 }
12843 }
12844 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12845 s.select_ranges(new_selection_ranges);
12846 });
12847 }
12848
12849 pub fn add_selection_above(
12850 &mut self,
12851 _: &AddSelectionAbove,
12852 window: &mut Window,
12853 cx: &mut Context<Self>,
12854 ) {
12855 self.add_selection(true, window, cx);
12856 }
12857
12858 pub fn add_selection_below(
12859 &mut self,
12860 _: &AddSelectionBelow,
12861 window: &mut Window,
12862 cx: &mut Context<Self>,
12863 ) {
12864 self.add_selection(false, window, cx);
12865 }
12866
12867 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12868 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12869
12870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12871 let all_selections = self.selections.all::<Point>(cx);
12872 let text_layout_details = self.text_layout_details(window);
12873
12874 let (mut columnar_selections, new_selections_to_columnarize) = {
12875 if let Some(state) = self.add_selections_state.as_ref() {
12876 let columnar_selection_ids: HashSet<_> = state
12877 .groups
12878 .iter()
12879 .flat_map(|group| group.stack.iter())
12880 .copied()
12881 .collect();
12882
12883 all_selections
12884 .into_iter()
12885 .partition(|s| columnar_selection_ids.contains(&s.id))
12886 } else {
12887 (Vec::new(), all_selections)
12888 }
12889 };
12890
12891 let mut state = self
12892 .add_selections_state
12893 .take()
12894 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12895
12896 for selection in new_selections_to_columnarize {
12897 let range = selection.display_range(&display_map).sorted();
12898 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12899 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12900 let positions = start_x.min(end_x)..start_x.max(end_x);
12901 let mut stack = Vec::new();
12902 for row in range.start.row().0..=range.end.row().0 {
12903 if let Some(selection) = self.selections.build_columnar_selection(
12904 &display_map,
12905 DisplayRow(row),
12906 &positions,
12907 selection.reversed,
12908 &text_layout_details,
12909 ) {
12910 stack.push(selection.id);
12911 columnar_selections.push(selection);
12912 }
12913 }
12914 if !stack.is_empty() {
12915 if above {
12916 stack.reverse();
12917 }
12918 state.groups.push(AddSelectionsGroup { above, stack });
12919 }
12920 }
12921
12922 let mut final_selections = Vec::new();
12923 let end_row = if above {
12924 DisplayRow(0)
12925 } else {
12926 display_map.max_point().row()
12927 };
12928
12929 let mut last_added_item_per_group = HashMap::default();
12930 for group in state.groups.iter_mut() {
12931 if let Some(last_id) = group.stack.last() {
12932 last_added_item_per_group.insert(*last_id, group);
12933 }
12934 }
12935
12936 for selection in columnar_selections {
12937 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12938 if above == group.above {
12939 let range = selection.display_range(&display_map).sorted();
12940 debug_assert_eq!(range.start.row(), range.end.row());
12941 let mut row = range.start.row();
12942 let positions =
12943 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12944 px(start)..px(end)
12945 } else {
12946 let start_x =
12947 display_map.x_for_display_point(range.start, &text_layout_details);
12948 let end_x =
12949 display_map.x_for_display_point(range.end, &text_layout_details);
12950 start_x.min(end_x)..start_x.max(end_x)
12951 };
12952
12953 let mut maybe_new_selection = None;
12954 while row != end_row {
12955 if above {
12956 row.0 -= 1;
12957 } else {
12958 row.0 += 1;
12959 }
12960 if let Some(new_selection) = self.selections.build_columnar_selection(
12961 &display_map,
12962 row,
12963 &positions,
12964 selection.reversed,
12965 &text_layout_details,
12966 ) {
12967 maybe_new_selection = Some(new_selection);
12968 break;
12969 }
12970 }
12971
12972 if let Some(new_selection) = maybe_new_selection {
12973 group.stack.push(new_selection.id);
12974 if above {
12975 final_selections.push(new_selection);
12976 final_selections.push(selection);
12977 } else {
12978 final_selections.push(selection);
12979 final_selections.push(new_selection);
12980 }
12981 } else {
12982 final_selections.push(selection);
12983 }
12984 } else {
12985 group.stack.pop();
12986 }
12987 } else {
12988 final_selections.push(selection);
12989 }
12990 }
12991
12992 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12993 s.select(final_selections);
12994 });
12995
12996 let final_selection_ids: HashSet<_> = self
12997 .selections
12998 .all::<Point>(cx)
12999 .iter()
13000 .map(|s| s.id)
13001 .collect();
13002 state.groups.retain_mut(|group| {
13003 // selections might get merged above so we remove invalid items from stacks
13004 group.stack.retain(|id| final_selection_ids.contains(id));
13005
13006 // single selection in stack can be treated as initial state
13007 group.stack.len() > 1
13008 });
13009
13010 if !state.groups.is_empty() {
13011 self.add_selections_state = Some(state);
13012 }
13013 }
13014
13015 fn select_match_ranges(
13016 &mut self,
13017 range: Range<usize>,
13018 reversed: bool,
13019 replace_newest: bool,
13020 auto_scroll: Option<Autoscroll>,
13021 window: &mut Window,
13022 cx: &mut Context<Editor>,
13023 ) {
13024 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13025 self.change_selections(auto_scroll, window, cx, |s| {
13026 if replace_newest {
13027 s.delete(s.newest_anchor().id);
13028 }
13029 if reversed {
13030 s.insert_range(range.end..range.start);
13031 } else {
13032 s.insert_range(range);
13033 }
13034 });
13035 }
13036
13037 pub fn select_next_match_internal(
13038 &mut self,
13039 display_map: &DisplaySnapshot,
13040 replace_newest: bool,
13041 autoscroll: Option<Autoscroll>,
13042 window: &mut Window,
13043 cx: &mut Context<Self>,
13044 ) -> Result<()> {
13045 let buffer = &display_map.buffer_snapshot;
13046 let mut selections = self.selections.all::<usize>(cx);
13047 if let Some(mut select_next_state) = self.select_next_state.take() {
13048 let query = &select_next_state.query;
13049 if !select_next_state.done {
13050 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13051 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13052 let mut next_selected_range = None;
13053
13054 let bytes_after_last_selection =
13055 buffer.bytes_in_range(last_selection.end..buffer.len());
13056 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13057 let query_matches = query
13058 .stream_find_iter(bytes_after_last_selection)
13059 .map(|result| (last_selection.end, result))
13060 .chain(
13061 query
13062 .stream_find_iter(bytes_before_first_selection)
13063 .map(|result| (0, result)),
13064 );
13065
13066 for (start_offset, query_match) in query_matches {
13067 let query_match = query_match.unwrap(); // can only fail due to I/O
13068 let offset_range =
13069 start_offset + query_match.start()..start_offset + query_match.end();
13070 let display_range = offset_range.start.to_display_point(display_map)
13071 ..offset_range.end.to_display_point(display_map);
13072
13073 if !select_next_state.wordwise
13074 || (!movement::is_inside_word(display_map, display_range.start)
13075 && !movement::is_inside_word(display_map, display_range.end))
13076 {
13077 // TODO: This is n^2, because we might check all the selections
13078 if !selections
13079 .iter()
13080 .any(|selection| selection.range().overlaps(&offset_range))
13081 {
13082 next_selected_range = Some(offset_range);
13083 break;
13084 }
13085 }
13086 }
13087
13088 if let Some(next_selected_range) = next_selected_range {
13089 self.select_match_ranges(
13090 next_selected_range,
13091 last_selection.reversed,
13092 replace_newest,
13093 autoscroll,
13094 window,
13095 cx,
13096 );
13097 } else {
13098 select_next_state.done = true;
13099 }
13100 }
13101
13102 self.select_next_state = Some(select_next_state);
13103 } else {
13104 let mut only_carets = true;
13105 let mut same_text_selected = true;
13106 let mut selected_text = None;
13107
13108 let mut selections_iter = selections.iter().peekable();
13109 while let Some(selection) = selections_iter.next() {
13110 if selection.start != selection.end {
13111 only_carets = false;
13112 }
13113
13114 if same_text_selected {
13115 if selected_text.is_none() {
13116 selected_text =
13117 Some(buffer.text_for_range(selection.range()).collect::<String>());
13118 }
13119
13120 if let Some(next_selection) = selections_iter.peek() {
13121 if next_selection.range().len() == selection.range().len() {
13122 let next_selected_text = buffer
13123 .text_for_range(next_selection.range())
13124 .collect::<String>();
13125 if Some(next_selected_text) != selected_text {
13126 same_text_selected = false;
13127 selected_text = None;
13128 }
13129 } else {
13130 same_text_selected = false;
13131 selected_text = None;
13132 }
13133 }
13134 }
13135 }
13136
13137 if only_carets {
13138 for selection in &mut selections {
13139 let word_range = movement::surrounding_word(
13140 display_map,
13141 selection.start.to_display_point(display_map),
13142 );
13143 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13144 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13145 selection.goal = SelectionGoal::None;
13146 selection.reversed = false;
13147 self.select_match_ranges(
13148 selection.start..selection.end,
13149 selection.reversed,
13150 replace_newest,
13151 autoscroll,
13152 window,
13153 cx,
13154 );
13155 }
13156
13157 if selections.len() == 1 {
13158 let selection = selections
13159 .last()
13160 .expect("ensured that there's only one selection");
13161 let query = buffer
13162 .text_for_range(selection.start..selection.end)
13163 .collect::<String>();
13164 let is_empty = query.is_empty();
13165 let select_state = SelectNextState {
13166 query: AhoCorasick::new(&[query])?,
13167 wordwise: true,
13168 done: is_empty,
13169 };
13170 self.select_next_state = Some(select_state);
13171 } else {
13172 self.select_next_state = None;
13173 }
13174 } else if let Some(selected_text) = selected_text {
13175 self.select_next_state = Some(SelectNextState {
13176 query: AhoCorasick::new(&[selected_text])?,
13177 wordwise: false,
13178 done: false,
13179 });
13180 self.select_next_match_internal(
13181 display_map,
13182 replace_newest,
13183 autoscroll,
13184 window,
13185 cx,
13186 )?;
13187 }
13188 }
13189 Ok(())
13190 }
13191
13192 pub fn select_all_matches(
13193 &mut self,
13194 _action: &SelectAllMatches,
13195 window: &mut Window,
13196 cx: &mut Context<Self>,
13197 ) -> Result<()> {
13198 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13199
13200 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13201
13202 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13203 let Some(select_next_state) = self.select_next_state.as_mut() else {
13204 return Ok(());
13205 };
13206 if select_next_state.done {
13207 return Ok(());
13208 }
13209
13210 let mut new_selections = Vec::new();
13211
13212 let reversed = self.selections.oldest::<usize>(cx).reversed;
13213 let buffer = &display_map.buffer_snapshot;
13214 let query_matches = select_next_state
13215 .query
13216 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13217
13218 for query_match in query_matches.into_iter() {
13219 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13220 let offset_range = if reversed {
13221 query_match.end()..query_match.start()
13222 } else {
13223 query_match.start()..query_match.end()
13224 };
13225 let display_range = offset_range.start.to_display_point(&display_map)
13226 ..offset_range.end.to_display_point(&display_map);
13227
13228 if !select_next_state.wordwise
13229 || (!movement::is_inside_word(&display_map, display_range.start)
13230 && !movement::is_inside_word(&display_map, display_range.end))
13231 {
13232 new_selections.push(offset_range.start..offset_range.end);
13233 }
13234 }
13235
13236 select_next_state.done = true;
13237 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13238 self.change_selections(None, window, cx, |selections| {
13239 selections.select_ranges(new_selections)
13240 });
13241
13242 Ok(())
13243 }
13244
13245 pub fn select_next(
13246 &mut self,
13247 action: &SelectNext,
13248 window: &mut Window,
13249 cx: &mut Context<Self>,
13250 ) -> Result<()> {
13251 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13252 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13253 self.select_next_match_internal(
13254 &display_map,
13255 action.replace_newest,
13256 Some(Autoscroll::newest()),
13257 window,
13258 cx,
13259 )?;
13260 Ok(())
13261 }
13262
13263 pub fn select_previous(
13264 &mut self,
13265 action: &SelectPrevious,
13266 window: &mut Window,
13267 cx: &mut Context<Self>,
13268 ) -> Result<()> {
13269 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13271 let buffer = &display_map.buffer_snapshot;
13272 let mut selections = self.selections.all::<usize>(cx);
13273 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13274 let query = &select_prev_state.query;
13275 if !select_prev_state.done {
13276 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13277 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13278 let mut next_selected_range = None;
13279 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13280 let bytes_before_last_selection =
13281 buffer.reversed_bytes_in_range(0..last_selection.start);
13282 let bytes_after_first_selection =
13283 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13284 let query_matches = query
13285 .stream_find_iter(bytes_before_last_selection)
13286 .map(|result| (last_selection.start, result))
13287 .chain(
13288 query
13289 .stream_find_iter(bytes_after_first_selection)
13290 .map(|result| (buffer.len(), result)),
13291 );
13292 for (end_offset, query_match) in query_matches {
13293 let query_match = query_match.unwrap(); // can only fail due to I/O
13294 let offset_range =
13295 end_offset - query_match.end()..end_offset - query_match.start();
13296 let display_range = offset_range.start.to_display_point(&display_map)
13297 ..offset_range.end.to_display_point(&display_map);
13298
13299 if !select_prev_state.wordwise
13300 || (!movement::is_inside_word(&display_map, display_range.start)
13301 && !movement::is_inside_word(&display_map, display_range.end))
13302 {
13303 next_selected_range = Some(offset_range);
13304 break;
13305 }
13306 }
13307
13308 if let Some(next_selected_range) = next_selected_range {
13309 self.select_match_ranges(
13310 next_selected_range,
13311 last_selection.reversed,
13312 action.replace_newest,
13313 Some(Autoscroll::newest()),
13314 window,
13315 cx,
13316 );
13317 } else {
13318 select_prev_state.done = true;
13319 }
13320 }
13321
13322 self.select_prev_state = Some(select_prev_state);
13323 } else {
13324 let mut only_carets = true;
13325 let mut same_text_selected = true;
13326 let mut selected_text = None;
13327
13328 let mut selections_iter = selections.iter().peekable();
13329 while let Some(selection) = selections_iter.next() {
13330 if selection.start != selection.end {
13331 only_carets = false;
13332 }
13333
13334 if same_text_selected {
13335 if selected_text.is_none() {
13336 selected_text =
13337 Some(buffer.text_for_range(selection.range()).collect::<String>());
13338 }
13339
13340 if let Some(next_selection) = selections_iter.peek() {
13341 if next_selection.range().len() == selection.range().len() {
13342 let next_selected_text = buffer
13343 .text_for_range(next_selection.range())
13344 .collect::<String>();
13345 if Some(next_selected_text) != selected_text {
13346 same_text_selected = false;
13347 selected_text = None;
13348 }
13349 } else {
13350 same_text_selected = false;
13351 selected_text = None;
13352 }
13353 }
13354 }
13355 }
13356
13357 if only_carets {
13358 for selection in &mut selections {
13359 let word_range = movement::surrounding_word(
13360 &display_map,
13361 selection.start.to_display_point(&display_map),
13362 );
13363 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13364 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13365 selection.goal = SelectionGoal::None;
13366 selection.reversed = false;
13367 self.select_match_ranges(
13368 selection.start..selection.end,
13369 selection.reversed,
13370 action.replace_newest,
13371 Some(Autoscroll::newest()),
13372 window,
13373 cx,
13374 );
13375 }
13376 if selections.len() == 1 {
13377 let selection = selections
13378 .last()
13379 .expect("ensured that there's only one selection");
13380 let query = buffer
13381 .text_for_range(selection.start..selection.end)
13382 .collect::<String>();
13383 let is_empty = query.is_empty();
13384 let select_state = SelectNextState {
13385 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13386 wordwise: true,
13387 done: is_empty,
13388 };
13389 self.select_prev_state = Some(select_state);
13390 } else {
13391 self.select_prev_state = None;
13392 }
13393 } else if let Some(selected_text) = selected_text {
13394 self.select_prev_state = Some(SelectNextState {
13395 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13396 wordwise: false,
13397 done: false,
13398 });
13399 self.select_previous(action, window, cx)?;
13400 }
13401 }
13402 Ok(())
13403 }
13404
13405 pub fn find_next_match(
13406 &mut self,
13407 _: &FindNextMatch,
13408 window: &mut Window,
13409 cx: &mut Context<Self>,
13410 ) -> Result<()> {
13411 let selections = self.selections.disjoint_anchors();
13412 match selections.first() {
13413 Some(first) if selections.len() >= 2 => {
13414 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13415 s.select_ranges([first.range()]);
13416 });
13417 }
13418 _ => self.select_next(
13419 &SelectNext {
13420 replace_newest: true,
13421 },
13422 window,
13423 cx,
13424 )?,
13425 }
13426 Ok(())
13427 }
13428
13429 pub fn find_previous_match(
13430 &mut self,
13431 _: &FindPreviousMatch,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) -> Result<()> {
13435 let selections = self.selections.disjoint_anchors();
13436 match selections.last() {
13437 Some(last) if selections.len() >= 2 => {
13438 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13439 s.select_ranges([last.range()]);
13440 });
13441 }
13442 _ => self.select_previous(
13443 &SelectPrevious {
13444 replace_newest: true,
13445 },
13446 window,
13447 cx,
13448 )?,
13449 }
13450 Ok(())
13451 }
13452
13453 pub fn toggle_comments(
13454 &mut self,
13455 action: &ToggleComments,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 if self.read_only(cx) {
13460 return;
13461 }
13462 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13463 let text_layout_details = &self.text_layout_details(window);
13464 self.transact(window, cx, |this, window, cx| {
13465 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13466 let mut edits = Vec::new();
13467 let mut selection_edit_ranges = Vec::new();
13468 let mut last_toggled_row = None;
13469 let snapshot = this.buffer.read(cx).read(cx);
13470 let empty_str: Arc<str> = Arc::default();
13471 let mut suffixes_inserted = Vec::new();
13472 let ignore_indent = action.ignore_indent;
13473
13474 fn comment_prefix_range(
13475 snapshot: &MultiBufferSnapshot,
13476 row: MultiBufferRow,
13477 comment_prefix: &str,
13478 comment_prefix_whitespace: &str,
13479 ignore_indent: bool,
13480 ) -> Range<Point> {
13481 let indent_size = if ignore_indent {
13482 0
13483 } else {
13484 snapshot.indent_size_for_line(row).len
13485 };
13486
13487 let start = Point::new(row.0, indent_size);
13488
13489 let mut line_bytes = snapshot
13490 .bytes_in_range(start..snapshot.max_point())
13491 .flatten()
13492 .copied();
13493
13494 // If this line currently begins with the line comment prefix, then record
13495 // the range containing the prefix.
13496 if line_bytes
13497 .by_ref()
13498 .take(comment_prefix.len())
13499 .eq(comment_prefix.bytes())
13500 {
13501 // Include any whitespace that matches the comment prefix.
13502 let matching_whitespace_len = line_bytes
13503 .zip(comment_prefix_whitespace.bytes())
13504 .take_while(|(a, b)| a == b)
13505 .count() as u32;
13506 let end = Point::new(
13507 start.row,
13508 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13509 );
13510 start..end
13511 } else {
13512 start..start
13513 }
13514 }
13515
13516 fn comment_suffix_range(
13517 snapshot: &MultiBufferSnapshot,
13518 row: MultiBufferRow,
13519 comment_suffix: &str,
13520 comment_suffix_has_leading_space: bool,
13521 ) -> Range<Point> {
13522 let end = Point::new(row.0, snapshot.line_len(row));
13523 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13524
13525 let mut line_end_bytes = snapshot
13526 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13527 .flatten()
13528 .copied();
13529
13530 let leading_space_len = if suffix_start_column > 0
13531 && line_end_bytes.next() == Some(b' ')
13532 && comment_suffix_has_leading_space
13533 {
13534 1
13535 } else {
13536 0
13537 };
13538
13539 // If this line currently begins with the line comment prefix, then record
13540 // the range containing the prefix.
13541 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13542 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13543 start..end
13544 } else {
13545 end..end
13546 }
13547 }
13548
13549 // TODO: Handle selections that cross excerpts
13550 for selection in &mut selections {
13551 let start_column = snapshot
13552 .indent_size_for_line(MultiBufferRow(selection.start.row))
13553 .len;
13554 let language = if let Some(language) =
13555 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13556 {
13557 language
13558 } else {
13559 continue;
13560 };
13561
13562 selection_edit_ranges.clear();
13563
13564 // If multiple selections contain a given row, avoid processing that
13565 // row more than once.
13566 let mut start_row = MultiBufferRow(selection.start.row);
13567 if last_toggled_row == Some(start_row) {
13568 start_row = start_row.next_row();
13569 }
13570 let end_row =
13571 if selection.end.row > selection.start.row && selection.end.column == 0 {
13572 MultiBufferRow(selection.end.row - 1)
13573 } else {
13574 MultiBufferRow(selection.end.row)
13575 };
13576 last_toggled_row = Some(end_row);
13577
13578 if start_row > end_row {
13579 continue;
13580 }
13581
13582 // If the language has line comments, toggle those.
13583 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13584
13585 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13586 if ignore_indent {
13587 full_comment_prefixes = full_comment_prefixes
13588 .into_iter()
13589 .map(|s| Arc::from(s.trim_end()))
13590 .collect();
13591 }
13592
13593 if !full_comment_prefixes.is_empty() {
13594 let first_prefix = full_comment_prefixes
13595 .first()
13596 .expect("prefixes is non-empty");
13597 let prefix_trimmed_lengths = full_comment_prefixes
13598 .iter()
13599 .map(|p| p.trim_end_matches(' ').len())
13600 .collect::<SmallVec<[usize; 4]>>();
13601
13602 let mut all_selection_lines_are_comments = true;
13603
13604 for row in start_row.0..=end_row.0 {
13605 let row = MultiBufferRow(row);
13606 if start_row < end_row && snapshot.is_line_blank(row) {
13607 continue;
13608 }
13609
13610 let prefix_range = full_comment_prefixes
13611 .iter()
13612 .zip(prefix_trimmed_lengths.iter().copied())
13613 .map(|(prefix, trimmed_prefix_len)| {
13614 comment_prefix_range(
13615 snapshot.deref(),
13616 row,
13617 &prefix[..trimmed_prefix_len],
13618 &prefix[trimmed_prefix_len..],
13619 ignore_indent,
13620 )
13621 })
13622 .max_by_key(|range| range.end.column - range.start.column)
13623 .expect("prefixes is non-empty");
13624
13625 if prefix_range.is_empty() {
13626 all_selection_lines_are_comments = false;
13627 }
13628
13629 selection_edit_ranges.push(prefix_range);
13630 }
13631
13632 if all_selection_lines_are_comments {
13633 edits.extend(
13634 selection_edit_ranges
13635 .iter()
13636 .cloned()
13637 .map(|range| (range, empty_str.clone())),
13638 );
13639 } else {
13640 let min_column = selection_edit_ranges
13641 .iter()
13642 .map(|range| range.start.column)
13643 .min()
13644 .unwrap_or(0);
13645 edits.extend(selection_edit_ranges.iter().map(|range| {
13646 let position = Point::new(range.start.row, min_column);
13647 (position..position, first_prefix.clone())
13648 }));
13649 }
13650 } else if let Some((full_comment_prefix, comment_suffix)) =
13651 language.block_comment_delimiters()
13652 {
13653 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13654 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13655 let prefix_range = comment_prefix_range(
13656 snapshot.deref(),
13657 start_row,
13658 comment_prefix,
13659 comment_prefix_whitespace,
13660 ignore_indent,
13661 );
13662 let suffix_range = comment_suffix_range(
13663 snapshot.deref(),
13664 end_row,
13665 comment_suffix.trim_start_matches(' '),
13666 comment_suffix.starts_with(' '),
13667 );
13668
13669 if prefix_range.is_empty() || suffix_range.is_empty() {
13670 edits.push((
13671 prefix_range.start..prefix_range.start,
13672 full_comment_prefix.clone(),
13673 ));
13674 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13675 suffixes_inserted.push((end_row, comment_suffix.len()));
13676 } else {
13677 edits.push((prefix_range, empty_str.clone()));
13678 edits.push((suffix_range, empty_str.clone()));
13679 }
13680 } else {
13681 continue;
13682 }
13683 }
13684
13685 drop(snapshot);
13686 this.buffer.update(cx, |buffer, cx| {
13687 buffer.edit(edits, None, cx);
13688 });
13689
13690 // Adjust selections so that they end before any comment suffixes that
13691 // were inserted.
13692 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13693 let mut selections = this.selections.all::<Point>(cx);
13694 let snapshot = this.buffer.read(cx).read(cx);
13695 for selection in &mut selections {
13696 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13697 match row.cmp(&MultiBufferRow(selection.end.row)) {
13698 Ordering::Less => {
13699 suffixes_inserted.next();
13700 continue;
13701 }
13702 Ordering::Greater => break,
13703 Ordering::Equal => {
13704 if selection.end.column == snapshot.line_len(row) {
13705 if selection.is_empty() {
13706 selection.start.column -= suffix_len as u32;
13707 }
13708 selection.end.column -= suffix_len as u32;
13709 }
13710 break;
13711 }
13712 }
13713 }
13714 }
13715
13716 drop(snapshot);
13717 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13718 s.select(selections)
13719 });
13720
13721 let selections = this.selections.all::<Point>(cx);
13722 let selections_on_single_row = selections.windows(2).all(|selections| {
13723 selections[0].start.row == selections[1].start.row
13724 && selections[0].end.row == selections[1].end.row
13725 && selections[0].start.row == selections[0].end.row
13726 });
13727 let selections_selecting = selections
13728 .iter()
13729 .any(|selection| selection.start != selection.end);
13730 let advance_downwards = action.advance_downwards
13731 && selections_on_single_row
13732 && !selections_selecting
13733 && !matches!(this.mode, EditorMode::SingleLine { .. });
13734
13735 if advance_downwards {
13736 let snapshot = this.buffer.read(cx).snapshot(cx);
13737
13738 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13739 s.move_cursors_with(|display_snapshot, display_point, _| {
13740 let mut point = display_point.to_point(display_snapshot);
13741 point.row += 1;
13742 point = snapshot.clip_point(point, Bias::Left);
13743 let display_point = point.to_display_point(display_snapshot);
13744 let goal = SelectionGoal::HorizontalPosition(
13745 display_snapshot
13746 .x_for_display_point(display_point, text_layout_details)
13747 .into(),
13748 );
13749 (display_point, goal)
13750 })
13751 });
13752 }
13753 });
13754 }
13755
13756 pub fn select_enclosing_symbol(
13757 &mut self,
13758 _: &SelectEnclosingSymbol,
13759 window: &mut Window,
13760 cx: &mut Context<Self>,
13761 ) {
13762 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13763
13764 let buffer = self.buffer.read(cx).snapshot(cx);
13765 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13766
13767 fn update_selection(
13768 selection: &Selection<usize>,
13769 buffer_snap: &MultiBufferSnapshot,
13770 ) -> Option<Selection<usize>> {
13771 let cursor = selection.head();
13772 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13773 for symbol in symbols.iter().rev() {
13774 let start = symbol.range.start.to_offset(buffer_snap);
13775 let end = symbol.range.end.to_offset(buffer_snap);
13776 let new_range = start..end;
13777 if start < selection.start || end > selection.end {
13778 return Some(Selection {
13779 id: selection.id,
13780 start: new_range.start,
13781 end: new_range.end,
13782 goal: SelectionGoal::None,
13783 reversed: selection.reversed,
13784 });
13785 }
13786 }
13787 None
13788 }
13789
13790 let mut selected_larger_symbol = false;
13791 let new_selections = old_selections
13792 .iter()
13793 .map(|selection| match update_selection(selection, &buffer) {
13794 Some(new_selection) => {
13795 if new_selection.range() != selection.range() {
13796 selected_larger_symbol = true;
13797 }
13798 new_selection
13799 }
13800 None => selection.clone(),
13801 })
13802 .collect::<Vec<_>>();
13803
13804 if selected_larger_symbol {
13805 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13806 s.select(new_selections);
13807 });
13808 }
13809 }
13810
13811 pub fn select_larger_syntax_node(
13812 &mut self,
13813 _: &SelectLargerSyntaxNode,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 let Some(visible_row_count) = self.visible_row_count() else {
13818 return;
13819 };
13820 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13821 if old_selections.is_empty() {
13822 return;
13823 }
13824
13825 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13826
13827 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13828 let buffer = self.buffer.read(cx).snapshot(cx);
13829
13830 let mut selected_larger_node = false;
13831 let mut new_selections = old_selections
13832 .iter()
13833 .map(|selection| {
13834 let old_range = selection.start..selection.end;
13835
13836 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13837 // manually select word at selection
13838 if ["string_content", "inline"].contains(&node.kind()) {
13839 let word_range = {
13840 let display_point = buffer
13841 .offset_to_point(old_range.start)
13842 .to_display_point(&display_map);
13843 let Range { start, end } =
13844 movement::surrounding_word(&display_map, display_point);
13845 start.to_point(&display_map).to_offset(&buffer)
13846 ..end.to_point(&display_map).to_offset(&buffer)
13847 };
13848 // ignore if word is already selected
13849 if !word_range.is_empty() && old_range != word_range {
13850 let last_word_range = {
13851 let display_point = buffer
13852 .offset_to_point(old_range.end)
13853 .to_display_point(&display_map);
13854 let Range { start, end } =
13855 movement::surrounding_word(&display_map, display_point);
13856 start.to_point(&display_map).to_offset(&buffer)
13857 ..end.to_point(&display_map).to_offset(&buffer)
13858 };
13859 // only select word if start and end point belongs to same word
13860 if word_range == last_word_range {
13861 selected_larger_node = true;
13862 return Selection {
13863 id: selection.id,
13864 start: word_range.start,
13865 end: word_range.end,
13866 goal: SelectionGoal::None,
13867 reversed: selection.reversed,
13868 };
13869 }
13870 }
13871 }
13872 }
13873
13874 let mut new_range = old_range.clone();
13875 while let Some((_node, containing_range)) =
13876 buffer.syntax_ancestor(new_range.clone())
13877 {
13878 new_range = match containing_range {
13879 MultiOrSingleBufferOffsetRange::Single(_) => break,
13880 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13881 };
13882 if !display_map.intersects_fold(new_range.start)
13883 && !display_map.intersects_fold(new_range.end)
13884 {
13885 break;
13886 }
13887 }
13888
13889 selected_larger_node |= new_range != old_range;
13890 Selection {
13891 id: selection.id,
13892 start: new_range.start,
13893 end: new_range.end,
13894 goal: SelectionGoal::None,
13895 reversed: selection.reversed,
13896 }
13897 })
13898 .collect::<Vec<_>>();
13899
13900 if !selected_larger_node {
13901 return; // don't put this call in the history
13902 }
13903
13904 // scroll based on transformation done to the last selection created by the user
13905 let (last_old, last_new) = old_selections
13906 .last()
13907 .zip(new_selections.last().cloned())
13908 .expect("old_selections isn't empty");
13909
13910 // revert selection
13911 let is_selection_reversed = {
13912 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13913 new_selections.last_mut().expect("checked above").reversed =
13914 should_newest_selection_be_reversed;
13915 should_newest_selection_be_reversed
13916 };
13917
13918 if selected_larger_node {
13919 self.select_syntax_node_history.disable_clearing = true;
13920 self.change_selections(None, window, cx, |s| {
13921 s.select(new_selections.clone());
13922 });
13923 self.select_syntax_node_history.disable_clearing = false;
13924 }
13925
13926 let start_row = last_new.start.to_display_point(&display_map).row().0;
13927 let end_row = last_new.end.to_display_point(&display_map).row().0;
13928 let selection_height = end_row - start_row + 1;
13929 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13930
13931 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13932 let scroll_behavior = if fits_on_the_screen {
13933 self.request_autoscroll(Autoscroll::fit(), cx);
13934 SelectSyntaxNodeScrollBehavior::FitSelection
13935 } else if is_selection_reversed {
13936 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13937 SelectSyntaxNodeScrollBehavior::CursorTop
13938 } else {
13939 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13940 SelectSyntaxNodeScrollBehavior::CursorBottom
13941 };
13942
13943 self.select_syntax_node_history.push((
13944 old_selections,
13945 scroll_behavior,
13946 is_selection_reversed,
13947 ));
13948 }
13949
13950 pub fn select_smaller_syntax_node(
13951 &mut self,
13952 _: &SelectSmallerSyntaxNode,
13953 window: &mut Window,
13954 cx: &mut Context<Self>,
13955 ) {
13956 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13957
13958 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13959 self.select_syntax_node_history.pop()
13960 {
13961 if let Some(selection) = selections.last_mut() {
13962 selection.reversed = is_selection_reversed;
13963 }
13964
13965 self.select_syntax_node_history.disable_clearing = true;
13966 self.change_selections(None, window, cx, |s| {
13967 s.select(selections.to_vec());
13968 });
13969 self.select_syntax_node_history.disable_clearing = false;
13970
13971 match scroll_behavior {
13972 SelectSyntaxNodeScrollBehavior::CursorTop => {
13973 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13974 }
13975 SelectSyntaxNodeScrollBehavior::FitSelection => {
13976 self.request_autoscroll(Autoscroll::fit(), cx);
13977 }
13978 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13979 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13980 }
13981 }
13982 }
13983 }
13984
13985 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13986 if !EditorSettings::get_global(cx).gutter.runnables {
13987 self.clear_tasks();
13988 return Task::ready(());
13989 }
13990 let project = self.project.as_ref().map(Entity::downgrade);
13991 let task_sources = self.lsp_task_sources(cx);
13992 let multi_buffer = self.buffer.downgrade();
13993 cx.spawn_in(window, async move |editor, cx| {
13994 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13995 let Some(project) = project.and_then(|p| p.upgrade()) else {
13996 return;
13997 };
13998 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13999 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14000 }) else {
14001 return;
14002 };
14003
14004 let hide_runnables = project
14005 .update(cx, |project, cx| {
14006 // Do not display any test indicators in non-dev server remote projects.
14007 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14008 })
14009 .unwrap_or(true);
14010 if hide_runnables {
14011 return;
14012 }
14013 let new_rows =
14014 cx.background_spawn({
14015 let snapshot = display_snapshot.clone();
14016 async move {
14017 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14018 }
14019 })
14020 .await;
14021 let Ok(lsp_tasks) =
14022 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14023 else {
14024 return;
14025 };
14026 let lsp_tasks = lsp_tasks.await;
14027
14028 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14029 lsp_tasks
14030 .into_iter()
14031 .flat_map(|(kind, tasks)| {
14032 tasks.into_iter().filter_map(move |(location, task)| {
14033 Some((kind.clone(), location?, task))
14034 })
14035 })
14036 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14037 let buffer = location.target.buffer;
14038 let buffer_snapshot = buffer.read(cx).snapshot();
14039 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14040 |(excerpt_id, snapshot, _)| {
14041 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14042 display_snapshot
14043 .buffer_snapshot
14044 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14045 } else {
14046 None
14047 }
14048 },
14049 );
14050 if let Some(offset) = offset {
14051 let task_buffer_range =
14052 location.target.range.to_point(&buffer_snapshot);
14053 let context_buffer_range =
14054 task_buffer_range.to_offset(&buffer_snapshot);
14055 let context_range = BufferOffset(context_buffer_range.start)
14056 ..BufferOffset(context_buffer_range.end);
14057
14058 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14059 .or_insert_with(|| RunnableTasks {
14060 templates: Vec::new(),
14061 offset,
14062 column: task_buffer_range.start.column,
14063 extra_variables: HashMap::default(),
14064 context_range,
14065 })
14066 .templates
14067 .push((kind, task.original_task().clone()));
14068 }
14069
14070 acc
14071 })
14072 }) else {
14073 return;
14074 };
14075
14076 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14077 buffer.language_settings(cx).tasks.prefer_lsp
14078 }) else {
14079 return;
14080 };
14081
14082 let rows = Self::runnable_rows(
14083 project,
14084 display_snapshot,
14085 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14086 new_rows,
14087 cx.clone(),
14088 )
14089 .await;
14090 editor
14091 .update(cx, |editor, _| {
14092 editor.clear_tasks();
14093 for (key, mut value) in rows {
14094 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14095 value.templates.extend(lsp_tasks.templates);
14096 }
14097
14098 editor.insert_tasks(key, value);
14099 }
14100 for (key, value) in lsp_tasks_by_rows {
14101 editor.insert_tasks(key, value);
14102 }
14103 })
14104 .ok();
14105 })
14106 }
14107 fn fetch_runnable_ranges(
14108 snapshot: &DisplaySnapshot,
14109 range: Range<Anchor>,
14110 ) -> Vec<language::RunnableRange> {
14111 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14112 }
14113
14114 fn runnable_rows(
14115 project: Entity<Project>,
14116 snapshot: DisplaySnapshot,
14117 prefer_lsp: bool,
14118 runnable_ranges: Vec<RunnableRange>,
14119 cx: AsyncWindowContext,
14120 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14121 cx.spawn(async move |cx| {
14122 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14123 for mut runnable in runnable_ranges {
14124 let Some(tasks) = cx
14125 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14126 .ok()
14127 else {
14128 continue;
14129 };
14130 let mut tasks = tasks.await;
14131
14132 if prefer_lsp {
14133 tasks.retain(|(task_kind, _)| {
14134 !matches!(task_kind, TaskSourceKind::Language { .. })
14135 });
14136 }
14137 if tasks.is_empty() {
14138 continue;
14139 }
14140
14141 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14142 let Some(row) = snapshot
14143 .buffer_snapshot
14144 .buffer_line_for_row(MultiBufferRow(point.row))
14145 .map(|(_, range)| range.start.row)
14146 else {
14147 continue;
14148 };
14149
14150 let context_range =
14151 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14152 runnable_rows.push((
14153 (runnable.buffer_id, row),
14154 RunnableTasks {
14155 templates: tasks,
14156 offset: snapshot
14157 .buffer_snapshot
14158 .anchor_before(runnable.run_range.start),
14159 context_range,
14160 column: point.column,
14161 extra_variables: runnable.extra_captures,
14162 },
14163 ));
14164 }
14165 runnable_rows
14166 })
14167 }
14168
14169 fn templates_with_tags(
14170 project: &Entity<Project>,
14171 runnable: &mut Runnable,
14172 cx: &mut App,
14173 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14174 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14175 let (worktree_id, file) = project
14176 .buffer_for_id(runnable.buffer, cx)
14177 .and_then(|buffer| buffer.read(cx).file())
14178 .map(|file| (file.worktree_id(cx), file.clone()))
14179 .unzip();
14180
14181 (
14182 project.task_store().read(cx).task_inventory().cloned(),
14183 worktree_id,
14184 file,
14185 )
14186 });
14187
14188 let tags = mem::take(&mut runnable.tags);
14189 let language = runnable.language.clone();
14190 cx.spawn(async move |cx| {
14191 let mut templates_with_tags = Vec::new();
14192 if let Some(inventory) = inventory {
14193 for RunnableTag(tag) in tags {
14194 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14195 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14196 }) else {
14197 return templates_with_tags;
14198 };
14199 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14200 move |(_, template)| {
14201 template.tags.iter().any(|source_tag| source_tag == &tag)
14202 },
14203 ));
14204 }
14205 }
14206 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14207
14208 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14209 // Strongest source wins; if we have worktree tag binding, prefer that to
14210 // global and language bindings;
14211 // if we have a global binding, prefer that to language binding.
14212 let first_mismatch = templates_with_tags
14213 .iter()
14214 .position(|(tag_source, _)| tag_source != leading_tag_source);
14215 if let Some(index) = first_mismatch {
14216 templates_with_tags.truncate(index);
14217 }
14218 }
14219
14220 templates_with_tags
14221 })
14222 }
14223
14224 pub fn move_to_enclosing_bracket(
14225 &mut self,
14226 _: &MoveToEnclosingBracket,
14227 window: &mut Window,
14228 cx: &mut Context<Self>,
14229 ) {
14230 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14231 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14232 s.move_offsets_with(|snapshot, selection| {
14233 let Some(enclosing_bracket_ranges) =
14234 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14235 else {
14236 return;
14237 };
14238
14239 let mut best_length = usize::MAX;
14240 let mut best_inside = false;
14241 let mut best_in_bracket_range = false;
14242 let mut best_destination = None;
14243 for (open, close) in enclosing_bracket_ranges {
14244 let close = close.to_inclusive();
14245 let length = close.end() - open.start;
14246 let inside = selection.start >= open.end && selection.end <= *close.start();
14247 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14248 || close.contains(&selection.head());
14249
14250 // If best is next to a bracket and current isn't, skip
14251 if !in_bracket_range && best_in_bracket_range {
14252 continue;
14253 }
14254
14255 // Prefer smaller lengths unless best is inside and current isn't
14256 if length > best_length && (best_inside || !inside) {
14257 continue;
14258 }
14259
14260 best_length = length;
14261 best_inside = inside;
14262 best_in_bracket_range = in_bracket_range;
14263 best_destination = Some(
14264 if close.contains(&selection.start) && close.contains(&selection.end) {
14265 if inside { open.end } else { open.start }
14266 } else if inside {
14267 *close.start()
14268 } else {
14269 *close.end()
14270 },
14271 );
14272 }
14273
14274 if let Some(destination) = best_destination {
14275 selection.collapse_to(destination, SelectionGoal::None);
14276 }
14277 })
14278 });
14279 }
14280
14281 pub fn undo_selection(
14282 &mut self,
14283 _: &UndoSelection,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) {
14287 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14288 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14289 self.selection_history.mode = SelectionHistoryMode::Undoing;
14290 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14291 this.end_selection(window, cx);
14292 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14293 s.select_anchors(entry.selections.to_vec())
14294 });
14295 });
14296 self.selection_history.mode = SelectionHistoryMode::Normal;
14297
14298 self.select_next_state = entry.select_next_state;
14299 self.select_prev_state = entry.select_prev_state;
14300 self.add_selections_state = entry.add_selections_state;
14301 }
14302 }
14303
14304 pub fn redo_selection(
14305 &mut self,
14306 _: &RedoSelection,
14307 window: &mut Window,
14308 cx: &mut Context<Self>,
14309 ) {
14310 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14311 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14312 self.selection_history.mode = SelectionHistoryMode::Redoing;
14313 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14314 this.end_selection(window, cx);
14315 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14316 s.select_anchors(entry.selections.to_vec())
14317 });
14318 });
14319 self.selection_history.mode = SelectionHistoryMode::Normal;
14320
14321 self.select_next_state = entry.select_next_state;
14322 self.select_prev_state = entry.select_prev_state;
14323 self.add_selections_state = entry.add_selections_state;
14324 }
14325 }
14326
14327 pub fn expand_excerpts(
14328 &mut self,
14329 action: &ExpandExcerpts,
14330 _: &mut Window,
14331 cx: &mut Context<Self>,
14332 ) {
14333 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14334 }
14335
14336 pub fn expand_excerpts_down(
14337 &mut self,
14338 action: &ExpandExcerptsDown,
14339 _: &mut Window,
14340 cx: &mut Context<Self>,
14341 ) {
14342 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14343 }
14344
14345 pub fn expand_excerpts_up(
14346 &mut self,
14347 action: &ExpandExcerptsUp,
14348 _: &mut Window,
14349 cx: &mut Context<Self>,
14350 ) {
14351 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14352 }
14353
14354 pub fn expand_excerpts_for_direction(
14355 &mut self,
14356 lines: u32,
14357 direction: ExpandExcerptDirection,
14358
14359 cx: &mut Context<Self>,
14360 ) {
14361 let selections = self.selections.disjoint_anchors();
14362
14363 let lines = if lines == 0 {
14364 EditorSettings::get_global(cx).expand_excerpt_lines
14365 } else {
14366 lines
14367 };
14368
14369 self.buffer.update(cx, |buffer, cx| {
14370 let snapshot = buffer.snapshot(cx);
14371 let mut excerpt_ids = selections
14372 .iter()
14373 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14374 .collect::<Vec<_>>();
14375 excerpt_ids.sort();
14376 excerpt_ids.dedup();
14377 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14378 })
14379 }
14380
14381 pub fn expand_excerpt(
14382 &mut self,
14383 excerpt: ExcerptId,
14384 direction: ExpandExcerptDirection,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) {
14388 let current_scroll_position = self.scroll_position(cx);
14389 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14390 let mut should_scroll_up = false;
14391
14392 if direction == ExpandExcerptDirection::Down {
14393 let multi_buffer = self.buffer.read(cx);
14394 let snapshot = multi_buffer.snapshot(cx);
14395 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14396 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14397 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14398 let buffer_snapshot = buffer.read(cx).snapshot();
14399 let excerpt_end_row =
14400 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14401 let last_row = buffer_snapshot.max_point().row;
14402 let lines_below = last_row.saturating_sub(excerpt_end_row);
14403 should_scroll_up = lines_below >= lines_to_expand;
14404 }
14405 }
14406 }
14407 }
14408
14409 self.buffer.update(cx, |buffer, cx| {
14410 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14411 });
14412
14413 if should_scroll_up {
14414 let new_scroll_position =
14415 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14416 self.set_scroll_position(new_scroll_position, window, cx);
14417 }
14418 }
14419
14420 pub fn go_to_singleton_buffer_point(
14421 &mut self,
14422 point: Point,
14423 window: &mut Window,
14424 cx: &mut Context<Self>,
14425 ) {
14426 self.go_to_singleton_buffer_range(point..point, window, cx);
14427 }
14428
14429 pub fn go_to_singleton_buffer_range(
14430 &mut self,
14431 range: Range<Point>,
14432 window: &mut Window,
14433 cx: &mut Context<Self>,
14434 ) {
14435 let multibuffer = self.buffer().read(cx);
14436 let Some(buffer) = multibuffer.as_singleton() else {
14437 return;
14438 };
14439 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14440 return;
14441 };
14442 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14443 return;
14444 };
14445 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14446 s.select_anchor_ranges([start..end])
14447 });
14448 }
14449
14450 pub fn go_to_diagnostic(
14451 &mut self,
14452 _: &GoToDiagnostic,
14453 window: &mut Window,
14454 cx: &mut Context<Self>,
14455 ) {
14456 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14457 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14458 }
14459
14460 pub fn go_to_prev_diagnostic(
14461 &mut self,
14462 _: &GoToPreviousDiagnostic,
14463 window: &mut Window,
14464 cx: &mut Context<Self>,
14465 ) {
14466 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14467 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14468 }
14469
14470 pub fn go_to_diagnostic_impl(
14471 &mut self,
14472 direction: Direction,
14473 window: &mut Window,
14474 cx: &mut Context<Self>,
14475 ) {
14476 let buffer = self.buffer.read(cx).snapshot(cx);
14477 let selection = self.selections.newest::<usize>(cx);
14478
14479 let mut active_group_id = None;
14480 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14481 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14482 active_group_id = Some(active_group.group_id);
14483 }
14484 }
14485
14486 fn filtered(
14487 snapshot: EditorSnapshot,
14488 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14489 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14490 diagnostics
14491 .filter(|entry| entry.range.start != entry.range.end)
14492 .filter(|entry| !entry.diagnostic.is_unnecessary)
14493 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14494 }
14495
14496 let snapshot = self.snapshot(window, cx);
14497 let before = filtered(
14498 snapshot.clone(),
14499 buffer
14500 .diagnostics_in_range(0..selection.start)
14501 .filter(|entry| entry.range.start <= selection.start),
14502 );
14503 let after = filtered(
14504 snapshot,
14505 buffer
14506 .diagnostics_in_range(selection.start..buffer.len())
14507 .filter(|entry| entry.range.start >= selection.start),
14508 );
14509
14510 let mut found: Option<DiagnosticEntry<usize>> = None;
14511 if direction == Direction::Prev {
14512 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14513 {
14514 for diagnostic in prev_diagnostics.into_iter().rev() {
14515 if diagnostic.range.start != selection.start
14516 || active_group_id
14517 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14518 {
14519 found = Some(diagnostic);
14520 break 'outer;
14521 }
14522 }
14523 }
14524 } else {
14525 for diagnostic in after.chain(before) {
14526 if diagnostic.range.start != selection.start
14527 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14528 {
14529 found = Some(diagnostic);
14530 break;
14531 }
14532 }
14533 }
14534 let Some(next_diagnostic) = found else {
14535 return;
14536 };
14537
14538 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14539 return;
14540 };
14541 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14542 s.select_ranges(vec![
14543 next_diagnostic.range.start..next_diagnostic.range.start,
14544 ])
14545 });
14546 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14547 self.refresh_inline_completion(false, true, window, cx);
14548 }
14549
14550 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14551 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14552 let snapshot = self.snapshot(window, cx);
14553 let selection = self.selections.newest::<Point>(cx);
14554 self.go_to_hunk_before_or_after_position(
14555 &snapshot,
14556 selection.head(),
14557 Direction::Next,
14558 window,
14559 cx,
14560 );
14561 }
14562
14563 pub fn go_to_hunk_before_or_after_position(
14564 &mut self,
14565 snapshot: &EditorSnapshot,
14566 position: Point,
14567 direction: Direction,
14568 window: &mut Window,
14569 cx: &mut Context<Editor>,
14570 ) {
14571 let row = if direction == Direction::Next {
14572 self.hunk_after_position(snapshot, position)
14573 .map(|hunk| hunk.row_range.start)
14574 } else {
14575 self.hunk_before_position(snapshot, position)
14576 };
14577
14578 if let Some(row) = row {
14579 let destination = Point::new(row.0, 0);
14580 let autoscroll = Autoscroll::center();
14581
14582 self.unfold_ranges(&[destination..destination], false, false, cx);
14583 self.change_selections(Some(autoscroll), window, cx, |s| {
14584 s.select_ranges([destination..destination]);
14585 });
14586 }
14587 }
14588
14589 fn hunk_after_position(
14590 &mut self,
14591 snapshot: &EditorSnapshot,
14592 position: Point,
14593 ) -> Option<MultiBufferDiffHunk> {
14594 snapshot
14595 .buffer_snapshot
14596 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14597 .find(|hunk| hunk.row_range.start.0 > position.row)
14598 .or_else(|| {
14599 snapshot
14600 .buffer_snapshot
14601 .diff_hunks_in_range(Point::zero()..position)
14602 .find(|hunk| hunk.row_range.end.0 < position.row)
14603 })
14604 }
14605
14606 fn go_to_prev_hunk(
14607 &mut self,
14608 _: &GoToPreviousHunk,
14609 window: &mut Window,
14610 cx: &mut Context<Self>,
14611 ) {
14612 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14613 let snapshot = self.snapshot(window, cx);
14614 let selection = self.selections.newest::<Point>(cx);
14615 self.go_to_hunk_before_or_after_position(
14616 &snapshot,
14617 selection.head(),
14618 Direction::Prev,
14619 window,
14620 cx,
14621 );
14622 }
14623
14624 fn hunk_before_position(
14625 &mut self,
14626 snapshot: &EditorSnapshot,
14627 position: Point,
14628 ) -> Option<MultiBufferRow> {
14629 snapshot
14630 .buffer_snapshot
14631 .diff_hunk_before(position)
14632 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14633 }
14634
14635 fn go_to_next_change(
14636 &mut self,
14637 _: &GoToNextChange,
14638 window: &mut Window,
14639 cx: &mut Context<Self>,
14640 ) {
14641 if let Some(selections) = self
14642 .change_list
14643 .next_change(1, Direction::Next)
14644 .map(|s| s.to_vec())
14645 {
14646 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14647 let map = s.display_map();
14648 s.select_display_ranges(selections.iter().map(|a| {
14649 let point = a.to_display_point(&map);
14650 point..point
14651 }))
14652 })
14653 }
14654 }
14655
14656 fn go_to_previous_change(
14657 &mut self,
14658 _: &GoToPreviousChange,
14659 window: &mut Window,
14660 cx: &mut Context<Self>,
14661 ) {
14662 if let Some(selections) = self
14663 .change_list
14664 .next_change(1, Direction::Prev)
14665 .map(|s| s.to_vec())
14666 {
14667 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14668 let map = s.display_map();
14669 s.select_display_ranges(selections.iter().map(|a| {
14670 let point = a.to_display_point(&map);
14671 point..point
14672 }))
14673 })
14674 }
14675 }
14676
14677 fn go_to_line<T: 'static>(
14678 &mut self,
14679 position: Anchor,
14680 highlight_color: Option<Hsla>,
14681 window: &mut Window,
14682 cx: &mut Context<Self>,
14683 ) {
14684 let snapshot = self.snapshot(window, cx).display_snapshot;
14685 let position = position.to_point(&snapshot.buffer_snapshot);
14686 let start = snapshot
14687 .buffer_snapshot
14688 .clip_point(Point::new(position.row, 0), Bias::Left);
14689 let end = start + Point::new(1, 0);
14690 let start = snapshot.buffer_snapshot.anchor_before(start);
14691 let end = snapshot.buffer_snapshot.anchor_before(end);
14692
14693 self.highlight_rows::<T>(
14694 start..end,
14695 highlight_color
14696 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14697 Default::default(),
14698 cx,
14699 );
14700
14701 if self.buffer.read(cx).is_singleton() {
14702 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14703 }
14704 }
14705
14706 pub fn go_to_definition(
14707 &mut self,
14708 _: &GoToDefinition,
14709 window: &mut Window,
14710 cx: &mut Context<Self>,
14711 ) -> Task<Result<Navigated>> {
14712 let definition =
14713 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14714 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14715 cx.spawn_in(window, async move |editor, cx| {
14716 if definition.await? == Navigated::Yes {
14717 return Ok(Navigated::Yes);
14718 }
14719 match fallback_strategy {
14720 GoToDefinitionFallback::None => Ok(Navigated::No),
14721 GoToDefinitionFallback::FindAllReferences => {
14722 match editor.update_in(cx, |editor, window, cx| {
14723 editor.find_all_references(&FindAllReferences, window, cx)
14724 })? {
14725 Some(references) => references.await,
14726 None => Ok(Navigated::No),
14727 }
14728 }
14729 }
14730 })
14731 }
14732
14733 pub fn go_to_declaration(
14734 &mut self,
14735 _: &GoToDeclaration,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) -> Task<Result<Navigated>> {
14739 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14740 }
14741
14742 pub fn go_to_declaration_split(
14743 &mut self,
14744 _: &GoToDeclaration,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) -> Task<Result<Navigated>> {
14748 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14749 }
14750
14751 pub fn go_to_implementation(
14752 &mut self,
14753 _: &GoToImplementation,
14754 window: &mut Window,
14755 cx: &mut Context<Self>,
14756 ) -> Task<Result<Navigated>> {
14757 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14758 }
14759
14760 pub fn go_to_implementation_split(
14761 &mut self,
14762 _: &GoToImplementationSplit,
14763 window: &mut Window,
14764 cx: &mut Context<Self>,
14765 ) -> Task<Result<Navigated>> {
14766 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14767 }
14768
14769 pub fn go_to_type_definition(
14770 &mut self,
14771 _: &GoToTypeDefinition,
14772 window: &mut Window,
14773 cx: &mut Context<Self>,
14774 ) -> Task<Result<Navigated>> {
14775 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14776 }
14777
14778 pub fn go_to_definition_split(
14779 &mut self,
14780 _: &GoToDefinitionSplit,
14781 window: &mut Window,
14782 cx: &mut Context<Self>,
14783 ) -> Task<Result<Navigated>> {
14784 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14785 }
14786
14787 pub fn go_to_type_definition_split(
14788 &mut self,
14789 _: &GoToTypeDefinitionSplit,
14790 window: &mut Window,
14791 cx: &mut Context<Self>,
14792 ) -> Task<Result<Navigated>> {
14793 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14794 }
14795
14796 fn go_to_definition_of_kind(
14797 &mut self,
14798 kind: GotoDefinitionKind,
14799 split: bool,
14800 window: &mut Window,
14801 cx: &mut Context<Self>,
14802 ) -> Task<Result<Navigated>> {
14803 let Some(provider) = self.semantics_provider.clone() else {
14804 return Task::ready(Ok(Navigated::No));
14805 };
14806 let head = self.selections.newest::<usize>(cx).head();
14807 let buffer = self.buffer.read(cx);
14808 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14809 text_anchor
14810 } else {
14811 return Task::ready(Ok(Navigated::No));
14812 };
14813
14814 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14815 return Task::ready(Ok(Navigated::No));
14816 };
14817
14818 cx.spawn_in(window, async move |editor, cx| {
14819 let definitions = definitions.await?;
14820 let navigated = editor
14821 .update_in(cx, |editor, window, cx| {
14822 editor.navigate_to_hover_links(
14823 Some(kind),
14824 definitions
14825 .into_iter()
14826 .filter(|location| {
14827 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14828 })
14829 .map(HoverLink::Text)
14830 .collect::<Vec<_>>(),
14831 split,
14832 window,
14833 cx,
14834 )
14835 })?
14836 .await?;
14837 anyhow::Ok(navigated)
14838 })
14839 }
14840
14841 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14842 let selection = self.selections.newest_anchor();
14843 let head = selection.head();
14844 let tail = selection.tail();
14845
14846 let Some((buffer, start_position)) =
14847 self.buffer.read(cx).text_anchor_for_position(head, cx)
14848 else {
14849 return;
14850 };
14851
14852 let end_position = if head != tail {
14853 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14854 return;
14855 };
14856 Some(pos)
14857 } else {
14858 None
14859 };
14860
14861 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14862 let url = if let Some(end_pos) = end_position {
14863 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14864 } else {
14865 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14866 };
14867
14868 if let Some(url) = url {
14869 editor.update(cx, |_, cx| {
14870 cx.open_url(&url);
14871 })
14872 } else {
14873 Ok(())
14874 }
14875 });
14876
14877 url_finder.detach();
14878 }
14879
14880 pub fn open_selected_filename(
14881 &mut self,
14882 _: &OpenSelectedFilename,
14883 window: &mut Window,
14884 cx: &mut Context<Self>,
14885 ) {
14886 let Some(workspace) = self.workspace() else {
14887 return;
14888 };
14889
14890 let position = self.selections.newest_anchor().head();
14891
14892 let Some((buffer, buffer_position)) =
14893 self.buffer.read(cx).text_anchor_for_position(position, cx)
14894 else {
14895 return;
14896 };
14897
14898 let project = self.project.clone();
14899
14900 cx.spawn_in(window, async move |_, cx| {
14901 let result = find_file(&buffer, project, buffer_position, cx).await;
14902
14903 if let Some((_, path)) = result {
14904 workspace
14905 .update_in(cx, |workspace, window, cx| {
14906 workspace.open_resolved_path(path, window, cx)
14907 })?
14908 .await?;
14909 }
14910 anyhow::Ok(())
14911 })
14912 .detach();
14913 }
14914
14915 pub(crate) fn navigate_to_hover_links(
14916 &mut self,
14917 kind: Option<GotoDefinitionKind>,
14918 mut definitions: Vec<HoverLink>,
14919 split: bool,
14920 window: &mut Window,
14921 cx: &mut Context<Editor>,
14922 ) -> Task<Result<Navigated>> {
14923 // If there is one definition, just open it directly
14924 if definitions.len() == 1 {
14925 let definition = definitions.pop().unwrap();
14926
14927 enum TargetTaskResult {
14928 Location(Option<Location>),
14929 AlreadyNavigated,
14930 }
14931
14932 let target_task = match definition {
14933 HoverLink::Text(link) => {
14934 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14935 }
14936 HoverLink::InlayHint(lsp_location, server_id) => {
14937 let computation =
14938 self.compute_target_location(lsp_location, server_id, window, cx);
14939 cx.background_spawn(async move {
14940 let location = computation.await?;
14941 Ok(TargetTaskResult::Location(location))
14942 })
14943 }
14944 HoverLink::Url(url) => {
14945 cx.open_url(&url);
14946 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14947 }
14948 HoverLink::File(path) => {
14949 if let Some(workspace) = self.workspace() {
14950 cx.spawn_in(window, async move |_, cx| {
14951 workspace
14952 .update_in(cx, |workspace, window, cx| {
14953 workspace.open_resolved_path(path, window, cx)
14954 })?
14955 .await
14956 .map(|_| TargetTaskResult::AlreadyNavigated)
14957 })
14958 } else {
14959 Task::ready(Ok(TargetTaskResult::Location(None)))
14960 }
14961 }
14962 };
14963 cx.spawn_in(window, async move |editor, cx| {
14964 let target = match target_task.await.context("target resolution task")? {
14965 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14966 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14967 TargetTaskResult::Location(Some(target)) => target,
14968 };
14969
14970 editor.update_in(cx, |editor, window, cx| {
14971 let Some(workspace) = editor.workspace() else {
14972 return Navigated::No;
14973 };
14974 let pane = workspace.read(cx).active_pane().clone();
14975
14976 let range = target.range.to_point(target.buffer.read(cx));
14977 let range = editor.range_for_match(&range);
14978 let range = collapse_multiline_range(range);
14979
14980 if !split
14981 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14982 {
14983 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14984 } else {
14985 window.defer(cx, move |window, cx| {
14986 let target_editor: Entity<Self> =
14987 workspace.update(cx, |workspace, cx| {
14988 let pane = if split {
14989 workspace.adjacent_pane(window, cx)
14990 } else {
14991 workspace.active_pane().clone()
14992 };
14993
14994 workspace.open_project_item(
14995 pane,
14996 target.buffer.clone(),
14997 true,
14998 true,
14999 window,
15000 cx,
15001 )
15002 });
15003 target_editor.update(cx, |target_editor, cx| {
15004 // When selecting a definition in a different buffer, disable the nav history
15005 // to avoid creating a history entry at the previous cursor location.
15006 pane.update(cx, |pane, _| pane.disable_history());
15007 target_editor.go_to_singleton_buffer_range(range, window, cx);
15008 pane.update(cx, |pane, _| pane.enable_history());
15009 });
15010 });
15011 }
15012 Navigated::Yes
15013 })
15014 })
15015 } else if !definitions.is_empty() {
15016 cx.spawn_in(window, async move |editor, cx| {
15017 let (title, location_tasks, workspace) = editor
15018 .update_in(cx, |editor, window, cx| {
15019 let tab_kind = match kind {
15020 Some(GotoDefinitionKind::Implementation) => "Implementations",
15021 _ => "Definitions",
15022 };
15023 let title = definitions
15024 .iter()
15025 .find_map(|definition| match definition {
15026 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15027 let buffer = origin.buffer.read(cx);
15028 format!(
15029 "{} for {}",
15030 tab_kind,
15031 buffer
15032 .text_for_range(origin.range.clone())
15033 .collect::<String>()
15034 )
15035 }),
15036 HoverLink::InlayHint(_, _) => None,
15037 HoverLink::Url(_) => None,
15038 HoverLink::File(_) => None,
15039 })
15040 .unwrap_or(tab_kind.to_string());
15041 let location_tasks = definitions
15042 .into_iter()
15043 .map(|definition| match definition {
15044 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15045 HoverLink::InlayHint(lsp_location, server_id) => editor
15046 .compute_target_location(lsp_location, server_id, window, cx),
15047 HoverLink::Url(_) => Task::ready(Ok(None)),
15048 HoverLink::File(_) => Task::ready(Ok(None)),
15049 })
15050 .collect::<Vec<_>>();
15051 (title, location_tasks, editor.workspace().clone())
15052 })
15053 .context("location tasks preparation")?;
15054
15055 let locations = future::join_all(location_tasks)
15056 .await
15057 .into_iter()
15058 .filter_map(|location| location.transpose())
15059 .collect::<Result<_>>()
15060 .context("location tasks")?;
15061
15062 let Some(workspace) = workspace else {
15063 return Ok(Navigated::No);
15064 };
15065 let opened = workspace
15066 .update_in(cx, |workspace, window, cx| {
15067 Self::open_locations_in_multibuffer(
15068 workspace,
15069 locations,
15070 title,
15071 split,
15072 MultibufferSelectionMode::First,
15073 window,
15074 cx,
15075 )
15076 })
15077 .ok();
15078
15079 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15080 })
15081 } else {
15082 Task::ready(Ok(Navigated::No))
15083 }
15084 }
15085
15086 fn compute_target_location(
15087 &self,
15088 lsp_location: lsp::Location,
15089 server_id: LanguageServerId,
15090 window: &mut Window,
15091 cx: &mut Context<Self>,
15092 ) -> Task<anyhow::Result<Option<Location>>> {
15093 let Some(project) = self.project.clone() else {
15094 return Task::ready(Ok(None));
15095 };
15096
15097 cx.spawn_in(window, async move |editor, cx| {
15098 let location_task = editor.update(cx, |_, cx| {
15099 project.update(cx, |project, cx| {
15100 let language_server_name = project
15101 .language_server_statuses(cx)
15102 .find(|(id, _)| server_id == *id)
15103 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15104 language_server_name.map(|language_server_name| {
15105 project.open_local_buffer_via_lsp(
15106 lsp_location.uri.clone(),
15107 server_id,
15108 language_server_name,
15109 cx,
15110 )
15111 })
15112 })
15113 })?;
15114 let location = match location_task {
15115 Some(task) => Some({
15116 let target_buffer_handle = task.await.context("open local buffer")?;
15117 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15118 let target_start = target_buffer
15119 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15120 let target_end = target_buffer
15121 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15122 target_buffer.anchor_after(target_start)
15123 ..target_buffer.anchor_before(target_end)
15124 })?;
15125 Location {
15126 buffer: target_buffer_handle,
15127 range,
15128 }
15129 }),
15130 None => None,
15131 };
15132 Ok(location)
15133 })
15134 }
15135
15136 pub fn find_all_references(
15137 &mut self,
15138 _: &FindAllReferences,
15139 window: &mut Window,
15140 cx: &mut Context<Self>,
15141 ) -> Option<Task<Result<Navigated>>> {
15142 let selection = self.selections.newest::<usize>(cx);
15143 let multi_buffer = self.buffer.read(cx);
15144 let head = selection.head();
15145
15146 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15147 let head_anchor = multi_buffer_snapshot.anchor_at(
15148 head,
15149 if head < selection.tail() {
15150 Bias::Right
15151 } else {
15152 Bias::Left
15153 },
15154 );
15155
15156 match self
15157 .find_all_references_task_sources
15158 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15159 {
15160 Ok(_) => {
15161 log::info!(
15162 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15163 );
15164 return None;
15165 }
15166 Err(i) => {
15167 self.find_all_references_task_sources.insert(i, head_anchor);
15168 }
15169 }
15170
15171 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15172 let workspace = self.workspace()?;
15173 let project = workspace.read(cx).project().clone();
15174 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15175 Some(cx.spawn_in(window, async move |editor, cx| {
15176 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15177 if let Ok(i) = editor
15178 .find_all_references_task_sources
15179 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15180 {
15181 editor.find_all_references_task_sources.remove(i);
15182 }
15183 });
15184
15185 let locations = references.await?;
15186 if locations.is_empty() {
15187 return anyhow::Ok(Navigated::No);
15188 }
15189
15190 workspace.update_in(cx, |workspace, window, cx| {
15191 let title = locations
15192 .first()
15193 .as_ref()
15194 .map(|location| {
15195 let buffer = location.buffer.read(cx);
15196 format!(
15197 "References to `{}`",
15198 buffer
15199 .text_for_range(location.range.clone())
15200 .collect::<String>()
15201 )
15202 })
15203 .unwrap();
15204 Self::open_locations_in_multibuffer(
15205 workspace,
15206 locations,
15207 title,
15208 false,
15209 MultibufferSelectionMode::First,
15210 window,
15211 cx,
15212 );
15213 Navigated::Yes
15214 })
15215 }))
15216 }
15217
15218 /// Opens a multibuffer with the given project locations in it
15219 pub fn open_locations_in_multibuffer(
15220 workspace: &mut Workspace,
15221 mut locations: Vec<Location>,
15222 title: String,
15223 split: bool,
15224 multibuffer_selection_mode: MultibufferSelectionMode,
15225 window: &mut Window,
15226 cx: &mut Context<Workspace>,
15227 ) {
15228 // If there are multiple definitions, open them in a multibuffer
15229 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15230 let mut locations = locations.into_iter().peekable();
15231 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15232 let capability = workspace.project().read(cx).capability();
15233
15234 let excerpt_buffer = cx.new(|cx| {
15235 let mut multibuffer = MultiBuffer::new(capability);
15236 while let Some(location) = locations.next() {
15237 let buffer = location.buffer.read(cx);
15238 let mut ranges_for_buffer = Vec::new();
15239 let range = location.range.to_point(buffer);
15240 ranges_for_buffer.push(range.clone());
15241
15242 while let Some(next_location) = locations.peek() {
15243 if next_location.buffer == location.buffer {
15244 ranges_for_buffer.push(next_location.range.to_point(buffer));
15245 locations.next();
15246 } else {
15247 break;
15248 }
15249 }
15250
15251 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15252 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15253 PathKey::for_buffer(&location.buffer, cx),
15254 location.buffer.clone(),
15255 ranges_for_buffer,
15256 DEFAULT_MULTIBUFFER_CONTEXT,
15257 cx,
15258 );
15259 ranges.extend(new_ranges)
15260 }
15261
15262 multibuffer.with_title(title)
15263 });
15264
15265 let editor = cx.new(|cx| {
15266 Editor::for_multibuffer(
15267 excerpt_buffer,
15268 Some(workspace.project().clone()),
15269 window,
15270 cx,
15271 )
15272 });
15273 editor.update(cx, |editor, cx| {
15274 match multibuffer_selection_mode {
15275 MultibufferSelectionMode::First => {
15276 if let Some(first_range) = ranges.first() {
15277 editor.change_selections(None, window, cx, |selections| {
15278 selections.clear_disjoint();
15279 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15280 });
15281 }
15282 editor.highlight_background::<Self>(
15283 &ranges,
15284 |theme| theme.editor_highlighted_line_background,
15285 cx,
15286 );
15287 }
15288 MultibufferSelectionMode::All => {
15289 editor.change_selections(None, window, cx, |selections| {
15290 selections.clear_disjoint();
15291 selections.select_anchor_ranges(ranges);
15292 });
15293 }
15294 }
15295 editor.register_buffers_with_language_servers(cx);
15296 });
15297
15298 let item = Box::new(editor);
15299 let item_id = item.item_id();
15300
15301 if split {
15302 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15303 } else {
15304 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15305 let (preview_item_id, preview_item_idx) =
15306 workspace.active_pane().read_with(cx, |pane, _| {
15307 (pane.preview_item_id(), pane.preview_item_idx())
15308 });
15309
15310 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15311
15312 if let Some(preview_item_id) = preview_item_id {
15313 workspace.active_pane().update(cx, |pane, cx| {
15314 pane.remove_item(preview_item_id, false, false, window, cx);
15315 });
15316 }
15317 } else {
15318 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15319 }
15320 }
15321 workspace.active_pane().update(cx, |pane, cx| {
15322 pane.set_preview_item_id(Some(item_id), cx);
15323 });
15324 }
15325
15326 pub fn rename(
15327 &mut self,
15328 _: &Rename,
15329 window: &mut Window,
15330 cx: &mut Context<Self>,
15331 ) -> Option<Task<Result<()>>> {
15332 use language::ToOffset as _;
15333
15334 let provider = self.semantics_provider.clone()?;
15335 let selection = self.selections.newest_anchor().clone();
15336 let (cursor_buffer, cursor_buffer_position) = self
15337 .buffer
15338 .read(cx)
15339 .text_anchor_for_position(selection.head(), cx)?;
15340 let (tail_buffer, cursor_buffer_position_end) = self
15341 .buffer
15342 .read(cx)
15343 .text_anchor_for_position(selection.tail(), cx)?;
15344 if tail_buffer != cursor_buffer {
15345 return None;
15346 }
15347
15348 let snapshot = cursor_buffer.read(cx).snapshot();
15349 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15350 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15351 let prepare_rename = provider
15352 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15353 .unwrap_or_else(|| Task::ready(Ok(None)));
15354 drop(snapshot);
15355
15356 Some(cx.spawn_in(window, async move |this, cx| {
15357 let rename_range = if let Some(range) = prepare_rename.await? {
15358 Some(range)
15359 } else {
15360 this.update(cx, |this, cx| {
15361 let buffer = this.buffer.read(cx).snapshot(cx);
15362 let mut buffer_highlights = this
15363 .document_highlights_for_position(selection.head(), &buffer)
15364 .filter(|highlight| {
15365 highlight.start.excerpt_id == selection.head().excerpt_id
15366 && highlight.end.excerpt_id == selection.head().excerpt_id
15367 });
15368 buffer_highlights
15369 .next()
15370 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15371 })?
15372 };
15373 if let Some(rename_range) = rename_range {
15374 this.update_in(cx, |this, window, cx| {
15375 let snapshot = cursor_buffer.read(cx).snapshot();
15376 let rename_buffer_range = rename_range.to_offset(&snapshot);
15377 let cursor_offset_in_rename_range =
15378 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15379 let cursor_offset_in_rename_range_end =
15380 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15381
15382 this.take_rename(false, window, cx);
15383 let buffer = this.buffer.read(cx).read(cx);
15384 let cursor_offset = selection.head().to_offset(&buffer);
15385 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15386 let rename_end = rename_start + rename_buffer_range.len();
15387 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15388 let mut old_highlight_id = None;
15389 let old_name: Arc<str> = buffer
15390 .chunks(rename_start..rename_end, true)
15391 .map(|chunk| {
15392 if old_highlight_id.is_none() {
15393 old_highlight_id = chunk.syntax_highlight_id;
15394 }
15395 chunk.text
15396 })
15397 .collect::<String>()
15398 .into();
15399
15400 drop(buffer);
15401
15402 // Position the selection in the rename editor so that it matches the current selection.
15403 this.show_local_selections = false;
15404 let rename_editor = cx.new(|cx| {
15405 let mut editor = Editor::single_line(window, cx);
15406 editor.buffer.update(cx, |buffer, cx| {
15407 buffer.edit([(0..0, old_name.clone())], None, cx)
15408 });
15409 let rename_selection_range = match cursor_offset_in_rename_range
15410 .cmp(&cursor_offset_in_rename_range_end)
15411 {
15412 Ordering::Equal => {
15413 editor.select_all(&SelectAll, window, cx);
15414 return editor;
15415 }
15416 Ordering::Less => {
15417 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15418 }
15419 Ordering::Greater => {
15420 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15421 }
15422 };
15423 if rename_selection_range.end > old_name.len() {
15424 editor.select_all(&SelectAll, window, cx);
15425 } else {
15426 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15427 s.select_ranges([rename_selection_range]);
15428 });
15429 }
15430 editor
15431 });
15432 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15433 if e == &EditorEvent::Focused {
15434 cx.emit(EditorEvent::FocusedIn)
15435 }
15436 })
15437 .detach();
15438
15439 let write_highlights =
15440 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15441 let read_highlights =
15442 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15443 let ranges = write_highlights
15444 .iter()
15445 .flat_map(|(_, ranges)| ranges.iter())
15446 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15447 .cloned()
15448 .collect();
15449
15450 this.highlight_text::<Rename>(
15451 ranges,
15452 HighlightStyle {
15453 fade_out: Some(0.6),
15454 ..Default::default()
15455 },
15456 cx,
15457 );
15458 let rename_focus_handle = rename_editor.focus_handle(cx);
15459 window.focus(&rename_focus_handle);
15460 let block_id = this.insert_blocks(
15461 [BlockProperties {
15462 style: BlockStyle::Flex,
15463 placement: BlockPlacement::Below(range.start),
15464 height: Some(1),
15465 render: Arc::new({
15466 let rename_editor = rename_editor.clone();
15467 move |cx: &mut BlockContext| {
15468 let mut text_style = cx.editor_style.text.clone();
15469 if let Some(highlight_style) = old_highlight_id
15470 .and_then(|h| h.style(&cx.editor_style.syntax))
15471 {
15472 text_style = text_style.highlight(highlight_style);
15473 }
15474 div()
15475 .block_mouse_except_scroll()
15476 .pl(cx.anchor_x)
15477 .child(EditorElement::new(
15478 &rename_editor,
15479 EditorStyle {
15480 background: cx.theme().system().transparent,
15481 local_player: cx.editor_style.local_player,
15482 text: text_style,
15483 scrollbar_width: cx.editor_style.scrollbar_width,
15484 syntax: cx.editor_style.syntax.clone(),
15485 status: cx.editor_style.status.clone(),
15486 inlay_hints_style: HighlightStyle {
15487 font_weight: Some(FontWeight::BOLD),
15488 ..make_inlay_hints_style(cx.app)
15489 },
15490 inline_completion_styles: make_suggestion_styles(
15491 cx.app,
15492 ),
15493 ..EditorStyle::default()
15494 },
15495 ))
15496 .into_any_element()
15497 }
15498 }),
15499 priority: 0,
15500 render_in_minimap: true,
15501 }],
15502 Some(Autoscroll::fit()),
15503 cx,
15504 )[0];
15505 this.pending_rename = Some(RenameState {
15506 range,
15507 old_name,
15508 editor: rename_editor,
15509 block_id,
15510 });
15511 })?;
15512 }
15513
15514 Ok(())
15515 }))
15516 }
15517
15518 pub fn confirm_rename(
15519 &mut self,
15520 _: &ConfirmRename,
15521 window: &mut Window,
15522 cx: &mut Context<Self>,
15523 ) -> Option<Task<Result<()>>> {
15524 let rename = self.take_rename(false, window, cx)?;
15525 let workspace = self.workspace()?.downgrade();
15526 let (buffer, start) = self
15527 .buffer
15528 .read(cx)
15529 .text_anchor_for_position(rename.range.start, cx)?;
15530 let (end_buffer, _) = self
15531 .buffer
15532 .read(cx)
15533 .text_anchor_for_position(rename.range.end, cx)?;
15534 if buffer != end_buffer {
15535 return None;
15536 }
15537
15538 let old_name = rename.old_name;
15539 let new_name = rename.editor.read(cx).text(cx);
15540
15541 let rename = self.semantics_provider.as_ref()?.perform_rename(
15542 &buffer,
15543 start,
15544 new_name.clone(),
15545 cx,
15546 )?;
15547
15548 Some(cx.spawn_in(window, async move |editor, cx| {
15549 let project_transaction = rename.await?;
15550 Self::open_project_transaction(
15551 &editor,
15552 workspace,
15553 project_transaction,
15554 format!("Rename: {} → {}", old_name, new_name),
15555 cx,
15556 )
15557 .await?;
15558
15559 editor.update(cx, |editor, cx| {
15560 editor.refresh_document_highlights(cx);
15561 })?;
15562 Ok(())
15563 }))
15564 }
15565
15566 fn take_rename(
15567 &mut self,
15568 moving_cursor: bool,
15569 window: &mut Window,
15570 cx: &mut Context<Self>,
15571 ) -> Option<RenameState> {
15572 let rename = self.pending_rename.take()?;
15573 if rename.editor.focus_handle(cx).is_focused(window) {
15574 window.focus(&self.focus_handle);
15575 }
15576
15577 self.remove_blocks(
15578 [rename.block_id].into_iter().collect(),
15579 Some(Autoscroll::fit()),
15580 cx,
15581 );
15582 self.clear_highlights::<Rename>(cx);
15583 self.show_local_selections = true;
15584
15585 if moving_cursor {
15586 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15587 editor.selections.newest::<usize>(cx).head()
15588 });
15589
15590 // Update the selection to match the position of the selection inside
15591 // the rename editor.
15592 let snapshot = self.buffer.read(cx).read(cx);
15593 let rename_range = rename.range.to_offset(&snapshot);
15594 let cursor_in_editor = snapshot
15595 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15596 .min(rename_range.end);
15597 drop(snapshot);
15598
15599 self.change_selections(None, window, cx, |s| {
15600 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15601 });
15602 } else {
15603 self.refresh_document_highlights(cx);
15604 }
15605
15606 Some(rename)
15607 }
15608
15609 pub fn pending_rename(&self) -> Option<&RenameState> {
15610 self.pending_rename.as_ref()
15611 }
15612
15613 fn format(
15614 &mut self,
15615 _: &Format,
15616 window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) -> Option<Task<Result<()>>> {
15619 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15620
15621 let project = match &self.project {
15622 Some(project) => project.clone(),
15623 None => return None,
15624 };
15625
15626 Some(self.perform_format(
15627 project,
15628 FormatTrigger::Manual,
15629 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15630 window,
15631 cx,
15632 ))
15633 }
15634
15635 fn format_selections(
15636 &mut self,
15637 _: &FormatSelections,
15638 window: &mut Window,
15639 cx: &mut Context<Self>,
15640 ) -> Option<Task<Result<()>>> {
15641 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15642
15643 let project = match &self.project {
15644 Some(project) => project.clone(),
15645 None => return None,
15646 };
15647
15648 let ranges = self
15649 .selections
15650 .all_adjusted(cx)
15651 .into_iter()
15652 .map(|selection| selection.range())
15653 .collect_vec();
15654
15655 Some(self.perform_format(
15656 project,
15657 FormatTrigger::Manual,
15658 FormatTarget::Ranges(ranges),
15659 window,
15660 cx,
15661 ))
15662 }
15663
15664 fn perform_format(
15665 &mut self,
15666 project: Entity<Project>,
15667 trigger: FormatTrigger,
15668 target: FormatTarget,
15669 window: &mut Window,
15670 cx: &mut Context<Self>,
15671 ) -> Task<Result<()>> {
15672 let buffer = self.buffer.clone();
15673 let (buffers, target) = match target {
15674 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15675 FormatTarget::Ranges(selection_ranges) => {
15676 let multi_buffer = buffer.read(cx);
15677 let snapshot = multi_buffer.read(cx);
15678 let mut buffers = HashSet::default();
15679 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15680 BTreeMap::new();
15681 for selection_range in selection_ranges {
15682 for (buffer, buffer_range, _) in
15683 snapshot.range_to_buffer_ranges(selection_range)
15684 {
15685 let buffer_id = buffer.remote_id();
15686 let start = buffer.anchor_before(buffer_range.start);
15687 let end = buffer.anchor_after(buffer_range.end);
15688 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15689 buffer_id_to_ranges
15690 .entry(buffer_id)
15691 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15692 .or_insert_with(|| vec![start..end]);
15693 }
15694 }
15695 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15696 }
15697 };
15698
15699 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15700 let selections_prev = transaction_id_prev
15701 .and_then(|transaction_id_prev| {
15702 // default to selections as they were after the last edit, if we have them,
15703 // instead of how they are now.
15704 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15705 // will take you back to where you made the last edit, instead of staying where you scrolled
15706 self.selection_history
15707 .transaction(transaction_id_prev)
15708 .map(|t| t.0.clone())
15709 })
15710 .unwrap_or_else(|| {
15711 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15712 self.selections.disjoint_anchors()
15713 });
15714
15715 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15716 let format = project.update(cx, |project, cx| {
15717 project.format(buffers, target, true, trigger, cx)
15718 });
15719
15720 cx.spawn_in(window, async move |editor, cx| {
15721 let transaction = futures::select_biased! {
15722 transaction = format.log_err().fuse() => transaction,
15723 () = timeout => {
15724 log::warn!("timed out waiting for formatting");
15725 None
15726 }
15727 };
15728
15729 buffer
15730 .update(cx, |buffer, cx| {
15731 if let Some(transaction) = transaction {
15732 if !buffer.is_singleton() {
15733 buffer.push_transaction(&transaction.0, cx);
15734 }
15735 }
15736 cx.notify();
15737 })
15738 .ok();
15739
15740 if let Some(transaction_id_now) =
15741 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15742 {
15743 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15744 if has_new_transaction {
15745 _ = editor.update(cx, |editor, _| {
15746 editor
15747 .selection_history
15748 .insert_transaction(transaction_id_now, selections_prev);
15749 });
15750 }
15751 }
15752
15753 Ok(())
15754 })
15755 }
15756
15757 fn organize_imports(
15758 &mut self,
15759 _: &OrganizeImports,
15760 window: &mut Window,
15761 cx: &mut Context<Self>,
15762 ) -> Option<Task<Result<()>>> {
15763 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15764 let project = match &self.project {
15765 Some(project) => project.clone(),
15766 None => return None,
15767 };
15768 Some(self.perform_code_action_kind(
15769 project,
15770 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15771 window,
15772 cx,
15773 ))
15774 }
15775
15776 fn perform_code_action_kind(
15777 &mut self,
15778 project: Entity<Project>,
15779 kind: CodeActionKind,
15780 window: &mut Window,
15781 cx: &mut Context<Self>,
15782 ) -> Task<Result<()>> {
15783 let buffer = self.buffer.clone();
15784 let buffers = buffer.read(cx).all_buffers();
15785 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15786 let apply_action = project.update(cx, |project, cx| {
15787 project.apply_code_action_kind(buffers, kind, true, cx)
15788 });
15789 cx.spawn_in(window, async move |_, cx| {
15790 let transaction = futures::select_biased! {
15791 () = timeout => {
15792 log::warn!("timed out waiting for executing code action");
15793 None
15794 }
15795 transaction = apply_action.log_err().fuse() => transaction,
15796 };
15797 buffer
15798 .update(cx, |buffer, cx| {
15799 // check if we need this
15800 if let Some(transaction) = transaction {
15801 if !buffer.is_singleton() {
15802 buffer.push_transaction(&transaction.0, cx);
15803 }
15804 }
15805 cx.notify();
15806 })
15807 .ok();
15808 Ok(())
15809 })
15810 }
15811
15812 fn restart_language_server(
15813 &mut self,
15814 _: &RestartLanguageServer,
15815 _: &mut Window,
15816 cx: &mut Context<Self>,
15817 ) {
15818 if let Some(project) = self.project.clone() {
15819 self.buffer.update(cx, |multi_buffer, cx| {
15820 project.update(cx, |project, cx| {
15821 project.restart_language_servers_for_buffers(
15822 multi_buffer.all_buffers().into_iter().collect(),
15823 cx,
15824 );
15825 });
15826 })
15827 }
15828 }
15829
15830 fn stop_language_server(
15831 &mut self,
15832 _: &StopLanguageServer,
15833 _: &mut Window,
15834 cx: &mut Context<Self>,
15835 ) {
15836 if let Some(project) = self.project.clone() {
15837 self.buffer.update(cx, |multi_buffer, cx| {
15838 project.update(cx, |project, cx| {
15839 project.stop_language_servers_for_buffers(
15840 multi_buffer.all_buffers().into_iter().collect(),
15841 cx,
15842 );
15843 cx.emit(project::Event::RefreshInlayHints);
15844 });
15845 });
15846 }
15847 }
15848
15849 fn cancel_language_server_work(
15850 workspace: &mut Workspace,
15851 _: &actions::CancelLanguageServerWork,
15852 _: &mut Window,
15853 cx: &mut Context<Workspace>,
15854 ) {
15855 let project = workspace.project();
15856 let buffers = workspace
15857 .active_item(cx)
15858 .and_then(|item| item.act_as::<Editor>(cx))
15859 .map_or(HashSet::default(), |editor| {
15860 editor.read(cx).buffer.read(cx).all_buffers()
15861 });
15862 project.update(cx, |project, cx| {
15863 project.cancel_language_server_work_for_buffers(buffers, cx);
15864 });
15865 }
15866
15867 fn show_character_palette(
15868 &mut self,
15869 _: &ShowCharacterPalette,
15870 window: &mut Window,
15871 _: &mut Context<Self>,
15872 ) {
15873 window.show_character_palette();
15874 }
15875
15876 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15877 if self.mode.is_minimap() {
15878 return;
15879 }
15880
15881 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15882 let buffer = self.buffer.read(cx).snapshot(cx);
15883 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15884 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15885 let is_valid = buffer
15886 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15887 .any(|entry| {
15888 entry.diagnostic.is_primary
15889 && !entry.range.is_empty()
15890 && entry.range.start == primary_range_start
15891 && entry.diagnostic.message == active_diagnostics.active_message
15892 });
15893
15894 if !is_valid {
15895 self.dismiss_diagnostics(cx);
15896 }
15897 }
15898 }
15899
15900 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15901 match &self.active_diagnostics {
15902 ActiveDiagnostic::Group(group) => Some(group),
15903 _ => None,
15904 }
15905 }
15906
15907 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15908 self.dismiss_diagnostics(cx);
15909 self.active_diagnostics = ActiveDiagnostic::All;
15910 }
15911
15912 fn activate_diagnostics(
15913 &mut self,
15914 buffer_id: BufferId,
15915 diagnostic: DiagnosticEntry<usize>,
15916 window: &mut Window,
15917 cx: &mut Context<Self>,
15918 ) {
15919 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15920 return;
15921 }
15922 self.dismiss_diagnostics(cx);
15923 let snapshot = self.snapshot(window, cx);
15924 let buffer = self.buffer.read(cx).snapshot(cx);
15925 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15926 return;
15927 };
15928
15929 let diagnostic_group = buffer
15930 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15931 .collect::<Vec<_>>();
15932
15933 let blocks =
15934 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15935
15936 let blocks = self.display_map.update(cx, |display_map, cx| {
15937 display_map.insert_blocks(blocks, cx).into_iter().collect()
15938 });
15939 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15940 active_range: buffer.anchor_before(diagnostic.range.start)
15941 ..buffer.anchor_after(diagnostic.range.end),
15942 active_message: diagnostic.diagnostic.message.clone(),
15943 group_id: diagnostic.diagnostic.group_id,
15944 blocks,
15945 });
15946 cx.notify();
15947 }
15948
15949 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15950 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15951 return;
15952 };
15953
15954 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15955 if let ActiveDiagnostic::Group(group) = prev {
15956 self.display_map.update(cx, |display_map, cx| {
15957 display_map.remove_blocks(group.blocks, cx);
15958 });
15959 cx.notify();
15960 }
15961 }
15962
15963 /// Disable inline diagnostics rendering for this editor.
15964 pub fn disable_inline_diagnostics(&mut self) {
15965 self.inline_diagnostics_enabled = false;
15966 self.inline_diagnostics_update = Task::ready(());
15967 self.inline_diagnostics.clear();
15968 }
15969
15970 pub fn diagnostics_enabled(&self) -> bool {
15971 self.mode.is_full()
15972 }
15973
15974 pub fn inline_diagnostics_enabled(&self) -> bool {
15975 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15976 }
15977
15978 pub fn show_inline_diagnostics(&self) -> bool {
15979 self.show_inline_diagnostics
15980 }
15981
15982 pub fn toggle_inline_diagnostics(
15983 &mut self,
15984 _: &ToggleInlineDiagnostics,
15985 window: &mut Window,
15986 cx: &mut Context<Editor>,
15987 ) {
15988 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15989 self.refresh_inline_diagnostics(false, window, cx);
15990 }
15991
15992 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15993 self.diagnostics_max_severity = severity;
15994 self.display_map.update(cx, |display_map, _| {
15995 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15996 });
15997 }
15998
15999 pub fn toggle_diagnostics(
16000 &mut self,
16001 _: &ToggleDiagnostics,
16002 window: &mut Window,
16003 cx: &mut Context<Editor>,
16004 ) {
16005 if !self.diagnostics_enabled() {
16006 return;
16007 }
16008
16009 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16010 EditorSettings::get_global(cx)
16011 .diagnostics_max_severity
16012 .filter(|severity| severity != &DiagnosticSeverity::Off)
16013 .unwrap_or(DiagnosticSeverity::Hint)
16014 } else {
16015 DiagnosticSeverity::Off
16016 };
16017 self.set_max_diagnostics_severity(new_severity, cx);
16018 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16019 self.active_diagnostics = ActiveDiagnostic::None;
16020 self.inline_diagnostics_update = Task::ready(());
16021 self.inline_diagnostics.clear();
16022 } else {
16023 self.refresh_inline_diagnostics(false, window, cx);
16024 }
16025
16026 cx.notify();
16027 }
16028
16029 pub fn toggle_minimap(
16030 &mut self,
16031 _: &ToggleMinimap,
16032 window: &mut Window,
16033 cx: &mut Context<Editor>,
16034 ) {
16035 if self.supports_minimap(cx) {
16036 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16037 }
16038 }
16039
16040 fn refresh_inline_diagnostics(
16041 &mut self,
16042 debounce: bool,
16043 window: &mut Window,
16044 cx: &mut Context<Self>,
16045 ) {
16046 let max_severity = ProjectSettings::get_global(cx)
16047 .diagnostics
16048 .inline
16049 .max_severity
16050 .unwrap_or(self.diagnostics_max_severity);
16051
16052 if !self.inline_diagnostics_enabled()
16053 || !self.show_inline_diagnostics
16054 || max_severity == DiagnosticSeverity::Off
16055 {
16056 self.inline_diagnostics_update = Task::ready(());
16057 self.inline_diagnostics.clear();
16058 return;
16059 }
16060
16061 let debounce_ms = ProjectSettings::get_global(cx)
16062 .diagnostics
16063 .inline
16064 .update_debounce_ms;
16065 let debounce = if debounce && debounce_ms > 0 {
16066 Some(Duration::from_millis(debounce_ms))
16067 } else {
16068 None
16069 };
16070 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16071 if let Some(debounce) = debounce {
16072 cx.background_executor().timer(debounce).await;
16073 }
16074 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16075 editor
16076 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16077 .ok()
16078 }) else {
16079 return;
16080 };
16081
16082 let new_inline_diagnostics = cx
16083 .background_spawn(async move {
16084 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16085 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16086 let message = diagnostic_entry
16087 .diagnostic
16088 .message
16089 .split_once('\n')
16090 .map(|(line, _)| line)
16091 .map(SharedString::new)
16092 .unwrap_or_else(|| {
16093 SharedString::from(diagnostic_entry.diagnostic.message)
16094 });
16095 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16096 let (Ok(i) | Err(i)) = inline_diagnostics
16097 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16098 inline_diagnostics.insert(
16099 i,
16100 (
16101 start_anchor,
16102 InlineDiagnostic {
16103 message,
16104 group_id: diagnostic_entry.diagnostic.group_id,
16105 start: diagnostic_entry.range.start.to_point(&snapshot),
16106 is_primary: diagnostic_entry.diagnostic.is_primary,
16107 severity: diagnostic_entry.diagnostic.severity,
16108 },
16109 ),
16110 );
16111 }
16112 inline_diagnostics
16113 })
16114 .await;
16115
16116 editor
16117 .update(cx, |editor, cx| {
16118 editor.inline_diagnostics = new_inline_diagnostics;
16119 cx.notify();
16120 })
16121 .ok();
16122 });
16123 }
16124
16125 fn pull_diagnostics(
16126 &mut self,
16127 buffer_id: Option<BufferId>,
16128 window: &Window,
16129 cx: &mut Context<Self>,
16130 ) -> Option<()> {
16131 if !self.mode().is_full() {
16132 return None;
16133 }
16134 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16135 .diagnostics
16136 .lsp_pull_diagnostics;
16137 if !pull_diagnostics_settings.enabled {
16138 return None;
16139 }
16140 let project = self.project.as_ref()?.downgrade();
16141 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16142 let mut buffers = self.buffer.read(cx).all_buffers();
16143 if let Some(buffer_id) = buffer_id {
16144 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16145 }
16146
16147 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16148 cx.background_executor().timer(debounce).await;
16149
16150 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16151 buffers
16152 .into_iter()
16153 .flat_map(|buffer| {
16154 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16155 })
16156 .collect::<FuturesUnordered<_>>()
16157 }) else {
16158 return;
16159 };
16160
16161 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16162 match pull_task {
16163 Ok(()) => {
16164 if editor
16165 .update_in(cx, |editor, window, cx| {
16166 editor.update_diagnostics_state(window, cx);
16167 })
16168 .is_err()
16169 {
16170 return;
16171 }
16172 }
16173 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16174 }
16175 }
16176 });
16177
16178 Some(())
16179 }
16180
16181 pub fn set_selections_from_remote(
16182 &mut self,
16183 selections: Vec<Selection<Anchor>>,
16184 pending_selection: Option<Selection<Anchor>>,
16185 window: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) {
16188 let old_cursor_position = self.selections.newest_anchor().head();
16189 self.selections.change_with(cx, |s| {
16190 s.select_anchors(selections);
16191 if let Some(pending_selection) = pending_selection {
16192 s.set_pending(pending_selection, SelectMode::Character);
16193 } else {
16194 s.clear_pending();
16195 }
16196 });
16197 self.selections_did_change(
16198 false,
16199 &old_cursor_position,
16200 SelectionEffects::default(),
16201 window,
16202 cx,
16203 );
16204 }
16205
16206 pub fn transact(
16207 &mut self,
16208 window: &mut Window,
16209 cx: &mut Context<Self>,
16210 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16211 ) -> Option<TransactionId> {
16212 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16213 this.start_transaction_at(Instant::now(), window, cx);
16214 update(this, window, cx);
16215 this.end_transaction_at(Instant::now(), cx)
16216 })
16217 }
16218
16219 pub fn start_transaction_at(
16220 &mut self,
16221 now: Instant,
16222 window: &mut Window,
16223 cx: &mut Context<Self>,
16224 ) {
16225 self.end_selection(window, cx);
16226 if let Some(tx_id) = self
16227 .buffer
16228 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16229 {
16230 self.selection_history
16231 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16232 cx.emit(EditorEvent::TransactionBegun {
16233 transaction_id: tx_id,
16234 })
16235 }
16236 }
16237
16238 pub fn end_transaction_at(
16239 &mut self,
16240 now: Instant,
16241 cx: &mut Context<Self>,
16242 ) -> Option<TransactionId> {
16243 if let Some(transaction_id) = self
16244 .buffer
16245 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16246 {
16247 if let Some((_, end_selections)) =
16248 self.selection_history.transaction_mut(transaction_id)
16249 {
16250 *end_selections = Some(self.selections.disjoint_anchors());
16251 } else {
16252 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16253 }
16254
16255 cx.emit(EditorEvent::Edited { transaction_id });
16256 Some(transaction_id)
16257 } else {
16258 None
16259 }
16260 }
16261
16262 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16263 if self.selection_mark_mode {
16264 self.change_selections(None, window, cx, |s| {
16265 s.move_with(|_, sel| {
16266 sel.collapse_to(sel.head(), SelectionGoal::None);
16267 });
16268 })
16269 }
16270 self.selection_mark_mode = true;
16271 cx.notify();
16272 }
16273
16274 pub fn swap_selection_ends(
16275 &mut self,
16276 _: &actions::SwapSelectionEnds,
16277 window: &mut Window,
16278 cx: &mut Context<Self>,
16279 ) {
16280 self.change_selections(None, window, cx, |s| {
16281 s.move_with(|_, sel| {
16282 if sel.start != sel.end {
16283 sel.reversed = !sel.reversed
16284 }
16285 });
16286 });
16287 self.request_autoscroll(Autoscroll::newest(), cx);
16288 cx.notify();
16289 }
16290
16291 pub fn toggle_fold(
16292 &mut self,
16293 _: &actions::ToggleFold,
16294 window: &mut Window,
16295 cx: &mut Context<Self>,
16296 ) {
16297 if self.is_singleton(cx) {
16298 let selection = self.selections.newest::<Point>(cx);
16299
16300 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16301 let range = if selection.is_empty() {
16302 let point = selection.head().to_display_point(&display_map);
16303 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16304 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16305 .to_point(&display_map);
16306 start..end
16307 } else {
16308 selection.range()
16309 };
16310 if display_map.folds_in_range(range).next().is_some() {
16311 self.unfold_lines(&Default::default(), window, cx)
16312 } else {
16313 self.fold(&Default::default(), window, cx)
16314 }
16315 } else {
16316 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16317 let buffer_ids: HashSet<_> = self
16318 .selections
16319 .disjoint_anchor_ranges()
16320 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16321 .collect();
16322
16323 let should_unfold = buffer_ids
16324 .iter()
16325 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16326
16327 for buffer_id in buffer_ids {
16328 if should_unfold {
16329 self.unfold_buffer(buffer_id, cx);
16330 } else {
16331 self.fold_buffer(buffer_id, cx);
16332 }
16333 }
16334 }
16335 }
16336
16337 pub fn toggle_fold_recursive(
16338 &mut self,
16339 _: &actions::ToggleFoldRecursive,
16340 window: &mut Window,
16341 cx: &mut Context<Self>,
16342 ) {
16343 let selection = self.selections.newest::<Point>(cx);
16344
16345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16346 let range = if selection.is_empty() {
16347 let point = selection.head().to_display_point(&display_map);
16348 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16349 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16350 .to_point(&display_map);
16351 start..end
16352 } else {
16353 selection.range()
16354 };
16355 if display_map.folds_in_range(range).next().is_some() {
16356 self.unfold_recursive(&Default::default(), window, cx)
16357 } else {
16358 self.fold_recursive(&Default::default(), window, cx)
16359 }
16360 }
16361
16362 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16363 if self.is_singleton(cx) {
16364 let mut to_fold = Vec::new();
16365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16366 let selections = self.selections.all_adjusted(cx);
16367
16368 for selection in selections {
16369 let range = selection.range().sorted();
16370 let buffer_start_row = range.start.row;
16371
16372 if range.start.row != range.end.row {
16373 let mut found = false;
16374 let mut row = range.start.row;
16375 while row <= range.end.row {
16376 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16377 {
16378 found = true;
16379 row = crease.range().end.row + 1;
16380 to_fold.push(crease);
16381 } else {
16382 row += 1
16383 }
16384 }
16385 if found {
16386 continue;
16387 }
16388 }
16389
16390 for row in (0..=range.start.row).rev() {
16391 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16392 if crease.range().end.row >= buffer_start_row {
16393 to_fold.push(crease);
16394 if row <= range.start.row {
16395 break;
16396 }
16397 }
16398 }
16399 }
16400 }
16401
16402 self.fold_creases(to_fold, true, window, cx);
16403 } else {
16404 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16405 let buffer_ids = self
16406 .selections
16407 .disjoint_anchor_ranges()
16408 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16409 .collect::<HashSet<_>>();
16410 for buffer_id in buffer_ids {
16411 self.fold_buffer(buffer_id, cx);
16412 }
16413 }
16414 }
16415
16416 fn fold_at_level(
16417 &mut self,
16418 fold_at: &FoldAtLevel,
16419 window: &mut Window,
16420 cx: &mut Context<Self>,
16421 ) {
16422 if !self.buffer.read(cx).is_singleton() {
16423 return;
16424 }
16425
16426 let fold_at_level = fold_at.0;
16427 let snapshot = self.buffer.read(cx).snapshot(cx);
16428 let mut to_fold = Vec::new();
16429 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16430
16431 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16432 while start_row < end_row {
16433 match self
16434 .snapshot(window, cx)
16435 .crease_for_buffer_row(MultiBufferRow(start_row))
16436 {
16437 Some(crease) => {
16438 let nested_start_row = crease.range().start.row + 1;
16439 let nested_end_row = crease.range().end.row;
16440
16441 if current_level < fold_at_level {
16442 stack.push((nested_start_row, nested_end_row, current_level + 1));
16443 } else if current_level == fold_at_level {
16444 to_fold.push(crease);
16445 }
16446
16447 start_row = nested_end_row + 1;
16448 }
16449 None => start_row += 1,
16450 }
16451 }
16452 }
16453
16454 self.fold_creases(to_fold, true, window, cx);
16455 }
16456
16457 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16458 if self.buffer.read(cx).is_singleton() {
16459 let mut fold_ranges = Vec::new();
16460 let snapshot = self.buffer.read(cx).snapshot(cx);
16461
16462 for row in 0..snapshot.max_row().0 {
16463 if let Some(foldable_range) = self
16464 .snapshot(window, cx)
16465 .crease_for_buffer_row(MultiBufferRow(row))
16466 {
16467 fold_ranges.push(foldable_range);
16468 }
16469 }
16470
16471 self.fold_creases(fold_ranges, true, window, cx);
16472 } else {
16473 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16474 editor
16475 .update_in(cx, |editor, _, cx| {
16476 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16477 editor.fold_buffer(buffer_id, cx);
16478 }
16479 })
16480 .ok();
16481 });
16482 }
16483 }
16484
16485 pub fn fold_function_bodies(
16486 &mut self,
16487 _: &actions::FoldFunctionBodies,
16488 window: &mut Window,
16489 cx: &mut Context<Self>,
16490 ) {
16491 let snapshot = self.buffer.read(cx).snapshot(cx);
16492
16493 let ranges = snapshot
16494 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16495 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16496 .collect::<Vec<_>>();
16497
16498 let creases = ranges
16499 .into_iter()
16500 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16501 .collect();
16502
16503 self.fold_creases(creases, true, window, cx);
16504 }
16505
16506 pub fn fold_recursive(
16507 &mut self,
16508 _: &actions::FoldRecursive,
16509 window: &mut Window,
16510 cx: &mut Context<Self>,
16511 ) {
16512 let mut to_fold = Vec::new();
16513 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16514 let selections = self.selections.all_adjusted(cx);
16515
16516 for selection in selections {
16517 let range = selection.range().sorted();
16518 let buffer_start_row = range.start.row;
16519
16520 if range.start.row != range.end.row {
16521 let mut found = false;
16522 for row in range.start.row..=range.end.row {
16523 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16524 found = true;
16525 to_fold.push(crease);
16526 }
16527 }
16528 if found {
16529 continue;
16530 }
16531 }
16532
16533 for row in (0..=range.start.row).rev() {
16534 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16535 if crease.range().end.row >= buffer_start_row {
16536 to_fold.push(crease);
16537 } else {
16538 break;
16539 }
16540 }
16541 }
16542 }
16543
16544 self.fold_creases(to_fold, true, window, cx);
16545 }
16546
16547 pub fn fold_at(
16548 &mut self,
16549 buffer_row: MultiBufferRow,
16550 window: &mut Window,
16551 cx: &mut Context<Self>,
16552 ) {
16553 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16554
16555 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16556 let autoscroll = self
16557 .selections
16558 .all::<Point>(cx)
16559 .iter()
16560 .any(|selection| crease.range().overlaps(&selection.range()));
16561
16562 self.fold_creases(vec![crease], autoscroll, window, cx);
16563 }
16564 }
16565
16566 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16567 if self.is_singleton(cx) {
16568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16569 let buffer = &display_map.buffer_snapshot;
16570 let selections = self.selections.all::<Point>(cx);
16571 let ranges = selections
16572 .iter()
16573 .map(|s| {
16574 let range = s.display_range(&display_map).sorted();
16575 let mut start = range.start.to_point(&display_map);
16576 let mut end = range.end.to_point(&display_map);
16577 start.column = 0;
16578 end.column = buffer.line_len(MultiBufferRow(end.row));
16579 start..end
16580 })
16581 .collect::<Vec<_>>();
16582
16583 self.unfold_ranges(&ranges, true, true, cx);
16584 } else {
16585 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16586 let buffer_ids = self
16587 .selections
16588 .disjoint_anchor_ranges()
16589 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16590 .collect::<HashSet<_>>();
16591 for buffer_id in buffer_ids {
16592 self.unfold_buffer(buffer_id, cx);
16593 }
16594 }
16595 }
16596
16597 pub fn unfold_recursive(
16598 &mut self,
16599 _: &UnfoldRecursive,
16600 _window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16604 let selections = self.selections.all::<Point>(cx);
16605 let ranges = selections
16606 .iter()
16607 .map(|s| {
16608 let mut range = s.display_range(&display_map).sorted();
16609 *range.start.column_mut() = 0;
16610 *range.end.column_mut() = display_map.line_len(range.end.row());
16611 let start = range.start.to_point(&display_map);
16612 let end = range.end.to_point(&display_map);
16613 start..end
16614 })
16615 .collect::<Vec<_>>();
16616
16617 self.unfold_ranges(&ranges, true, true, cx);
16618 }
16619
16620 pub fn unfold_at(
16621 &mut self,
16622 buffer_row: MultiBufferRow,
16623 _window: &mut Window,
16624 cx: &mut Context<Self>,
16625 ) {
16626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16627
16628 let intersection_range = Point::new(buffer_row.0, 0)
16629 ..Point::new(
16630 buffer_row.0,
16631 display_map.buffer_snapshot.line_len(buffer_row),
16632 );
16633
16634 let autoscroll = self
16635 .selections
16636 .all::<Point>(cx)
16637 .iter()
16638 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16639
16640 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16641 }
16642
16643 pub fn unfold_all(
16644 &mut self,
16645 _: &actions::UnfoldAll,
16646 _window: &mut Window,
16647 cx: &mut Context<Self>,
16648 ) {
16649 if self.buffer.read(cx).is_singleton() {
16650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16651 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16652 } else {
16653 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16654 editor
16655 .update(cx, |editor, cx| {
16656 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16657 editor.unfold_buffer(buffer_id, cx);
16658 }
16659 })
16660 .ok();
16661 });
16662 }
16663 }
16664
16665 pub fn fold_selected_ranges(
16666 &mut self,
16667 _: &FoldSelectedRanges,
16668 window: &mut Window,
16669 cx: &mut Context<Self>,
16670 ) {
16671 let selections = self.selections.all_adjusted(cx);
16672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16673 let ranges = selections
16674 .into_iter()
16675 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16676 .collect::<Vec<_>>();
16677 self.fold_creases(ranges, true, window, cx);
16678 }
16679
16680 pub fn fold_ranges<T: ToOffset + Clone>(
16681 &mut self,
16682 ranges: Vec<Range<T>>,
16683 auto_scroll: bool,
16684 window: &mut Window,
16685 cx: &mut Context<Self>,
16686 ) {
16687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16688 let ranges = ranges
16689 .into_iter()
16690 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16691 .collect::<Vec<_>>();
16692 self.fold_creases(ranges, auto_scroll, window, cx);
16693 }
16694
16695 pub fn fold_creases<T: ToOffset + Clone>(
16696 &mut self,
16697 creases: Vec<Crease<T>>,
16698 auto_scroll: bool,
16699 _window: &mut Window,
16700 cx: &mut Context<Self>,
16701 ) {
16702 if creases.is_empty() {
16703 return;
16704 }
16705
16706 let mut buffers_affected = HashSet::default();
16707 let multi_buffer = self.buffer().read(cx);
16708 for crease in &creases {
16709 if let Some((_, buffer, _)) =
16710 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16711 {
16712 buffers_affected.insert(buffer.read(cx).remote_id());
16713 };
16714 }
16715
16716 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16717
16718 if auto_scroll {
16719 self.request_autoscroll(Autoscroll::fit(), cx);
16720 }
16721
16722 cx.notify();
16723
16724 self.scrollbar_marker_state.dirty = true;
16725 self.folds_did_change(cx);
16726 }
16727
16728 /// Removes any folds whose ranges intersect any of the given ranges.
16729 pub fn unfold_ranges<T: ToOffset + Clone>(
16730 &mut self,
16731 ranges: &[Range<T>],
16732 inclusive: bool,
16733 auto_scroll: bool,
16734 cx: &mut Context<Self>,
16735 ) {
16736 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16737 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16738 });
16739 self.folds_did_change(cx);
16740 }
16741
16742 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16743 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16744 return;
16745 }
16746 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16747 self.display_map.update(cx, |display_map, cx| {
16748 display_map.fold_buffers([buffer_id], cx)
16749 });
16750 cx.emit(EditorEvent::BufferFoldToggled {
16751 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16752 folded: true,
16753 });
16754 cx.notify();
16755 }
16756
16757 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16758 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16759 return;
16760 }
16761 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16762 self.display_map.update(cx, |display_map, cx| {
16763 display_map.unfold_buffers([buffer_id], cx);
16764 });
16765 cx.emit(EditorEvent::BufferFoldToggled {
16766 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16767 folded: false,
16768 });
16769 cx.notify();
16770 }
16771
16772 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16773 self.display_map.read(cx).is_buffer_folded(buffer)
16774 }
16775
16776 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16777 self.display_map.read(cx).folded_buffers()
16778 }
16779
16780 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16781 self.display_map.update(cx, |display_map, cx| {
16782 display_map.disable_header_for_buffer(buffer_id, cx);
16783 });
16784 cx.notify();
16785 }
16786
16787 /// Removes any folds with the given ranges.
16788 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16789 &mut self,
16790 ranges: &[Range<T>],
16791 type_id: TypeId,
16792 auto_scroll: bool,
16793 cx: &mut Context<Self>,
16794 ) {
16795 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16796 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16797 });
16798 self.folds_did_change(cx);
16799 }
16800
16801 fn remove_folds_with<T: ToOffset + Clone>(
16802 &mut self,
16803 ranges: &[Range<T>],
16804 auto_scroll: bool,
16805 cx: &mut Context<Self>,
16806 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16807 ) {
16808 if ranges.is_empty() {
16809 return;
16810 }
16811
16812 let mut buffers_affected = HashSet::default();
16813 let multi_buffer = self.buffer().read(cx);
16814 for range in ranges {
16815 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16816 buffers_affected.insert(buffer.read(cx).remote_id());
16817 };
16818 }
16819
16820 self.display_map.update(cx, update);
16821
16822 if auto_scroll {
16823 self.request_autoscroll(Autoscroll::fit(), cx);
16824 }
16825
16826 cx.notify();
16827 self.scrollbar_marker_state.dirty = true;
16828 self.active_indent_guides_state.dirty = true;
16829 }
16830
16831 pub fn update_fold_widths(
16832 &mut self,
16833 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16834 cx: &mut Context<Self>,
16835 ) -> bool {
16836 self.display_map
16837 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16838 }
16839
16840 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16841 self.display_map.read(cx).fold_placeholder.clone()
16842 }
16843
16844 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16845 self.buffer.update(cx, |buffer, cx| {
16846 buffer.set_all_diff_hunks_expanded(cx);
16847 });
16848 }
16849
16850 pub fn expand_all_diff_hunks(
16851 &mut self,
16852 _: &ExpandAllDiffHunks,
16853 _window: &mut Window,
16854 cx: &mut Context<Self>,
16855 ) {
16856 self.buffer.update(cx, |buffer, cx| {
16857 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16858 });
16859 }
16860
16861 pub fn toggle_selected_diff_hunks(
16862 &mut self,
16863 _: &ToggleSelectedDiffHunks,
16864 _window: &mut Window,
16865 cx: &mut Context<Self>,
16866 ) {
16867 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16868 self.toggle_diff_hunks_in_ranges(ranges, cx);
16869 }
16870
16871 pub fn diff_hunks_in_ranges<'a>(
16872 &'a self,
16873 ranges: &'a [Range<Anchor>],
16874 buffer: &'a MultiBufferSnapshot,
16875 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16876 ranges.iter().flat_map(move |range| {
16877 let end_excerpt_id = range.end.excerpt_id;
16878 let range = range.to_point(buffer);
16879 let mut peek_end = range.end;
16880 if range.end.row < buffer.max_row().0 {
16881 peek_end = Point::new(range.end.row + 1, 0);
16882 }
16883 buffer
16884 .diff_hunks_in_range(range.start..peek_end)
16885 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16886 })
16887 }
16888
16889 pub fn has_stageable_diff_hunks_in_ranges(
16890 &self,
16891 ranges: &[Range<Anchor>],
16892 snapshot: &MultiBufferSnapshot,
16893 ) -> bool {
16894 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16895 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16896 }
16897
16898 pub fn toggle_staged_selected_diff_hunks(
16899 &mut self,
16900 _: &::git::ToggleStaged,
16901 _: &mut Window,
16902 cx: &mut Context<Self>,
16903 ) {
16904 let snapshot = self.buffer.read(cx).snapshot(cx);
16905 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16906 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16907 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16908 }
16909
16910 pub fn set_render_diff_hunk_controls(
16911 &mut self,
16912 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16913 cx: &mut Context<Self>,
16914 ) {
16915 self.render_diff_hunk_controls = render_diff_hunk_controls;
16916 cx.notify();
16917 }
16918
16919 pub fn stage_and_next(
16920 &mut self,
16921 _: &::git::StageAndNext,
16922 window: &mut Window,
16923 cx: &mut Context<Self>,
16924 ) {
16925 self.do_stage_or_unstage_and_next(true, window, cx);
16926 }
16927
16928 pub fn unstage_and_next(
16929 &mut self,
16930 _: &::git::UnstageAndNext,
16931 window: &mut Window,
16932 cx: &mut Context<Self>,
16933 ) {
16934 self.do_stage_or_unstage_and_next(false, window, cx);
16935 }
16936
16937 pub fn stage_or_unstage_diff_hunks(
16938 &mut self,
16939 stage: bool,
16940 ranges: Vec<Range<Anchor>>,
16941 cx: &mut Context<Self>,
16942 ) {
16943 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16944 cx.spawn(async move |this, cx| {
16945 task.await?;
16946 this.update(cx, |this, cx| {
16947 let snapshot = this.buffer.read(cx).snapshot(cx);
16948 let chunk_by = this
16949 .diff_hunks_in_ranges(&ranges, &snapshot)
16950 .chunk_by(|hunk| hunk.buffer_id);
16951 for (buffer_id, hunks) in &chunk_by {
16952 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16953 }
16954 })
16955 })
16956 .detach_and_log_err(cx);
16957 }
16958
16959 fn save_buffers_for_ranges_if_needed(
16960 &mut self,
16961 ranges: &[Range<Anchor>],
16962 cx: &mut Context<Editor>,
16963 ) -> Task<Result<()>> {
16964 let multibuffer = self.buffer.read(cx);
16965 let snapshot = multibuffer.read(cx);
16966 let buffer_ids: HashSet<_> = ranges
16967 .iter()
16968 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16969 .collect();
16970 drop(snapshot);
16971
16972 let mut buffers = HashSet::default();
16973 for buffer_id in buffer_ids {
16974 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16975 let buffer = buffer_entity.read(cx);
16976 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16977 {
16978 buffers.insert(buffer_entity);
16979 }
16980 }
16981 }
16982
16983 if let Some(project) = &self.project {
16984 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16985 } else {
16986 Task::ready(Ok(()))
16987 }
16988 }
16989
16990 fn do_stage_or_unstage_and_next(
16991 &mut self,
16992 stage: bool,
16993 window: &mut Window,
16994 cx: &mut Context<Self>,
16995 ) {
16996 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16997
16998 if ranges.iter().any(|range| range.start != range.end) {
16999 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17000 return;
17001 }
17002
17003 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17004 let snapshot = self.snapshot(window, cx);
17005 let position = self.selections.newest::<Point>(cx).head();
17006 let mut row = snapshot
17007 .buffer_snapshot
17008 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17009 .find(|hunk| hunk.row_range.start.0 > position.row)
17010 .map(|hunk| hunk.row_range.start);
17011
17012 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17013 // Outside of the project diff editor, wrap around to the beginning.
17014 if !all_diff_hunks_expanded {
17015 row = row.or_else(|| {
17016 snapshot
17017 .buffer_snapshot
17018 .diff_hunks_in_range(Point::zero()..position)
17019 .find(|hunk| hunk.row_range.end.0 < position.row)
17020 .map(|hunk| hunk.row_range.start)
17021 });
17022 }
17023
17024 if let Some(row) = row {
17025 let destination = Point::new(row.0, 0);
17026 let autoscroll = Autoscroll::center();
17027
17028 self.unfold_ranges(&[destination..destination], false, false, cx);
17029 self.change_selections(Some(autoscroll), window, cx, |s| {
17030 s.select_ranges([destination..destination]);
17031 });
17032 }
17033 }
17034
17035 fn do_stage_or_unstage(
17036 &self,
17037 stage: bool,
17038 buffer_id: BufferId,
17039 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17040 cx: &mut App,
17041 ) -> Option<()> {
17042 let project = self.project.as_ref()?;
17043 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17044 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17045 let buffer_snapshot = buffer.read(cx).snapshot();
17046 let file_exists = buffer_snapshot
17047 .file()
17048 .is_some_and(|file| file.disk_state().exists());
17049 diff.update(cx, |diff, cx| {
17050 diff.stage_or_unstage_hunks(
17051 stage,
17052 &hunks
17053 .map(|hunk| buffer_diff::DiffHunk {
17054 buffer_range: hunk.buffer_range,
17055 diff_base_byte_range: hunk.diff_base_byte_range,
17056 secondary_status: hunk.secondary_status,
17057 range: Point::zero()..Point::zero(), // unused
17058 })
17059 .collect::<Vec<_>>(),
17060 &buffer_snapshot,
17061 file_exists,
17062 cx,
17063 )
17064 });
17065 None
17066 }
17067
17068 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17069 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17070 self.buffer
17071 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17072 }
17073
17074 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17075 self.buffer.update(cx, |buffer, cx| {
17076 let ranges = vec![Anchor::min()..Anchor::max()];
17077 if !buffer.all_diff_hunks_expanded()
17078 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17079 {
17080 buffer.collapse_diff_hunks(ranges, cx);
17081 true
17082 } else {
17083 false
17084 }
17085 })
17086 }
17087
17088 fn toggle_diff_hunks_in_ranges(
17089 &mut self,
17090 ranges: Vec<Range<Anchor>>,
17091 cx: &mut Context<Editor>,
17092 ) {
17093 self.buffer.update(cx, |buffer, cx| {
17094 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17095 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17096 })
17097 }
17098
17099 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17100 self.buffer.update(cx, |buffer, cx| {
17101 let snapshot = buffer.snapshot(cx);
17102 let excerpt_id = range.end.excerpt_id;
17103 let point_range = range.to_point(&snapshot);
17104 let expand = !buffer.single_hunk_is_expanded(range, cx);
17105 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17106 })
17107 }
17108
17109 pub(crate) fn apply_all_diff_hunks(
17110 &mut self,
17111 _: &ApplyAllDiffHunks,
17112 window: &mut Window,
17113 cx: &mut Context<Self>,
17114 ) {
17115 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17116
17117 let buffers = self.buffer.read(cx).all_buffers();
17118 for branch_buffer in buffers {
17119 branch_buffer.update(cx, |branch_buffer, cx| {
17120 branch_buffer.merge_into_base(Vec::new(), cx);
17121 });
17122 }
17123
17124 if let Some(project) = self.project.clone() {
17125 self.save(
17126 SaveOptions {
17127 format: true,
17128 autosave: false,
17129 },
17130 project,
17131 window,
17132 cx,
17133 )
17134 .detach_and_log_err(cx);
17135 }
17136 }
17137
17138 pub(crate) fn apply_selected_diff_hunks(
17139 &mut self,
17140 _: &ApplyDiffHunk,
17141 window: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) {
17144 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17145 let snapshot = self.snapshot(window, cx);
17146 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17147 let mut ranges_by_buffer = HashMap::default();
17148 self.transact(window, cx, |editor, _window, cx| {
17149 for hunk in hunks {
17150 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17151 ranges_by_buffer
17152 .entry(buffer.clone())
17153 .or_insert_with(Vec::new)
17154 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17155 }
17156 }
17157
17158 for (buffer, ranges) in ranges_by_buffer {
17159 buffer.update(cx, |buffer, cx| {
17160 buffer.merge_into_base(ranges, cx);
17161 });
17162 }
17163 });
17164
17165 if let Some(project) = self.project.clone() {
17166 self.save(
17167 SaveOptions {
17168 format: true,
17169 autosave: false,
17170 },
17171 project,
17172 window,
17173 cx,
17174 )
17175 .detach_and_log_err(cx);
17176 }
17177 }
17178
17179 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17180 if hovered != self.gutter_hovered {
17181 self.gutter_hovered = hovered;
17182 cx.notify();
17183 }
17184 }
17185
17186 pub fn insert_blocks(
17187 &mut self,
17188 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17189 autoscroll: Option<Autoscroll>,
17190 cx: &mut Context<Self>,
17191 ) -> Vec<CustomBlockId> {
17192 let blocks = self
17193 .display_map
17194 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17195 if let Some(autoscroll) = autoscroll {
17196 self.request_autoscroll(autoscroll, cx);
17197 }
17198 cx.notify();
17199 blocks
17200 }
17201
17202 pub fn resize_blocks(
17203 &mut self,
17204 heights: HashMap<CustomBlockId, u32>,
17205 autoscroll: Option<Autoscroll>,
17206 cx: &mut Context<Self>,
17207 ) {
17208 self.display_map
17209 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17210 if let Some(autoscroll) = autoscroll {
17211 self.request_autoscroll(autoscroll, cx);
17212 }
17213 cx.notify();
17214 }
17215
17216 pub fn replace_blocks(
17217 &mut self,
17218 renderers: HashMap<CustomBlockId, RenderBlock>,
17219 autoscroll: Option<Autoscroll>,
17220 cx: &mut Context<Self>,
17221 ) {
17222 self.display_map
17223 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17224 if let Some(autoscroll) = autoscroll {
17225 self.request_autoscroll(autoscroll, cx);
17226 }
17227 cx.notify();
17228 }
17229
17230 pub fn remove_blocks(
17231 &mut self,
17232 block_ids: HashSet<CustomBlockId>,
17233 autoscroll: Option<Autoscroll>,
17234 cx: &mut Context<Self>,
17235 ) {
17236 self.display_map.update(cx, |display_map, cx| {
17237 display_map.remove_blocks(block_ids, cx)
17238 });
17239 if let Some(autoscroll) = autoscroll {
17240 self.request_autoscroll(autoscroll, cx);
17241 }
17242 cx.notify();
17243 }
17244
17245 pub fn row_for_block(
17246 &self,
17247 block_id: CustomBlockId,
17248 cx: &mut Context<Self>,
17249 ) -> Option<DisplayRow> {
17250 self.display_map
17251 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17252 }
17253
17254 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17255 self.focused_block = Some(focused_block);
17256 }
17257
17258 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17259 self.focused_block.take()
17260 }
17261
17262 pub fn insert_creases(
17263 &mut self,
17264 creases: impl IntoIterator<Item = Crease<Anchor>>,
17265 cx: &mut Context<Self>,
17266 ) -> Vec<CreaseId> {
17267 self.display_map
17268 .update(cx, |map, cx| map.insert_creases(creases, cx))
17269 }
17270
17271 pub fn remove_creases(
17272 &mut self,
17273 ids: impl IntoIterator<Item = CreaseId>,
17274 cx: &mut Context<Self>,
17275 ) -> Vec<(CreaseId, Range<Anchor>)> {
17276 self.display_map
17277 .update(cx, |map, cx| map.remove_creases(ids, cx))
17278 }
17279
17280 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17281 self.display_map
17282 .update(cx, |map, cx| map.snapshot(cx))
17283 .longest_row()
17284 }
17285
17286 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17287 self.display_map
17288 .update(cx, |map, cx| map.snapshot(cx))
17289 .max_point()
17290 }
17291
17292 pub fn text(&self, cx: &App) -> String {
17293 self.buffer.read(cx).read(cx).text()
17294 }
17295
17296 pub fn is_empty(&self, cx: &App) -> bool {
17297 self.buffer.read(cx).read(cx).is_empty()
17298 }
17299
17300 pub fn text_option(&self, cx: &App) -> Option<String> {
17301 let text = self.text(cx);
17302 let text = text.trim();
17303
17304 if text.is_empty() {
17305 return None;
17306 }
17307
17308 Some(text.to_string())
17309 }
17310
17311 pub fn set_text(
17312 &mut self,
17313 text: impl Into<Arc<str>>,
17314 window: &mut Window,
17315 cx: &mut Context<Self>,
17316 ) {
17317 self.transact(window, cx, |this, _, cx| {
17318 this.buffer
17319 .read(cx)
17320 .as_singleton()
17321 .expect("you can only call set_text on editors for singleton buffers")
17322 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17323 });
17324 }
17325
17326 pub fn display_text(&self, cx: &mut App) -> String {
17327 self.display_map
17328 .update(cx, |map, cx| map.snapshot(cx))
17329 .text()
17330 }
17331
17332 fn create_minimap(
17333 &self,
17334 minimap_settings: MinimapSettings,
17335 window: &mut Window,
17336 cx: &mut Context<Self>,
17337 ) -> Option<Entity<Self>> {
17338 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17339 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17340 }
17341
17342 fn initialize_new_minimap(
17343 &self,
17344 minimap_settings: MinimapSettings,
17345 window: &mut Window,
17346 cx: &mut Context<Self>,
17347 ) -> Entity<Self> {
17348 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17349
17350 let mut minimap = Editor::new_internal(
17351 EditorMode::Minimap {
17352 parent: cx.weak_entity(),
17353 },
17354 self.buffer.clone(),
17355 self.project.clone(),
17356 Some(self.display_map.clone()),
17357 window,
17358 cx,
17359 );
17360 minimap.scroll_manager.clone_state(&self.scroll_manager);
17361 minimap.set_text_style_refinement(TextStyleRefinement {
17362 font_size: Some(MINIMAP_FONT_SIZE),
17363 font_weight: Some(MINIMAP_FONT_WEIGHT),
17364 ..Default::default()
17365 });
17366 minimap.update_minimap_configuration(minimap_settings, cx);
17367 cx.new(|_| minimap)
17368 }
17369
17370 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17371 let current_line_highlight = minimap_settings
17372 .current_line_highlight
17373 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17374 self.set_current_line_highlight(Some(current_line_highlight));
17375 }
17376
17377 pub fn minimap(&self) -> Option<&Entity<Self>> {
17378 self.minimap
17379 .as_ref()
17380 .filter(|_| self.minimap_visibility.visible())
17381 }
17382
17383 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17384 let mut wrap_guides = smallvec![];
17385
17386 if self.show_wrap_guides == Some(false) {
17387 return wrap_guides;
17388 }
17389
17390 let settings = self.buffer.read(cx).language_settings(cx);
17391 if settings.show_wrap_guides {
17392 match self.soft_wrap_mode(cx) {
17393 SoftWrap::Column(soft_wrap) => {
17394 wrap_guides.push((soft_wrap as usize, true));
17395 }
17396 SoftWrap::Bounded(soft_wrap) => {
17397 wrap_guides.push((soft_wrap as usize, true));
17398 }
17399 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17400 }
17401 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17402 }
17403
17404 wrap_guides
17405 }
17406
17407 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17408 let settings = self.buffer.read(cx).language_settings(cx);
17409 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17410 match mode {
17411 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17412 SoftWrap::None
17413 }
17414 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17415 language_settings::SoftWrap::PreferredLineLength => {
17416 SoftWrap::Column(settings.preferred_line_length)
17417 }
17418 language_settings::SoftWrap::Bounded => {
17419 SoftWrap::Bounded(settings.preferred_line_length)
17420 }
17421 }
17422 }
17423
17424 pub fn set_soft_wrap_mode(
17425 &mut self,
17426 mode: language_settings::SoftWrap,
17427
17428 cx: &mut Context<Self>,
17429 ) {
17430 self.soft_wrap_mode_override = Some(mode);
17431 cx.notify();
17432 }
17433
17434 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17435 self.hard_wrap = hard_wrap;
17436 cx.notify();
17437 }
17438
17439 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17440 self.text_style_refinement = Some(style);
17441 }
17442
17443 /// called by the Element so we know what style we were most recently rendered with.
17444 pub(crate) fn set_style(
17445 &mut self,
17446 style: EditorStyle,
17447 window: &mut Window,
17448 cx: &mut Context<Self>,
17449 ) {
17450 // We intentionally do not inform the display map about the minimap style
17451 // so that wrapping is not recalculated and stays consistent for the editor
17452 // and its linked minimap.
17453 if !self.mode.is_minimap() {
17454 let rem_size = window.rem_size();
17455 self.display_map.update(cx, |map, cx| {
17456 map.set_font(
17457 style.text.font(),
17458 style.text.font_size.to_pixels(rem_size),
17459 cx,
17460 )
17461 });
17462 }
17463 self.style = Some(style);
17464 }
17465
17466 pub fn style(&self) -> Option<&EditorStyle> {
17467 self.style.as_ref()
17468 }
17469
17470 // Called by the element. This method is not designed to be called outside of the editor
17471 // element's layout code because it does not notify when rewrapping is computed synchronously.
17472 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17473 self.display_map
17474 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17475 }
17476
17477 pub fn set_soft_wrap(&mut self) {
17478 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17479 }
17480
17481 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17482 if self.soft_wrap_mode_override.is_some() {
17483 self.soft_wrap_mode_override.take();
17484 } else {
17485 let soft_wrap = match self.soft_wrap_mode(cx) {
17486 SoftWrap::GitDiff => return,
17487 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17488 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17489 language_settings::SoftWrap::None
17490 }
17491 };
17492 self.soft_wrap_mode_override = Some(soft_wrap);
17493 }
17494 cx.notify();
17495 }
17496
17497 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17498 let Some(workspace) = self.workspace() else {
17499 return;
17500 };
17501 let fs = workspace.read(cx).app_state().fs.clone();
17502 let current_show = TabBarSettings::get_global(cx).show;
17503 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17504 setting.show = Some(!current_show);
17505 });
17506 }
17507
17508 pub fn toggle_indent_guides(
17509 &mut self,
17510 _: &ToggleIndentGuides,
17511 _: &mut Window,
17512 cx: &mut Context<Self>,
17513 ) {
17514 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17515 self.buffer
17516 .read(cx)
17517 .language_settings(cx)
17518 .indent_guides
17519 .enabled
17520 });
17521 self.show_indent_guides = Some(!currently_enabled);
17522 cx.notify();
17523 }
17524
17525 fn should_show_indent_guides(&self) -> Option<bool> {
17526 self.show_indent_guides
17527 }
17528
17529 pub fn toggle_line_numbers(
17530 &mut self,
17531 _: &ToggleLineNumbers,
17532 _: &mut Window,
17533 cx: &mut Context<Self>,
17534 ) {
17535 let mut editor_settings = EditorSettings::get_global(cx).clone();
17536 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17537 EditorSettings::override_global(editor_settings, cx);
17538 }
17539
17540 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17541 if let Some(show_line_numbers) = self.show_line_numbers {
17542 return show_line_numbers;
17543 }
17544 EditorSettings::get_global(cx).gutter.line_numbers
17545 }
17546
17547 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17548 self.use_relative_line_numbers
17549 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17550 }
17551
17552 pub fn toggle_relative_line_numbers(
17553 &mut self,
17554 _: &ToggleRelativeLineNumbers,
17555 _: &mut Window,
17556 cx: &mut Context<Self>,
17557 ) {
17558 let is_relative = self.should_use_relative_line_numbers(cx);
17559 self.set_relative_line_number(Some(!is_relative), cx)
17560 }
17561
17562 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17563 self.use_relative_line_numbers = is_relative;
17564 cx.notify();
17565 }
17566
17567 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17568 self.show_gutter = show_gutter;
17569 cx.notify();
17570 }
17571
17572 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17573 self.show_scrollbars = ScrollbarAxes {
17574 horizontal: show,
17575 vertical: show,
17576 };
17577 cx.notify();
17578 }
17579
17580 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17581 self.show_scrollbars.vertical = show;
17582 cx.notify();
17583 }
17584
17585 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17586 self.show_scrollbars.horizontal = show;
17587 cx.notify();
17588 }
17589
17590 pub fn set_minimap_visibility(
17591 &mut self,
17592 minimap_visibility: MinimapVisibility,
17593 window: &mut Window,
17594 cx: &mut Context<Self>,
17595 ) {
17596 if self.minimap_visibility != minimap_visibility {
17597 if minimap_visibility.visible() && self.minimap.is_none() {
17598 let minimap_settings = EditorSettings::get_global(cx).minimap;
17599 self.minimap =
17600 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17601 }
17602 self.minimap_visibility = minimap_visibility;
17603 cx.notify();
17604 }
17605 }
17606
17607 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17608 self.set_show_scrollbars(false, cx);
17609 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17610 }
17611
17612 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17613 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17614 }
17615
17616 /// Normally the text in full mode and auto height editors is padded on the
17617 /// left side by roughly half a character width for improved hit testing.
17618 ///
17619 /// Use this method to disable this for cases where this is not wanted (e.g.
17620 /// if you want to align the editor text with some other text above or below)
17621 /// or if you want to add this padding to single-line editors.
17622 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17623 self.offset_content = offset_content;
17624 cx.notify();
17625 }
17626
17627 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17628 self.show_line_numbers = Some(show_line_numbers);
17629 cx.notify();
17630 }
17631
17632 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17633 self.disable_expand_excerpt_buttons = true;
17634 cx.notify();
17635 }
17636
17637 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17638 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17639 cx.notify();
17640 }
17641
17642 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17643 self.show_code_actions = Some(show_code_actions);
17644 cx.notify();
17645 }
17646
17647 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17648 self.show_runnables = Some(show_runnables);
17649 cx.notify();
17650 }
17651
17652 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17653 self.show_breakpoints = Some(show_breakpoints);
17654 cx.notify();
17655 }
17656
17657 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17658 if self.display_map.read(cx).masked != masked {
17659 self.display_map.update(cx, |map, _| map.masked = masked);
17660 }
17661 cx.notify()
17662 }
17663
17664 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17665 self.show_wrap_guides = Some(show_wrap_guides);
17666 cx.notify();
17667 }
17668
17669 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17670 self.show_indent_guides = Some(show_indent_guides);
17671 cx.notify();
17672 }
17673
17674 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17675 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17676 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17677 if let Some(dir) = file.abs_path(cx).parent() {
17678 return Some(dir.to_owned());
17679 }
17680 }
17681
17682 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17683 return Some(project_path.path.to_path_buf());
17684 }
17685 }
17686
17687 None
17688 }
17689
17690 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17691 self.active_excerpt(cx)?
17692 .1
17693 .read(cx)
17694 .file()
17695 .and_then(|f| f.as_local())
17696 }
17697
17698 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17699 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17700 let buffer = buffer.read(cx);
17701 if let Some(project_path) = buffer.project_path(cx) {
17702 let project = self.project.as_ref()?.read(cx);
17703 project.absolute_path(&project_path, cx)
17704 } else {
17705 buffer
17706 .file()
17707 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17708 }
17709 })
17710 }
17711
17712 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17713 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17714 let project_path = buffer.read(cx).project_path(cx)?;
17715 let project = self.project.as_ref()?.read(cx);
17716 let entry = project.entry_for_path(&project_path, cx)?;
17717 let path = entry.path.to_path_buf();
17718 Some(path)
17719 })
17720 }
17721
17722 pub fn reveal_in_finder(
17723 &mut self,
17724 _: &RevealInFileManager,
17725 _window: &mut Window,
17726 cx: &mut Context<Self>,
17727 ) {
17728 if let Some(target) = self.target_file(cx) {
17729 cx.reveal_path(&target.abs_path(cx));
17730 }
17731 }
17732
17733 pub fn copy_path(
17734 &mut self,
17735 _: &zed_actions::workspace::CopyPath,
17736 _window: &mut Window,
17737 cx: &mut Context<Self>,
17738 ) {
17739 if let Some(path) = self.target_file_abs_path(cx) {
17740 if let Some(path) = path.to_str() {
17741 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17742 }
17743 }
17744 }
17745
17746 pub fn copy_relative_path(
17747 &mut self,
17748 _: &zed_actions::workspace::CopyRelativePath,
17749 _window: &mut Window,
17750 cx: &mut Context<Self>,
17751 ) {
17752 if let Some(path) = self.target_file_path(cx) {
17753 if let Some(path) = path.to_str() {
17754 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17755 }
17756 }
17757 }
17758
17759 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17760 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17761 buffer.read(cx).project_path(cx)
17762 } else {
17763 None
17764 }
17765 }
17766
17767 // Returns true if the editor handled a go-to-line request
17768 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17769 maybe!({
17770 let breakpoint_store = self.breakpoint_store.as_ref()?;
17771
17772 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17773 else {
17774 self.clear_row_highlights::<ActiveDebugLine>();
17775 return None;
17776 };
17777
17778 let position = active_stack_frame.position;
17779 let buffer_id = position.buffer_id?;
17780 let snapshot = self
17781 .project
17782 .as_ref()?
17783 .read(cx)
17784 .buffer_for_id(buffer_id, cx)?
17785 .read(cx)
17786 .snapshot();
17787
17788 let mut handled = false;
17789 for (id, ExcerptRange { context, .. }) in
17790 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17791 {
17792 if context.start.cmp(&position, &snapshot).is_ge()
17793 || context.end.cmp(&position, &snapshot).is_lt()
17794 {
17795 continue;
17796 }
17797 let snapshot = self.buffer.read(cx).snapshot(cx);
17798 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17799
17800 handled = true;
17801 self.clear_row_highlights::<ActiveDebugLine>();
17802
17803 self.go_to_line::<ActiveDebugLine>(
17804 multibuffer_anchor,
17805 Some(cx.theme().colors().editor_debugger_active_line_background),
17806 window,
17807 cx,
17808 );
17809
17810 cx.notify();
17811 }
17812
17813 handled.then_some(())
17814 })
17815 .is_some()
17816 }
17817
17818 pub fn copy_file_name_without_extension(
17819 &mut self,
17820 _: &CopyFileNameWithoutExtension,
17821 _: &mut Window,
17822 cx: &mut Context<Self>,
17823 ) {
17824 if let Some(file) = self.target_file(cx) {
17825 if let Some(file_stem) = file.path().file_stem() {
17826 if let Some(name) = file_stem.to_str() {
17827 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17828 }
17829 }
17830 }
17831 }
17832
17833 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17834 if let Some(file) = self.target_file(cx) {
17835 if let Some(file_name) = file.path().file_name() {
17836 if let Some(name) = file_name.to_str() {
17837 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17838 }
17839 }
17840 }
17841 }
17842
17843 pub fn toggle_git_blame(
17844 &mut self,
17845 _: &::git::Blame,
17846 window: &mut Window,
17847 cx: &mut Context<Self>,
17848 ) {
17849 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17850
17851 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17852 self.start_git_blame(true, window, cx);
17853 }
17854
17855 cx.notify();
17856 }
17857
17858 pub fn toggle_git_blame_inline(
17859 &mut self,
17860 _: &ToggleGitBlameInline,
17861 window: &mut Window,
17862 cx: &mut Context<Self>,
17863 ) {
17864 self.toggle_git_blame_inline_internal(true, window, cx);
17865 cx.notify();
17866 }
17867
17868 pub fn open_git_blame_commit(
17869 &mut self,
17870 _: &OpenGitBlameCommit,
17871 window: &mut Window,
17872 cx: &mut Context<Self>,
17873 ) {
17874 self.open_git_blame_commit_internal(window, cx);
17875 }
17876
17877 fn open_git_blame_commit_internal(
17878 &mut self,
17879 window: &mut Window,
17880 cx: &mut Context<Self>,
17881 ) -> Option<()> {
17882 let blame = self.blame.as_ref()?;
17883 let snapshot = self.snapshot(window, cx);
17884 let cursor = self.selections.newest::<Point>(cx).head();
17885 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17886 let blame_entry = blame
17887 .update(cx, |blame, cx| {
17888 blame
17889 .blame_for_rows(
17890 &[RowInfo {
17891 buffer_id: Some(buffer.remote_id()),
17892 buffer_row: Some(point.row),
17893 ..Default::default()
17894 }],
17895 cx,
17896 )
17897 .next()
17898 })
17899 .flatten()?;
17900 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17901 let repo = blame.read(cx).repository(cx)?;
17902 let workspace = self.workspace()?.downgrade();
17903 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17904 None
17905 }
17906
17907 pub fn git_blame_inline_enabled(&self) -> bool {
17908 self.git_blame_inline_enabled
17909 }
17910
17911 pub fn toggle_selection_menu(
17912 &mut self,
17913 _: &ToggleSelectionMenu,
17914 _: &mut Window,
17915 cx: &mut Context<Self>,
17916 ) {
17917 self.show_selection_menu = self
17918 .show_selection_menu
17919 .map(|show_selections_menu| !show_selections_menu)
17920 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17921
17922 cx.notify();
17923 }
17924
17925 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17926 self.show_selection_menu
17927 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17928 }
17929
17930 fn start_git_blame(
17931 &mut self,
17932 user_triggered: bool,
17933 window: &mut Window,
17934 cx: &mut Context<Self>,
17935 ) {
17936 if let Some(project) = self.project.as_ref() {
17937 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17938 return;
17939 };
17940
17941 if buffer.read(cx).file().is_none() {
17942 return;
17943 }
17944
17945 let focused = self.focus_handle(cx).contains_focused(window, cx);
17946
17947 let project = project.clone();
17948 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17949 self.blame_subscription =
17950 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17951 self.blame = Some(blame);
17952 }
17953 }
17954
17955 fn toggle_git_blame_inline_internal(
17956 &mut self,
17957 user_triggered: bool,
17958 window: &mut Window,
17959 cx: &mut Context<Self>,
17960 ) {
17961 if self.git_blame_inline_enabled {
17962 self.git_blame_inline_enabled = false;
17963 self.show_git_blame_inline = false;
17964 self.show_git_blame_inline_delay_task.take();
17965 } else {
17966 self.git_blame_inline_enabled = true;
17967 self.start_git_blame_inline(user_triggered, window, cx);
17968 }
17969
17970 cx.notify();
17971 }
17972
17973 fn start_git_blame_inline(
17974 &mut self,
17975 user_triggered: bool,
17976 window: &mut Window,
17977 cx: &mut Context<Self>,
17978 ) {
17979 self.start_git_blame(user_triggered, window, cx);
17980
17981 if ProjectSettings::get_global(cx)
17982 .git
17983 .inline_blame_delay()
17984 .is_some()
17985 {
17986 self.start_inline_blame_timer(window, cx);
17987 } else {
17988 self.show_git_blame_inline = true
17989 }
17990 }
17991
17992 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17993 self.blame.as_ref()
17994 }
17995
17996 pub fn show_git_blame_gutter(&self) -> bool {
17997 self.show_git_blame_gutter
17998 }
17999
18000 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18001 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18002 }
18003
18004 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18005 self.show_git_blame_inline
18006 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18007 && !self.newest_selection_head_on_empty_line(cx)
18008 && self.has_blame_entries(cx)
18009 }
18010
18011 fn has_blame_entries(&self, cx: &App) -> bool {
18012 self.blame()
18013 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18014 }
18015
18016 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18017 let cursor_anchor = self.selections.newest_anchor().head();
18018
18019 let snapshot = self.buffer.read(cx).snapshot(cx);
18020 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18021
18022 snapshot.line_len(buffer_row) == 0
18023 }
18024
18025 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18026 let buffer_and_selection = maybe!({
18027 let selection = self.selections.newest::<Point>(cx);
18028 let selection_range = selection.range();
18029
18030 let multi_buffer = self.buffer().read(cx);
18031 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18032 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18033
18034 let (buffer, range, _) = if selection.reversed {
18035 buffer_ranges.first()
18036 } else {
18037 buffer_ranges.last()
18038 }?;
18039
18040 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18041 ..text::ToPoint::to_point(&range.end, &buffer).row;
18042 Some((
18043 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18044 selection,
18045 ))
18046 });
18047
18048 let Some((buffer, selection)) = buffer_and_selection else {
18049 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18050 };
18051
18052 let Some(project) = self.project.as_ref() else {
18053 return Task::ready(Err(anyhow!("editor does not have project")));
18054 };
18055
18056 project.update(cx, |project, cx| {
18057 project.get_permalink_to_line(&buffer, selection, cx)
18058 })
18059 }
18060
18061 pub fn copy_permalink_to_line(
18062 &mut self,
18063 _: &CopyPermalinkToLine,
18064 window: &mut Window,
18065 cx: &mut Context<Self>,
18066 ) {
18067 let permalink_task = self.get_permalink_to_line(cx);
18068 let workspace = self.workspace();
18069
18070 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18071 Ok(permalink) => {
18072 cx.update(|_, cx| {
18073 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18074 })
18075 .ok();
18076 }
18077 Err(err) => {
18078 let message = format!("Failed to copy permalink: {err}");
18079
18080 anyhow::Result::<()>::Err(err).log_err();
18081
18082 if let Some(workspace) = workspace {
18083 workspace
18084 .update_in(cx, |workspace, _, cx| {
18085 struct CopyPermalinkToLine;
18086
18087 workspace.show_toast(
18088 Toast::new(
18089 NotificationId::unique::<CopyPermalinkToLine>(),
18090 message,
18091 ),
18092 cx,
18093 )
18094 })
18095 .ok();
18096 }
18097 }
18098 })
18099 .detach();
18100 }
18101
18102 pub fn copy_file_location(
18103 &mut self,
18104 _: &CopyFileLocation,
18105 _: &mut Window,
18106 cx: &mut Context<Self>,
18107 ) {
18108 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18109 if let Some(file) = self.target_file(cx) {
18110 if let Some(path) = file.path().to_str() {
18111 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18112 }
18113 }
18114 }
18115
18116 pub fn open_permalink_to_line(
18117 &mut self,
18118 _: &OpenPermalinkToLine,
18119 window: &mut Window,
18120 cx: &mut Context<Self>,
18121 ) {
18122 let permalink_task = self.get_permalink_to_line(cx);
18123 let workspace = self.workspace();
18124
18125 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18126 Ok(permalink) => {
18127 cx.update(|_, cx| {
18128 cx.open_url(permalink.as_ref());
18129 })
18130 .ok();
18131 }
18132 Err(err) => {
18133 let message = format!("Failed to open permalink: {err}");
18134
18135 anyhow::Result::<()>::Err(err).log_err();
18136
18137 if let Some(workspace) = workspace {
18138 workspace
18139 .update(cx, |workspace, cx| {
18140 struct OpenPermalinkToLine;
18141
18142 workspace.show_toast(
18143 Toast::new(
18144 NotificationId::unique::<OpenPermalinkToLine>(),
18145 message,
18146 ),
18147 cx,
18148 )
18149 })
18150 .ok();
18151 }
18152 }
18153 })
18154 .detach();
18155 }
18156
18157 pub fn insert_uuid_v4(
18158 &mut self,
18159 _: &InsertUuidV4,
18160 window: &mut Window,
18161 cx: &mut Context<Self>,
18162 ) {
18163 self.insert_uuid(UuidVersion::V4, window, cx);
18164 }
18165
18166 pub fn insert_uuid_v7(
18167 &mut self,
18168 _: &InsertUuidV7,
18169 window: &mut Window,
18170 cx: &mut Context<Self>,
18171 ) {
18172 self.insert_uuid(UuidVersion::V7, window, cx);
18173 }
18174
18175 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18176 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18177 self.transact(window, cx, |this, window, cx| {
18178 let edits = this
18179 .selections
18180 .all::<Point>(cx)
18181 .into_iter()
18182 .map(|selection| {
18183 let uuid = match version {
18184 UuidVersion::V4 => uuid::Uuid::new_v4(),
18185 UuidVersion::V7 => uuid::Uuid::now_v7(),
18186 };
18187
18188 (selection.range(), uuid.to_string())
18189 });
18190 this.edit(edits, cx);
18191 this.refresh_inline_completion(true, false, window, cx);
18192 });
18193 }
18194
18195 pub fn open_selections_in_multibuffer(
18196 &mut self,
18197 _: &OpenSelectionsInMultibuffer,
18198 window: &mut Window,
18199 cx: &mut Context<Self>,
18200 ) {
18201 let multibuffer = self.buffer.read(cx);
18202
18203 let Some(buffer) = multibuffer.as_singleton() else {
18204 return;
18205 };
18206
18207 let Some(workspace) = self.workspace() else {
18208 return;
18209 };
18210
18211 let locations = self
18212 .selections
18213 .disjoint_anchors()
18214 .iter()
18215 .map(|range| Location {
18216 buffer: buffer.clone(),
18217 range: range.start.text_anchor..range.end.text_anchor,
18218 })
18219 .collect::<Vec<_>>();
18220
18221 let title = multibuffer.title(cx).to_string();
18222
18223 cx.spawn_in(window, async move |_, cx| {
18224 workspace.update_in(cx, |workspace, window, cx| {
18225 Self::open_locations_in_multibuffer(
18226 workspace,
18227 locations,
18228 format!("Selections for '{title}'"),
18229 false,
18230 MultibufferSelectionMode::All,
18231 window,
18232 cx,
18233 );
18234 })
18235 })
18236 .detach();
18237 }
18238
18239 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18240 /// last highlight added will be used.
18241 ///
18242 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18243 pub fn highlight_rows<T: 'static>(
18244 &mut self,
18245 range: Range<Anchor>,
18246 color: Hsla,
18247 options: RowHighlightOptions,
18248 cx: &mut Context<Self>,
18249 ) {
18250 let snapshot = self.buffer().read(cx).snapshot(cx);
18251 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18252 let ix = row_highlights.binary_search_by(|highlight| {
18253 Ordering::Equal
18254 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18255 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18256 });
18257
18258 if let Err(mut ix) = ix {
18259 let index = post_inc(&mut self.highlight_order);
18260
18261 // If this range intersects with the preceding highlight, then merge it with
18262 // the preceding highlight. Otherwise insert a new highlight.
18263 let mut merged = false;
18264 if ix > 0 {
18265 let prev_highlight = &mut row_highlights[ix - 1];
18266 if prev_highlight
18267 .range
18268 .end
18269 .cmp(&range.start, &snapshot)
18270 .is_ge()
18271 {
18272 ix -= 1;
18273 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18274 prev_highlight.range.end = range.end;
18275 }
18276 merged = true;
18277 prev_highlight.index = index;
18278 prev_highlight.color = color;
18279 prev_highlight.options = options;
18280 }
18281 }
18282
18283 if !merged {
18284 row_highlights.insert(
18285 ix,
18286 RowHighlight {
18287 range: range.clone(),
18288 index,
18289 color,
18290 options,
18291 type_id: TypeId::of::<T>(),
18292 },
18293 );
18294 }
18295
18296 // If any of the following highlights intersect with this one, merge them.
18297 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18298 let highlight = &row_highlights[ix];
18299 if next_highlight
18300 .range
18301 .start
18302 .cmp(&highlight.range.end, &snapshot)
18303 .is_le()
18304 {
18305 if next_highlight
18306 .range
18307 .end
18308 .cmp(&highlight.range.end, &snapshot)
18309 .is_gt()
18310 {
18311 row_highlights[ix].range.end = next_highlight.range.end;
18312 }
18313 row_highlights.remove(ix + 1);
18314 } else {
18315 break;
18316 }
18317 }
18318 }
18319 }
18320
18321 /// Remove any highlighted row ranges of the given type that intersect the
18322 /// given ranges.
18323 pub fn remove_highlighted_rows<T: 'static>(
18324 &mut self,
18325 ranges_to_remove: Vec<Range<Anchor>>,
18326 cx: &mut Context<Self>,
18327 ) {
18328 let snapshot = self.buffer().read(cx).snapshot(cx);
18329 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18330 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18331 row_highlights.retain(|highlight| {
18332 while let Some(range_to_remove) = ranges_to_remove.peek() {
18333 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18334 Ordering::Less | Ordering::Equal => {
18335 ranges_to_remove.next();
18336 }
18337 Ordering::Greater => {
18338 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18339 Ordering::Less | Ordering::Equal => {
18340 return false;
18341 }
18342 Ordering::Greater => break,
18343 }
18344 }
18345 }
18346 }
18347
18348 true
18349 })
18350 }
18351
18352 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18353 pub fn clear_row_highlights<T: 'static>(&mut self) {
18354 self.highlighted_rows.remove(&TypeId::of::<T>());
18355 }
18356
18357 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18358 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18359 self.highlighted_rows
18360 .get(&TypeId::of::<T>())
18361 .map_or(&[] as &[_], |vec| vec.as_slice())
18362 .iter()
18363 .map(|highlight| (highlight.range.clone(), highlight.color))
18364 }
18365
18366 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18367 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18368 /// Allows to ignore certain kinds of highlights.
18369 pub fn highlighted_display_rows(
18370 &self,
18371 window: &mut Window,
18372 cx: &mut App,
18373 ) -> BTreeMap<DisplayRow, LineHighlight> {
18374 let snapshot = self.snapshot(window, cx);
18375 let mut used_highlight_orders = HashMap::default();
18376 self.highlighted_rows
18377 .iter()
18378 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18379 .fold(
18380 BTreeMap::<DisplayRow, LineHighlight>::new(),
18381 |mut unique_rows, highlight| {
18382 let start = highlight.range.start.to_display_point(&snapshot);
18383 let end = highlight.range.end.to_display_point(&snapshot);
18384 let start_row = start.row().0;
18385 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18386 && end.column() == 0
18387 {
18388 end.row().0.saturating_sub(1)
18389 } else {
18390 end.row().0
18391 };
18392 for row in start_row..=end_row {
18393 let used_index =
18394 used_highlight_orders.entry(row).or_insert(highlight.index);
18395 if highlight.index >= *used_index {
18396 *used_index = highlight.index;
18397 unique_rows.insert(
18398 DisplayRow(row),
18399 LineHighlight {
18400 include_gutter: highlight.options.include_gutter,
18401 border: None,
18402 background: highlight.color.into(),
18403 type_id: Some(highlight.type_id),
18404 },
18405 );
18406 }
18407 }
18408 unique_rows
18409 },
18410 )
18411 }
18412
18413 pub fn highlighted_display_row_for_autoscroll(
18414 &self,
18415 snapshot: &DisplaySnapshot,
18416 ) -> Option<DisplayRow> {
18417 self.highlighted_rows
18418 .values()
18419 .flat_map(|highlighted_rows| highlighted_rows.iter())
18420 .filter_map(|highlight| {
18421 if highlight.options.autoscroll {
18422 Some(highlight.range.start.to_display_point(snapshot).row())
18423 } else {
18424 None
18425 }
18426 })
18427 .min()
18428 }
18429
18430 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18431 self.highlight_background::<SearchWithinRange>(
18432 ranges,
18433 |colors| colors.editor_document_highlight_read_background,
18434 cx,
18435 )
18436 }
18437
18438 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18439 self.breadcrumb_header = Some(new_header);
18440 }
18441
18442 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18443 self.clear_background_highlights::<SearchWithinRange>(cx);
18444 }
18445
18446 pub fn highlight_background<T: 'static>(
18447 &mut self,
18448 ranges: &[Range<Anchor>],
18449 color_fetcher: fn(&ThemeColors) -> Hsla,
18450 cx: &mut Context<Self>,
18451 ) {
18452 self.background_highlights
18453 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18454 self.scrollbar_marker_state.dirty = true;
18455 cx.notify();
18456 }
18457
18458 pub fn clear_background_highlights<T: 'static>(
18459 &mut self,
18460 cx: &mut Context<Self>,
18461 ) -> Option<BackgroundHighlight> {
18462 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18463 if !text_highlights.1.is_empty() {
18464 self.scrollbar_marker_state.dirty = true;
18465 cx.notify();
18466 }
18467 Some(text_highlights)
18468 }
18469
18470 pub fn highlight_gutter<T: 'static>(
18471 &mut self,
18472 ranges: impl Into<Vec<Range<Anchor>>>,
18473 color_fetcher: fn(&App) -> Hsla,
18474 cx: &mut Context<Self>,
18475 ) {
18476 self.gutter_highlights
18477 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18478 cx.notify();
18479 }
18480
18481 pub fn clear_gutter_highlights<T: 'static>(
18482 &mut self,
18483 cx: &mut Context<Self>,
18484 ) -> Option<GutterHighlight> {
18485 cx.notify();
18486 self.gutter_highlights.remove(&TypeId::of::<T>())
18487 }
18488
18489 pub fn insert_gutter_highlight<T: 'static>(
18490 &mut self,
18491 range: Range<Anchor>,
18492 color_fetcher: fn(&App) -> Hsla,
18493 cx: &mut Context<Self>,
18494 ) {
18495 let snapshot = self.buffer().read(cx).snapshot(cx);
18496 let mut highlights = self
18497 .gutter_highlights
18498 .remove(&TypeId::of::<T>())
18499 .map(|(_, highlights)| highlights)
18500 .unwrap_or_default();
18501 let ix = highlights.binary_search_by(|highlight| {
18502 Ordering::Equal
18503 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18504 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18505 });
18506 if let Err(ix) = ix {
18507 highlights.insert(ix, range);
18508 }
18509 self.gutter_highlights
18510 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18511 }
18512
18513 pub fn remove_gutter_highlights<T: 'static>(
18514 &mut self,
18515 ranges_to_remove: Vec<Range<Anchor>>,
18516 cx: &mut Context<Self>,
18517 ) {
18518 let snapshot = self.buffer().read(cx).snapshot(cx);
18519 let Some((color_fetcher, mut gutter_highlights)) =
18520 self.gutter_highlights.remove(&TypeId::of::<T>())
18521 else {
18522 return;
18523 };
18524 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18525 gutter_highlights.retain(|highlight| {
18526 while let Some(range_to_remove) = ranges_to_remove.peek() {
18527 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18528 Ordering::Less | Ordering::Equal => {
18529 ranges_to_remove.next();
18530 }
18531 Ordering::Greater => {
18532 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18533 Ordering::Less | Ordering::Equal => {
18534 return false;
18535 }
18536 Ordering::Greater => break,
18537 }
18538 }
18539 }
18540 }
18541
18542 true
18543 });
18544 self.gutter_highlights
18545 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18546 }
18547
18548 #[cfg(feature = "test-support")]
18549 pub fn all_text_background_highlights(
18550 &self,
18551 window: &mut Window,
18552 cx: &mut Context<Self>,
18553 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18554 let snapshot = self.snapshot(window, cx);
18555 let buffer = &snapshot.buffer_snapshot;
18556 let start = buffer.anchor_before(0);
18557 let end = buffer.anchor_after(buffer.len());
18558 let theme = cx.theme().colors();
18559 self.background_highlights_in_range(start..end, &snapshot, theme)
18560 }
18561
18562 #[cfg(feature = "test-support")]
18563 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18564 let snapshot = self.buffer().read(cx).snapshot(cx);
18565
18566 let highlights = self
18567 .background_highlights
18568 .get(&TypeId::of::<items::BufferSearchHighlights>());
18569
18570 if let Some((_color, ranges)) = highlights {
18571 ranges
18572 .iter()
18573 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18574 .collect_vec()
18575 } else {
18576 vec![]
18577 }
18578 }
18579
18580 fn document_highlights_for_position<'a>(
18581 &'a self,
18582 position: Anchor,
18583 buffer: &'a MultiBufferSnapshot,
18584 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18585 let read_highlights = self
18586 .background_highlights
18587 .get(&TypeId::of::<DocumentHighlightRead>())
18588 .map(|h| &h.1);
18589 let write_highlights = self
18590 .background_highlights
18591 .get(&TypeId::of::<DocumentHighlightWrite>())
18592 .map(|h| &h.1);
18593 let left_position = position.bias_left(buffer);
18594 let right_position = position.bias_right(buffer);
18595 read_highlights
18596 .into_iter()
18597 .chain(write_highlights)
18598 .flat_map(move |ranges| {
18599 let start_ix = match ranges.binary_search_by(|probe| {
18600 let cmp = probe.end.cmp(&left_position, buffer);
18601 if cmp.is_ge() {
18602 Ordering::Greater
18603 } else {
18604 Ordering::Less
18605 }
18606 }) {
18607 Ok(i) | Err(i) => i,
18608 };
18609
18610 ranges[start_ix..]
18611 .iter()
18612 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18613 })
18614 }
18615
18616 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18617 self.background_highlights
18618 .get(&TypeId::of::<T>())
18619 .map_or(false, |(_, highlights)| !highlights.is_empty())
18620 }
18621
18622 pub fn background_highlights_in_range(
18623 &self,
18624 search_range: Range<Anchor>,
18625 display_snapshot: &DisplaySnapshot,
18626 theme: &ThemeColors,
18627 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18628 let mut results = Vec::new();
18629 for (color_fetcher, ranges) in self.background_highlights.values() {
18630 let color = color_fetcher(theme);
18631 let start_ix = match ranges.binary_search_by(|probe| {
18632 let cmp = probe
18633 .end
18634 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18635 if cmp.is_gt() {
18636 Ordering::Greater
18637 } else {
18638 Ordering::Less
18639 }
18640 }) {
18641 Ok(i) | Err(i) => i,
18642 };
18643 for range in &ranges[start_ix..] {
18644 if range
18645 .start
18646 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18647 .is_ge()
18648 {
18649 break;
18650 }
18651
18652 let start = range.start.to_display_point(display_snapshot);
18653 let end = range.end.to_display_point(display_snapshot);
18654 results.push((start..end, color))
18655 }
18656 }
18657 results
18658 }
18659
18660 pub fn background_highlight_row_ranges<T: 'static>(
18661 &self,
18662 search_range: Range<Anchor>,
18663 display_snapshot: &DisplaySnapshot,
18664 count: usize,
18665 ) -> Vec<RangeInclusive<DisplayPoint>> {
18666 let mut results = Vec::new();
18667 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18668 return vec![];
18669 };
18670
18671 let start_ix = match ranges.binary_search_by(|probe| {
18672 let cmp = probe
18673 .end
18674 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18675 if cmp.is_gt() {
18676 Ordering::Greater
18677 } else {
18678 Ordering::Less
18679 }
18680 }) {
18681 Ok(i) | Err(i) => i,
18682 };
18683 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18684 if let (Some(start_display), Some(end_display)) = (start, end) {
18685 results.push(
18686 start_display.to_display_point(display_snapshot)
18687 ..=end_display.to_display_point(display_snapshot),
18688 );
18689 }
18690 };
18691 let mut start_row: Option<Point> = None;
18692 let mut end_row: Option<Point> = None;
18693 if ranges.len() > count {
18694 return Vec::new();
18695 }
18696 for range in &ranges[start_ix..] {
18697 if range
18698 .start
18699 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18700 .is_ge()
18701 {
18702 break;
18703 }
18704 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18705 if let Some(current_row) = &end_row {
18706 if end.row == current_row.row {
18707 continue;
18708 }
18709 }
18710 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18711 if start_row.is_none() {
18712 assert_eq!(end_row, None);
18713 start_row = Some(start);
18714 end_row = Some(end);
18715 continue;
18716 }
18717 if let Some(current_end) = end_row.as_mut() {
18718 if start.row > current_end.row + 1 {
18719 push_region(start_row, end_row);
18720 start_row = Some(start);
18721 end_row = Some(end);
18722 } else {
18723 // Merge two hunks.
18724 *current_end = end;
18725 }
18726 } else {
18727 unreachable!();
18728 }
18729 }
18730 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18731 push_region(start_row, end_row);
18732 results
18733 }
18734
18735 pub fn gutter_highlights_in_range(
18736 &self,
18737 search_range: Range<Anchor>,
18738 display_snapshot: &DisplaySnapshot,
18739 cx: &App,
18740 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18741 let mut results = Vec::new();
18742 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18743 let color = color_fetcher(cx);
18744 let start_ix = match ranges.binary_search_by(|probe| {
18745 let cmp = probe
18746 .end
18747 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18748 if cmp.is_gt() {
18749 Ordering::Greater
18750 } else {
18751 Ordering::Less
18752 }
18753 }) {
18754 Ok(i) | Err(i) => i,
18755 };
18756 for range in &ranges[start_ix..] {
18757 if range
18758 .start
18759 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18760 .is_ge()
18761 {
18762 break;
18763 }
18764
18765 let start = range.start.to_display_point(display_snapshot);
18766 let end = range.end.to_display_point(display_snapshot);
18767 results.push((start..end, color))
18768 }
18769 }
18770 results
18771 }
18772
18773 /// Get the text ranges corresponding to the redaction query
18774 pub fn redacted_ranges(
18775 &self,
18776 search_range: Range<Anchor>,
18777 display_snapshot: &DisplaySnapshot,
18778 cx: &App,
18779 ) -> Vec<Range<DisplayPoint>> {
18780 display_snapshot
18781 .buffer_snapshot
18782 .redacted_ranges(search_range, |file| {
18783 if let Some(file) = file {
18784 file.is_private()
18785 && EditorSettings::get(
18786 Some(SettingsLocation {
18787 worktree_id: file.worktree_id(cx),
18788 path: file.path().as_ref(),
18789 }),
18790 cx,
18791 )
18792 .redact_private_values
18793 } else {
18794 false
18795 }
18796 })
18797 .map(|range| {
18798 range.start.to_display_point(display_snapshot)
18799 ..range.end.to_display_point(display_snapshot)
18800 })
18801 .collect()
18802 }
18803
18804 pub fn highlight_text<T: 'static>(
18805 &mut self,
18806 ranges: Vec<Range<Anchor>>,
18807 style: HighlightStyle,
18808 cx: &mut Context<Self>,
18809 ) {
18810 self.display_map.update(cx, |map, _| {
18811 map.highlight_text(TypeId::of::<T>(), ranges, style)
18812 });
18813 cx.notify();
18814 }
18815
18816 pub(crate) fn highlight_inlays<T: 'static>(
18817 &mut self,
18818 highlights: Vec<InlayHighlight>,
18819 style: HighlightStyle,
18820 cx: &mut Context<Self>,
18821 ) {
18822 self.display_map.update(cx, |map, _| {
18823 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18824 });
18825 cx.notify();
18826 }
18827
18828 pub fn text_highlights<'a, T: 'static>(
18829 &'a self,
18830 cx: &'a App,
18831 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18832 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18833 }
18834
18835 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18836 let cleared = self
18837 .display_map
18838 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18839 if cleared {
18840 cx.notify();
18841 }
18842 }
18843
18844 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18845 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18846 && self.focus_handle.is_focused(window)
18847 }
18848
18849 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18850 self.show_cursor_when_unfocused = is_enabled;
18851 cx.notify();
18852 }
18853
18854 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18855 cx.notify();
18856 }
18857
18858 fn on_debug_session_event(
18859 &mut self,
18860 _session: Entity<Session>,
18861 event: &SessionEvent,
18862 cx: &mut Context<Self>,
18863 ) {
18864 match event {
18865 SessionEvent::InvalidateInlineValue => {
18866 self.refresh_inline_values(cx);
18867 }
18868 _ => {}
18869 }
18870 }
18871
18872 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18873 let Some(project) = self.project.clone() else {
18874 return;
18875 };
18876
18877 if !self.inline_value_cache.enabled {
18878 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18879 self.splice_inlays(&inlays, Vec::new(), cx);
18880 return;
18881 }
18882
18883 let current_execution_position = self
18884 .highlighted_rows
18885 .get(&TypeId::of::<ActiveDebugLine>())
18886 .and_then(|lines| lines.last().map(|line| line.range.start));
18887
18888 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18889 let inline_values = editor
18890 .update(cx, |editor, cx| {
18891 let Some(current_execution_position) = current_execution_position else {
18892 return Some(Task::ready(Ok(Vec::new())));
18893 };
18894
18895 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18896 let snapshot = buffer.snapshot(cx);
18897
18898 let excerpt = snapshot.excerpt_containing(
18899 current_execution_position..current_execution_position,
18900 )?;
18901
18902 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18903 })?;
18904
18905 let range =
18906 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18907
18908 project.inline_values(buffer, range, cx)
18909 })
18910 .ok()
18911 .flatten()?
18912 .await
18913 .context("refreshing debugger inlays")
18914 .log_err()?;
18915
18916 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18917
18918 for (buffer_id, inline_value) in inline_values
18919 .into_iter()
18920 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18921 {
18922 buffer_inline_values
18923 .entry(buffer_id)
18924 .or_default()
18925 .push(inline_value);
18926 }
18927
18928 editor
18929 .update(cx, |editor, cx| {
18930 let snapshot = editor.buffer.read(cx).snapshot(cx);
18931 let mut new_inlays = Vec::default();
18932
18933 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18934 let buffer_id = buffer_snapshot.remote_id();
18935 buffer_inline_values
18936 .get(&buffer_id)
18937 .into_iter()
18938 .flatten()
18939 .for_each(|hint| {
18940 let inlay = Inlay::debugger_hint(
18941 post_inc(&mut editor.next_inlay_id),
18942 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18943 hint.text(),
18944 );
18945
18946 new_inlays.push(inlay);
18947 });
18948 }
18949
18950 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18951 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18952
18953 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18954 })
18955 .ok()?;
18956 Some(())
18957 });
18958 }
18959
18960 fn on_buffer_event(
18961 &mut self,
18962 multibuffer: &Entity<MultiBuffer>,
18963 event: &multi_buffer::Event,
18964 window: &mut Window,
18965 cx: &mut Context<Self>,
18966 ) {
18967 match event {
18968 multi_buffer::Event::Edited {
18969 singleton_buffer_edited,
18970 edited_buffer,
18971 } => {
18972 self.scrollbar_marker_state.dirty = true;
18973 self.active_indent_guides_state.dirty = true;
18974 self.refresh_active_diagnostics(cx);
18975 self.refresh_code_actions(window, cx);
18976 self.refresh_selected_text_highlights(true, window, cx);
18977 refresh_matching_bracket_highlights(self, window, cx);
18978 if self.has_active_inline_completion() {
18979 self.update_visible_inline_completion(window, cx);
18980 }
18981 if let Some(project) = self.project.as_ref() {
18982 if let Some(edited_buffer) = edited_buffer {
18983 project.update(cx, |project, cx| {
18984 self.registered_buffers
18985 .entry(edited_buffer.read(cx).remote_id())
18986 .or_insert_with(|| {
18987 project
18988 .register_buffer_with_language_servers(&edited_buffer, cx)
18989 });
18990 });
18991 if edited_buffer.read(cx).file().is_some() {
18992 self.pull_diagnostics(
18993 Some(edited_buffer.read(cx).remote_id()),
18994 window,
18995 cx,
18996 );
18997 }
18998 }
18999 }
19000 cx.emit(EditorEvent::BufferEdited);
19001 cx.emit(SearchEvent::MatchesInvalidated);
19002 if *singleton_buffer_edited {
19003 if let Some(buffer) = edited_buffer {
19004 if buffer.read(cx).file().is_none() {
19005 cx.emit(EditorEvent::TitleChanged);
19006 }
19007 }
19008 if let Some(project) = &self.project {
19009 #[allow(clippy::mutable_key_type)]
19010 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19011 multibuffer
19012 .all_buffers()
19013 .into_iter()
19014 .filter_map(|buffer| {
19015 buffer.update(cx, |buffer, cx| {
19016 let language = buffer.language()?;
19017 let should_discard = project.update(cx, |project, cx| {
19018 project.is_local()
19019 && !project.has_language_servers_for(buffer, cx)
19020 });
19021 should_discard.not().then_some(language.clone())
19022 })
19023 })
19024 .collect::<HashSet<_>>()
19025 });
19026 if !languages_affected.is_empty() {
19027 self.refresh_inlay_hints(
19028 InlayHintRefreshReason::BufferEdited(languages_affected),
19029 cx,
19030 );
19031 }
19032 }
19033 }
19034
19035 let Some(project) = &self.project else { return };
19036 let (telemetry, is_via_ssh) = {
19037 let project = project.read(cx);
19038 let telemetry = project.client().telemetry().clone();
19039 let is_via_ssh = project.is_via_ssh();
19040 (telemetry, is_via_ssh)
19041 };
19042 refresh_linked_ranges(self, window, cx);
19043 telemetry.log_edit_event("editor", is_via_ssh);
19044 }
19045 multi_buffer::Event::ExcerptsAdded {
19046 buffer,
19047 predecessor,
19048 excerpts,
19049 } => {
19050 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19051 let buffer_id = buffer.read(cx).remote_id();
19052 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19053 if let Some(project) = &self.project {
19054 update_uncommitted_diff_for_buffer(
19055 cx.entity(),
19056 project,
19057 [buffer.clone()],
19058 self.buffer.clone(),
19059 cx,
19060 )
19061 .detach();
19062 }
19063 }
19064 cx.emit(EditorEvent::ExcerptsAdded {
19065 buffer: buffer.clone(),
19066 predecessor: *predecessor,
19067 excerpts: excerpts.clone(),
19068 });
19069 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19070 }
19071 multi_buffer::Event::ExcerptsRemoved {
19072 ids,
19073 removed_buffer_ids,
19074 } => {
19075 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19076 let buffer = self.buffer.read(cx);
19077 self.registered_buffers
19078 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19079 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19080 cx.emit(EditorEvent::ExcerptsRemoved {
19081 ids: ids.clone(),
19082 removed_buffer_ids: removed_buffer_ids.clone(),
19083 })
19084 }
19085 multi_buffer::Event::ExcerptsEdited {
19086 excerpt_ids,
19087 buffer_ids,
19088 } => {
19089 self.display_map.update(cx, |map, cx| {
19090 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19091 });
19092 cx.emit(EditorEvent::ExcerptsEdited {
19093 ids: excerpt_ids.clone(),
19094 })
19095 }
19096 multi_buffer::Event::ExcerptsExpanded { ids } => {
19097 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19098 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19099 }
19100 multi_buffer::Event::Reparsed(buffer_id) => {
19101 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19102 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19103
19104 cx.emit(EditorEvent::Reparsed(*buffer_id));
19105 }
19106 multi_buffer::Event::DiffHunksToggled => {
19107 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19108 }
19109 multi_buffer::Event::LanguageChanged(buffer_id) => {
19110 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19111 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19112 cx.emit(EditorEvent::Reparsed(*buffer_id));
19113 cx.notify();
19114 }
19115 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19116 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19117 multi_buffer::Event::FileHandleChanged
19118 | multi_buffer::Event::Reloaded
19119 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19120 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19121 multi_buffer::Event::DiagnosticsUpdated => {
19122 self.update_diagnostics_state(window, cx);
19123 }
19124 _ => {}
19125 };
19126 }
19127
19128 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19129 self.refresh_active_diagnostics(cx);
19130 self.refresh_inline_diagnostics(true, window, cx);
19131 self.scrollbar_marker_state.dirty = true;
19132 cx.notify();
19133 }
19134
19135 pub fn start_temporary_diff_override(&mut self) {
19136 self.load_diff_task.take();
19137 self.temporary_diff_override = true;
19138 }
19139
19140 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19141 self.temporary_diff_override = false;
19142 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19143 self.buffer.update(cx, |buffer, cx| {
19144 buffer.set_all_diff_hunks_collapsed(cx);
19145 });
19146
19147 if let Some(project) = self.project.clone() {
19148 self.load_diff_task = Some(
19149 update_uncommitted_diff_for_buffer(
19150 cx.entity(),
19151 &project,
19152 self.buffer.read(cx).all_buffers(),
19153 self.buffer.clone(),
19154 cx,
19155 )
19156 .shared(),
19157 );
19158 }
19159 }
19160
19161 fn on_display_map_changed(
19162 &mut self,
19163 _: Entity<DisplayMap>,
19164 _: &mut Window,
19165 cx: &mut Context<Self>,
19166 ) {
19167 cx.notify();
19168 }
19169
19170 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19171 let new_severity = if self.diagnostics_enabled() {
19172 EditorSettings::get_global(cx)
19173 .diagnostics_max_severity
19174 .unwrap_or(DiagnosticSeverity::Hint)
19175 } else {
19176 DiagnosticSeverity::Off
19177 };
19178 self.set_max_diagnostics_severity(new_severity, cx);
19179 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19180 self.update_edit_prediction_settings(cx);
19181 self.refresh_inline_completion(true, false, window, cx);
19182 self.refresh_inlay_hints(
19183 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19184 self.selections.newest_anchor().head(),
19185 &self.buffer.read(cx).snapshot(cx),
19186 cx,
19187 )),
19188 cx,
19189 );
19190
19191 let old_cursor_shape = self.cursor_shape;
19192
19193 {
19194 let editor_settings = EditorSettings::get_global(cx);
19195 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19196 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19197 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19198 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19199 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19200 }
19201
19202 if old_cursor_shape != self.cursor_shape {
19203 cx.emit(EditorEvent::CursorShapeChanged);
19204 }
19205
19206 let project_settings = ProjectSettings::get_global(cx);
19207 self.serialize_dirty_buffers =
19208 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19209
19210 if self.mode.is_full() {
19211 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19212 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19213 if self.show_inline_diagnostics != show_inline_diagnostics {
19214 self.show_inline_diagnostics = show_inline_diagnostics;
19215 self.refresh_inline_diagnostics(false, window, cx);
19216 }
19217
19218 if self.git_blame_inline_enabled != inline_blame_enabled {
19219 self.toggle_git_blame_inline_internal(false, window, cx);
19220 }
19221
19222 let minimap_settings = EditorSettings::get_global(cx).minimap;
19223 if self.minimap_visibility != MinimapVisibility::Disabled {
19224 if self.minimap_visibility.settings_visibility()
19225 != minimap_settings.minimap_enabled()
19226 {
19227 self.set_minimap_visibility(
19228 MinimapVisibility::for_mode(self.mode(), cx),
19229 window,
19230 cx,
19231 );
19232 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19233 minimap_entity.update(cx, |minimap_editor, cx| {
19234 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19235 })
19236 }
19237 }
19238 }
19239
19240 cx.notify();
19241 }
19242
19243 pub fn set_searchable(&mut self, searchable: bool) {
19244 self.searchable = searchable;
19245 }
19246
19247 pub fn searchable(&self) -> bool {
19248 self.searchable
19249 }
19250
19251 fn open_proposed_changes_editor(
19252 &mut self,
19253 _: &OpenProposedChangesEditor,
19254 window: &mut Window,
19255 cx: &mut Context<Self>,
19256 ) {
19257 let Some(workspace) = self.workspace() else {
19258 cx.propagate();
19259 return;
19260 };
19261
19262 let selections = self.selections.all::<usize>(cx);
19263 let multi_buffer = self.buffer.read(cx);
19264 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19265 let mut new_selections_by_buffer = HashMap::default();
19266 for selection in selections {
19267 for (buffer, range, _) in
19268 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19269 {
19270 let mut range = range.to_point(buffer);
19271 range.start.column = 0;
19272 range.end.column = buffer.line_len(range.end.row);
19273 new_selections_by_buffer
19274 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19275 .or_insert(Vec::new())
19276 .push(range)
19277 }
19278 }
19279
19280 let proposed_changes_buffers = new_selections_by_buffer
19281 .into_iter()
19282 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19283 .collect::<Vec<_>>();
19284 let proposed_changes_editor = cx.new(|cx| {
19285 ProposedChangesEditor::new(
19286 "Proposed changes",
19287 proposed_changes_buffers,
19288 self.project.clone(),
19289 window,
19290 cx,
19291 )
19292 });
19293
19294 window.defer(cx, move |window, cx| {
19295 workspace.update(cx, |workspace, cx| {
19296 workspace.active_pane().update(cx, |pane, cx| {
19297 pane.add_item(
19298 Box::new(proposed_changes_editor),
19299 true,
19300 true,
19301 None,
19302 window,
19303 cx,
19304 );
19305 });
19306 });
19307 });
19308 }
19309
19310 pub fn open_excerpts_in_split(
19311 &mut self,
19312 _: &OpenExcerptsSplit,
19313 window: &mut Window,
19314 cx: &mut Context<Self>,
19315 ) {
19316 self.open_excerpts_common(None, true, window, cx)
19317 }
19318
19319 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19320 self.open_excerpts_common(None, false, window, cx)
19321 }
19322
19323 fn open_excerpts_common(
19324 &mut self,
19325 jump_data: Option<JumpData>,
19326 split: bool,
19327 window: &mut Window,
19328 cx: &mut Context<Self>,
19329 ) {
19330 let Some(workspace) = self.workspace() else {
19331 cx.propagate();
19332 return;
19333 };
19334
19335 if self.buffer.read(cx).is_singleton() {
19336 cx.propagate();
19337 return;
19338 }
19339
19340 let mut new_selections_by_buffer = HashMap::default();
19341 match &jump_data {
19342 Some(JumpData::MultiBufferPoint {
19343 excerpt_id,
19344 position,
19345 anchor,
19346 line_offset_from_top,
19347 }) => {
19348 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19349 if let Some(buffer) = multi_buffer_snapshot
19350 .buffer_id_for_excerpt(*excerpt_id)
19351 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19352 {
19353 let buffer_snapshot = buffer.read(cx).snapshot();
19354 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19355 language::ToPoint::to_point(anchor, &buffer_snapshot)
19356 } else {
19357 buffer_snapshot.clip_point(*position, Bias::Left)
19358 };
19359 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19360 new_selections_by_buffer.insert(
19361 buffer,
19362 (
19363 vec![jump_to_offset..jump_to_offset],
19364 Some(*line_offset_from_top),
19365 ),
19366 );
19367 }
19368 }
19369 Some(JumpData::MultiBufferRow {
19370 row,
19371 line_offset_from_top,
19372 }) => {
19373 let point = MultiBufferPoint::new(row.0, 0);
19374 if let Some((buffer, buffer_point, _)) =
19375 self.buffer.read(cx).point_to_buffer_point(point, cx)
19376 {
19377 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19378 new_selections_by_buffer
19379 .entry(buffer)
19380 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19381 .0
19382 .push(buffer_offset..buffer_offset)
19383 }
19384 }
19385 None => {
19386 let selections = self.selections.all::<usize>(cx);
19387 let multi_buffer = self.buffer.read(cx);
19388 for selection in selections {
19389 for (snapshot, range, _, anchor) in multi_buffer
19390 .snapshot(cx)
19391 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19392 {
19393 if let Some(anchor) = anchor {
19394 // selection is in a deleted hunk
19395 let Some(buffer_id) = anchor.buffer_id else {
19396 continue;
19397 };
19398 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19399 continue;
19400 };
19401 let offset = text::ToOffset::to_offset(
19402 &anchor.text_anchor,
19403 &buffer_handle.read(cx).snapshot(),
19404 );
19405 let range = offset..offset;
19406 new_selections_by_buffer
19407 .entry(buffer_handle)
19408 .or_insert((Vec::new(), None))
19409 .0
19410 .push(range)
19411 } else {
19412 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19413 else {
19414 continue;
19415 };
19416 new_selections_by_buffer
19417 .entry(buffer_handle)
19418 .or_insert((Vec::new(), None))
19419 .0
19420 .push(range)
19421 }
19422 }
19423 }
19424 }
19425 }
19426
19427 new_selections_by_buffer
19428 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19429
19430 if new_selections_by_buffer.is_empty() {
19431 return;
19432 }
19433
19434 // We defer the pane interaction because we ourselves are a workspace item
19435 // and activating a new item causes the pane to call a method on us reentrantly,
19436 // which panics if we're on the stack.
19437 window.defer(cx, move |window, cx| {
19438 workspace.update(cx, |workspace, cx| {
19439 let pane = if split {
19440 workspace.adjacent_pane(window, cx)
19441 } else {
19442 workspace.active_pane().clone()
19443 };
19444
19445 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19446 let editor = buffer
19447 .read(cx)
19448 .file()
19449 .is_none()
19450 .then(|| {
19451 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19452 // so `workspace.open_project_item` will never find them, always opening a new editor.
19453 // Instead, we try to activate the existing editor in the pane first.
19454 let (editor, pane_item_index) =
19455 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19456 let editor = item.downcast::<Editor>()?;
19457 let singleton_buffer =
19458 editor.read(cx).buffer().read(cx).as_singleton()?;
19459 if singleton_buffer == buffer {
19460 Some((editor, i))
19461 } else {
19462 None
19463 }
19464 })?;
19465 pane.update(cx, |pane, cx| {
19466 pane.activate_item(pane_item_index, true, true, window, cx)
19467 });
19468 Some(editor)
19469 })
19470 .flatten()
19471 .unwrap_or_else(|| {
19472 workspace.open_project_item::<Self>(
19473 pane.clone(),
19474 buffer,
19475 true,
19476 true,
19477 window,
19478 cx,
19479 )
19480 });
19481
19482 editor.update(cx, |editor, cx| {
19483 let autoscroll = match scroll_offset {
19484 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19485 None => Autoscroll::newest(),
19486 };
19487 let nav_history = editor.nav_history.take();
19488 editor.change_selections(Some(autoscroll), window, cx, |s| {
19489 s.select_ranges(ranges);
19490 });
19491 editor.nav_history = nav_history;
19492 });
19493 }
19494 })
19495 });
19496 }
19497
19498 // For now, don't allow opening excerpts in buffers that aren't backed by
19499 // regular project files.
19500 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19501 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19502 }
19503
19504 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19505 let snapshot = self.buffer.read(cx).read(cx);
19506 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19507 Some(
19508 ranges
19509 .iter()
19510 .map(move |range| {
19511 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19512 })
19513 .collect(),
19514 )
19515 }
19516
19517 fn selection_replacement_ranges(
19518 &self,
19519 range: Range<OffsetUtf16>,
19520 cx: &mut App,
19521 ) -> Vec<Range<OffsetUtf16>> {
19522 let selections = self.selections.all::<OffsetUtf16>(cx);
19523 let newest_selection = selections
19524 .iter()
19525 .max_by_key(|selection| selection.id)
19526 .unwrap();
19527 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19528 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19529 let snapshot = self.buffer.read(cx).read(cx);
19530 selections
19531 .into_iter()
19532 .map(|mut selection| {
19533 selection.start.0 =
19534 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19535 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19536 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19537 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19538 })
19539 .collect()
19540 }
19541
19542 fn report_editor_event(
19543 &self,
19544 event_type: &'static str,
19545 file_extension: Option<String>,
19546 cx: &App,
19547 ) {
19548 if cfg!(any(test, feature = "test-support")) {
19549 return;
19550 }
19551
19552 let Some(project) = &self.project else { return };
19553
19554 // If None, we are in a file without an extension
19555 let file = self
19556 .buffer
19557 .read(cx)
19558 .as_singleton()
19559 .and_then(|b| b.read(cx).file());
19560 let file_extension = file_extension.or(file
19561 .as_ref()
19562 .and_then(|file| Path::new(file.file_name(cx)).extension())
19563 .and_then(|e| e.to_str())
19564 .map(|a| a.to_string()));
19565
19566 let vim_mode = vim_enabled(cx);
19567
19568 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19569 let copilot_enabled = edit_predictions_provider
19570 == language::language_settings::EditPredictionProvider::Copilot;
19571 let copilot_enabled_for_language = self
19572 .buffer
19573 .read(cx)
19574 .language_settings(cx)
19575 .show_edit_predictions;
19576
19577 let project = project.read(cx);
19578 telemetry::event!(
19579 event_type,
19580 file_extension,
19581 vim_mode,
19582 copilot_enabled,
19583 copilot_enabled_for_language,
19584 edit_predictions_provider,
19585 is_via_ssh = project.is_via_ssh(),
19586 );
19587 }
19588
19589 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19590 /// with each line being an array of {text, highlight} objects.
19591 fn copy_highlight_json(
19592 &mut self,
19593 _: &CopyHighlightJson,
19594 window: &mut Window,
19595 cx: &mut Context<Self>,
19596 ) {
19597 #[derive(Serialize)]
19598 struct Chunk<'a> {
19599 text: String,
19600 highlight: Option<&'a str>,
19601 }
19602
19603 let snapshot = self.buffer.read(cx).snapshot(cx);
19604 let range = self
19605 .selected_text_range(false, window, cx)
19606 .and_then(|selection| {
19607 if selection.range.is_empty() {
19608 None
19609 } else {
19610 Some(selection.range)
19611 }
19612 })
19613 .unwrap_or_else(|| 0..snapshot.len());
19614
19615 let chunks = snapshot.chunks(range, true);
19616 let mut lines = Vec::new();
19617 let mut line: VecDeque<Chunk> = VecDeque::new();
19618
19619 let Some(style) = self.style.as_ref() else {
19620 return;
19621 };
19622
19623 for chunk in chunks {
19624 let highlight = chunk
19625 .syntax_highlight_id
19626 .and_then(|id| id.name(&style.syntax));
19627 let mut chunk_lines = chunk.text.split('\n').peekable();
19628 while let Some(text) = chunk_lines.next() {
19629 let mut merged_with_last_token = false;
19630 if let Some(last_token) = line.back_mut() {
19631 if last_token.highlight == highlight {
19632 last_token.text.push_str(text);
19633 merged_with_last_token = true;
19634 }
19635 }
19636
19637 if !merged_with_last_token {
19638 line.push_back(Chunk {
19639 text: text.into(),
19640 highlight,
19641 });
19642 }
19643
19644 if chunk_lines.peek().is_some() {
19645 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19646 line.pop_front();
19647 }
19648 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19649 line.pop_back();
19650 }
19651
19652 lines.push(mem::take(&mut line));
19653 }
19654 }
19655 }
19656
19657 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19658 return;
19659 };
19660 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19661 }
19662
19663 pub fn open_context_menu(
19664 &mut self,
19665 _: &OpenContextMenu,
19666 window: &mut Window,
19667 cx: &mut Context<Self>,
19668 ) {
19669 self.request_autoscroll(Autoscroll::newest(), cx);
19670 let position = self.selections.newest_display(cx).start;
19671 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19672 }
19673
19674 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19675 &self.inlay_hint_cache
19676 }
19677
19678 pub fn replay_insert_event(
19679 &mut self,
19680 text: &str,
19681 relative_utf16_range: Option<Range<isize>>,
19682 window: &mut Window,
19683 cx: &mut Context<Self>,
19684 ) {
19685 if !self.input_enabled {
19686 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19687 return;
19688 }
19689 if let Some(relative_utf16_range) = relative_utf16_range {
19690 let selections = self.selections.all::<OffsetUtf16>(cx);
19691 self.change_selections(None, window, cx, |s| {
19692 let new_ranges = selections.into_iter().map(|range| {
19693 let start = OffsetUtf16(
19694 range
19695 .head()
19696 .0
19697 .saturating_add_signed(relative_utf16_range.start),
19698 );
19699 let end = OffsetUtf16(
19700 range
19701 .head()
19702 .0
19703 .saturating_add_signed(relative_utf16_range.end),
19704 );
19705 start..end
19706 });
19707 s.select_ranges(new_ranges);
19708 });
19709 }
19710
19711 self.handle_input(text, window, cx);
19712 }
19713
19714 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19715 let Some(provider) = self.semantics_provider.as_ref() else {
19716 return false;
19717 };
19718
19719 let mut supports = false;
19720 self.buffer().update(cx, |this, cx| {
19721 this.for_each_buffer(|buffer| {
19722 supports |= provider.supports_inlay_hints(buffer, cx);
19723 });
19724 });
19725
19726 supports
19727 }
19728
19729 pub fn is_focused(&self, window: &Window) -> bool {
19730 self.focus_handle.is_focused(window)
19731 }
19732
19733 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19734 cx.emit(EditorEvent::Focused);
19735
19736 if let Some(descendant) = self
19737 .last_focused_descendant
19738 .take()
19739 .and_then(|descendant| descendant.upgrade())
19740 {
19741 window.focus(&descendant);
19742 } else {
19743 if let Some(blame) = self.blame.as_ref() {
19744 blame.update(cx, GitBlame::focus)
19745 }
19746
19747 self.blink_manager.update(cx, BlinkManager::enable);
19748 self.show_cursor_names(window, cx);
19749 self.buffer.update(cx, |buffer, cx| {
19750 buffer.finalize_last_transaction(cx);
19751 if self.leader_id.is_none() {
19752 buffer.set_active_selections(
19753 &self.selections.disjoint_anchors(),
19754 self.selections.line_mode,
19755 self.cursor_shape,
19756 cx,
19757 );
19758 }
19759 });
19760 }
19761 }
19762
19763 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19764 cx.emit(EditorEvent::FocusedIn)
19765 }
19766
19767 fn handle_focus_out(
19768 &mut self,
19769 event: FocusOutEvent,
19770 _window: &mut Window,
19771 cx: &mut Context<Self>,
19772 ) {
19773 if event.blurred != self.focus_handle {
19774 self.last_focused_descendant = Some(event.blurred);
19775 }
19776 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19777 }
19778
19779 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19780 self.blink_manager.update(cx, BlinkManager::disable);
19781 self.buffer
19782 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19783
19784 if let Some(blame) = self.blame.as_ref() {
19785 blame.update(cx, GitBlame::blur)
19786 }
19787 if !self.hover_state.focused(window, cx) {
19788 hide_hover(self, cx);
19789 }
19790 if !self
19791 .context_menu
19792 .borrow()
19793 .as_ref()
19794 .is_some_and(|context_menu| context_menu.focused(window, cx))
19795 {
19796 self.hide_context_menu(window, cx);
19797 }
19798 self.discard_inline_completion(false, cx);
19799 cx.emit(EditorEvent::Blurred);
19800 cx.notify();
19801 }
19802
19803 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19804 let mut pending: String = window
19805 .pending_input_keystrokes()
19806 .into_iter()
19807 .flatten()
19808 .filter_map(|keystroke| {
19809 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19810 keystroke.key_char.clone()
19811 } else {
19812 None
19813 }
19814 })
19815 .collect();
19816
19817 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19818 pending = "".to_string();
19819 }
19820
19821 let existing_pending = self
19822 .text_highlights::<PendingInput>(cx)
19823 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19824 if existing_pending.is_none() && pending.is_empty() {
19825 return;
19826 }
19827 let transaction =
19828 self.transact(window, cx, |this, window, cx| {
19829 let selections = this.selections.all::<usize>(cx);
19830 let edits = selections
19831 .iter()
19832 .map(|selection| (selection.end..selection.end, pending.clone()));
19833 this.edit(edits, cx);
19834 this.change_selections(None, window, cx, |s| {
19835 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19836 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19837 }));
19838 });
19839 if let Some(existing_ranges) = existing_pending {
19840 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19841 this.edit(edits, cx);
19842 }
19843 });
19844
19845 let snapshot = self.snapshot(window, cx);
19846 let ranges = self
19847 .selections
19848 .all::<usize>(cx)
19849 .into_iter()
19850 .map(|selection| {
19851 snapshot.buffer_snapshot.anchor_after(selection.end)
19852 ..snapshot
19853 .buffer_snapshot
19854 .anchor_before(selection.end + pending.len())
19855 })
19856 .collect();
19857
19858 if pending.is_empty() {
19859 self.clear_highlights::<PendingInput>(cx);
19860 } else {
19861 self.highlight_text::<PendingInput>(
19862 ranges,
19863 HighlightStyle {
19864 underline: Some(UnderlineStyle {
19865 thickness: px(1.),
19866 color: None,
19867 wavy: false,
19868 }),
19869 ..Default::default()
19870 },
19871 cx,
19872 );
19873 }
19874
19875 self.ime_transaction = self.ime_transaction.or(transaction);
19876 if let Some(transaction) = self.ime_transaction {
19877 self.buffer.update(cx, |buffer, cx| {
19878 buffer.group_until_transaction(transaction, cx);
19879 });
19880 }
19881
19882 if self.text_highlights::<PendingInput>(cx).is_none() {
19883 self.ime_transaction.take();
19884 }
19885 }
19886
19887 pub fn register_action_renderer(
19888 &mut self,
19889 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19890 ) -> Subscription {
19891 let id = self.next_editor_action_id.post_inc();
19892 self.editor_actions
19893 .borrow_mut()
19894 .insert(id, Box::new(listener));
19895
19896 let editor_actions = self.editor_actions.clone();
19897 Subscription::new(move || {
19898 editor_actions.borrow_mut().remove(&id);
19899 })
19900 }
19901
19902 pub fn register_action<A: Action>(
19903 &mut self,
19904 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19905 ) -> Subscription {
19906 let id = self.next_editor_action_id.post_inc();
19907 let listener = Arc::new(listener);
19908 self.editor_actions.borrow_mut().insert(
19909 id,
19910 Box::new(move |_, window, _| {
19911 let listener = listener.clone();
19912 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19913 let action = action.downcast_ref().unwrap();
19914 if phase == DispatchPhase::Bubble {
19915 listener(action, window, cx)
19916 }
19917 })
19918 }),
19919 );
19920
19921 let editor_actions = self.editor_actions.clone();
19922 Subscription::new(move || {
19923 editor_actions.borrow_mut().remove(&id);
19924 })
19925 }
19926
19927 pub fn file_header_size(&self) -> u32 {
19928 FILE_HEADER_HEIGHT
19929 }
19930
19931 pub fn restore(
19932 &mut self,
19933 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19934 window: &mut Window,
19935 cx: &mut Context<Self>,
19936 ) {
19937 let workspace = self.workspace();
19938 let project = self.project.as_ref();
19939 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19940 let mut tasks = Vec::new();
19941 for (buffer_id, changes) in revert_changes {
19942 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19943 buffer.update(cx, |buffer, cx| {
19944 buffer.edit(
19945 changes
19946 .into_iter()
19947 .map(|(range, text)| (range, text.to_string())),
19948 None,
19949 cx,
19950 );
19951 });
19952
19953 if let Some(project) =
19954 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19955 {
19956 project.update(cx, |project, cx| {
19957 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19958 })
19959 }
19960 }
19961 }
19962 tasks
19963 });
19964 cx.spawn_in(window, async move |_, cx| {
19965 for (buffer, task) in save_tasks {
19966 let result = task.await;
19967 if result.is_err() {
19968 let Some(path) = buffer
19969 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19970 .ok()
19971 else {
19972 continue;
19973 };
19974 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19975 let Some(task) = cx
19976 .update_window_entity(&workspace, |workspace, window, cx| {
19977 workspace
19978 .open_path_preview(path, None, false, false, false, window, cx)
19979 })
19980 .ok()
19981 else {
19982 continue;
19983 };
19984 task.await.log_err();
19985 }
19986 }
19987 }
19988 })
19989 .detach();
19990 self.change_selections(None, window, cx, |selections| selections.refresh());
19991 }
19992
19993 pub fn to_pixel_point(
19994 &self,
19995 source: multi_buffer::Anchor,
19996 editor_snapshot: &EditorSnapshot,
19997 window: &mut Window,
19998 ) -> Option<gpui::Point<Pixels>> {
19999 let source_point = source.to_display_point(editor_snapshot);
20000 self.display_to_pixel_point(source_point, editor_snapshot, window)
20001 }
20002
20003 pub fn display_to_pixel_point(
20004 &self,
20005 source: DisplayPoint,
20006 editor_snapshot: &EditorSnapshot,
20007 window: &mut Window,
20008 ) -> Option<gpui::Point<Pixels>> {
20009 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20010 let text_layout_details = self.text_layout_details(window);
20011 let scroll_top = text_layout_details
20012 .scroll_anchor
20013 .scroll_position(editor_snapshot)
20014 .y;
20015
20016 if source.row().as_f32() < scroll_top.floor() {
20017 return None;
20018 }
20019 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20020 let source_y = line_height * (source.row().as_f32() - scroll_top);
20021 Some(gpui::Point::new(source_x, source_y))
20022 }
20023
20024 pub fn has_visible_completions_menu(&self) -> bool {
20025 !self.edit_prediction_preview_is_active()
20026 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20027 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20028 })
20029 }
20030
20031 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20032 if self.mode.is_minimap() {
20033 return;
20034 }
20035 self.addons
20036 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20037 }
20038
20039 pub fn unregister_addon<T: Addon>(&mut self) {
20040 self.addons.remove(&std::any::TypeId::of::<T>());
20041 }
20042
20043 pub fn addon<T: Addon>(&self) -> Option<&T> {
20044 let type_id = std::any::TypeId::of::<T>();
20045 self.addons
20046 .get(&type_id)
20047 .and_then(|item| item.to_any().downcast_ref::<T>())
20048 }
20049
20050 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20051 let type_id = std::any::TypeId::of::<T>();
20052 self.addons
20053 .get_mut(&type_id)
20054 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20055 }
20056
20057 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20058 let text_layout_details = self.text_layout_details(window);
20059 let style = &text_layout_details.editor_style;
20060 let font_id = window.text_system().resolve_font(&style.text.font());
20061 let font_size = style.text.font_size.to_pixels(window.rem_size());
20062 let line_height = style.text.line_height_in_pixels(window.rem_size());
20063 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20064
20065 gpui::Size::new(em_width, line_height)
20066 }
20067
20068 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20069 self.load_diff_task.clone()
20070 }
20071
20072 fn read_metadata_from_db(
20073 &mut self,
20074 item_id: u64,
20075 workspace_id: WorkspaceId,
20076 window: &mut Window,
20077 cx: &mut Context<Editor>,
20078 ) {
20079 if self.is_singleton(cx)
20080 && !self.mode.is_minimap()
20081 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20082 {
20083 let buffer_snapshot = OnceCell::new();
20084
20085 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20086 if !folds.is_empty() {
20087 let snapshot =
20088 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20089 self.fold_ranges(
20090 folds
20091 .into_iter()
20092 .map(|(start, end)| {
20093 snapshot.clip_offset(start, Bias::Left)
20094 ..snapshot.clip_offset(end, Bias::Right)
20095 })
20096 .collect(),
20097 false,
20098 window,
20099 cx,
20100 );
20101 }
20102 }
20103
20104 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20105 if !selections.is_empty() {
20106 let snapshot =
20107 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20108 // skip adding the initial selection to selection history
20109 self.selection_history.mode = SelectionHistoryMode::Skipping;
20110 self.change_selections(None, window, cx, |s| {
20111 s.select_ranges(selections.into_iter().map(|(start, end)| {
20112 snapshot.clip_offset(start, Bias::Left)
20113 ..snapshot.clip_offset(end, Bias::Right)
20114 }));
20115 });
20116 self.selection_history.mode = SelectionHistoryMode::Normal;
20117 }
20118 };
20119 }
20120
20121 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20122 }
20123}
20124
20125fn vim_enabled(cx: &App) -> bool {
20126 cx.global::<SettingsStore>()
20127 .raw_user_settings()
20128 .get("vim_mode")
20129 == Some(&serde_json::Value::Bool(true))
20130}
20131
20132fn process_completion_for_edit(
20133 completion: &Completion,
20134 intent: CompletionIntent,
20135 buffer: &Entity<Buffer>,
20136 cursor_position: &text::Anchor,
20137 cx: &mut Context<Editor>,
20138) -> CompletionEdit {
20139 let buffer = buffer.read(cx);
20140 let buffer_snapshot = buffer.snapshot();
20141 let (snippet, new_text) = if completion.is_snippet() {
20142 // Workaround for typescript language server issues so that methods don't expand within
20143 // strings and functions with type expressions. The previous point is used because the query
20144 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20145 let mut snippet_source = completion.new_text.clone();
20146 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20147 previous_point.column = previous_point.column.saturating_sub(1);
20148 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20149 if scope.prefers_label_for_snippet_in_completion() {
20150 if let Some(label) = completion.label() {
20151 if matches!(
20152 completion.kind(),
20153 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20154 ) {
20155 snippet_source = label;
20156 }
20157 }
20158 }
20159 }
20160 match Snippet::parse(&snippet_source).log_err() {
20161 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20162 None => (None, completion.new_text.clone()),
20163 }
20164 } else {
20165 (None, completion.new_text.clone())
20166 };
20167
20168 let mut range_to_replace = {
20169 let replace_range = &completion.replace_range;
20170 if let CompletionSource::Lsp {
20171 insert_range: Some(insert_range),
20172 ..
20173 } = &completion.source
20174 {
20175 debug_assert_eq!(
20176 insert_range.start, replace_range.start,
20177 "insert_range and replace_range should start at the same position"
20178 );
20179 debug_assert!(
20180 insert_range
20181 .start
20182 .cmp(&cursor_position, &buffer_snapshot)
20183 .is_le(),
20184 "insert_range should start before or at cursor position"
20185 );
20186 debug_assert!(
20187 replace_range
20188 .start
20189 .cmp(&cursor_position, &buffer_snapshot)
20190 .is_le(),
20191 "replace_range should start before or at cursor position"
20192 );
20193 debug_assert!(
20194 insert_range
20195 .end
20196 .cmp(&cursor_position, &buffer_snapshot)
20197 .is_le(),
20198 "insert_range should end before or at cursor position"
20199 );
20200
20201 let should_replace = match intent {
20202 CompletionIntent::CompleteWithInsert => false,
20203 CompletionIntent::CompleteWithReplace => true,
20204 CompletionIntent::Complete | CompletionIntent::Compose => {
20205 let insert_mode =
20206 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20207 .completions
20208 .lsp_insert_mode;
20209 match insert_mode {
20210 LspInsertMode::Insert => false,
20211 LspInsertMode::Replace => true,
20212 LspInsertMode::ReplaceSubsequence => {
20213 let mut text_to_replace = buffer.chars_for_range(
20214 buffer.anchor_before(replace_range.start)
20215 ..buffer.anchor_after(replace_range.end),
20216 );
20217 let mut current_needle = text_to_replace.next();
20218 for haystack_ch in completion.label.text.chars() {
20219 if let Some(needle_ch) = current_needle {
20220 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20221 current_needle = text_to_replace.next();
20222 }
20223 }
20224 }
20225 current_needle.is_none()
20226 }
20227 LspInsertMode::ReplaceSuffix => {
20228 if replace_range
20229 .end
20230 .cmp(&cursor_position, &buffer_snapshot)
20231 .is_gt()
20232 {
20233 let range_after_cursor = *cursor_position..replace_range.end;
20234 let text_after_cursor = buffer
20235 .text_for_range(
20236 buffer.anchor_before(range_after_cursor.start)
20237 ..buffer.anchor_after(range_after_cursor.end),
20238 )
20239 .collect::<String>()
20240 .to_ascii_lowercase();
20241 completion
20242 .label
20243 .text
20244 .to_ascii_lowercase()
20245 .ends_with(&text_after_cursor)
20246 } else {
20247 true
20248 }
20249 }
20250 }
20251 }
20252 };
20253
20254 if should_replace {
20255 replace_range.clone()
20256 } else {
20257 insert_range.clone()
20258 }
20259 } else {
20260 replace_range.clone()
20261 }
20262 };
20263
20264 if range_to_replace
20265 .end
20266 .cmp(&cursor_position, &buffer_snapshot)
20267 .is_lt()
20268 {
20269 range_to_replace.end = *cursor_position;
20270 }
20271
20272 CompletionEdit {
20273 new_text,
20274 replace_range: range_to_replace.to_offset(&buffer),
20275 snippet,
20276 }
20277}
20278
20279struct CompletionEdit {
20280 new_text: String,
20281 replace_range: Range<usize>,
20282 snippet: Option<Snippet>,
20283}
20284
20285fn insert_extra_newline_brackets(
20286 buffer: &MultiBufferSnapshot,
20287 range: Range<usize>,
20288 language: &language::LanguageScope,
20289) -> bool {
20290 let leading_whitespace_len = buffer
20291 .reversed_chars_at(range.start)
20292 .take_while(|c| c.is_whitespace() && *c != '\n')
20293 .map(|c| c.len_utf8())
20294 .sum::<usize>();
20295 let trailing_whitespace_len = buffer
20296 .chars_at(range.end)
20297 .take_while(|c| c.is_whitespace() && *c != '\n')
20298 .map(|c| c.len_utf8())
20299 .sum::<usize>();
20300 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20301
20302 language.brackets().any(|(pair, enabled)| {
20303 let pair_start = pair.start.trim_end();
20304 let pair_end = pair.end.trim_start();
20305
20306 enabled
20307 && pair.newline
20308 && buffer.contains_str_at(range.end, pair_end)
20309 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20310 })
20311}
20312
20313fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20314 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20315 [(buffer, range, _)] => (*buffer, range.clone()),
20316 _ => return false,
20317 };
20318 let pair = {
20319 let mut result: Option<BracketMatch> = None;
20320
20321 for pair in buffer
20322 .all_bracket_ranges(range.clone())
20323 .filter(move |pair| {
20324 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20325 })
20326 {
20327 let len = pair.close_range.end - pair.open_range.start;
20328
20329 if let Some(existing) = &result {
20330 let existing_len = existing.close_range.end - existing.open_range.start;
20331 if len > existing_len {
20332 continue;
20333 }
20334 }
20335
20336 result = Some(pair);
20337 }
20338
20339 result
20340 };
20341 let Some(pair) = pair else {
20342 return false;
20343 };
20344 pair.newline_only
20345 && buffer
20346 .chars_for_range(pair.open_range.end..range.start)
20347 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20348 .all(|c| c.is_whitespace() && c != '\n')
20349}
20350
20351fn update_uncommitted_diff_for_buffer(
20352 editor: Entity<Editor>,
20353 project: &Entity<Project>,
20354 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20355 buffer: Entity<MultiBuffer>,
20356 cx: &mut App,
20357) -> Task<()> {
20358 let mut tasks = Vec::new();
20359 project.update(cx, |project, cx| {
20360 for buffer in buffers {
20361 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20362 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20363 }
20364 }
20365 });
20366 cx.spawn(async move |cx| {
20367 let diffs = future::join_all(tasks).await;
20368 if editor
20369 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20370 .unwrap_or(false)
20371 {
20372 return;
20373 }
20374
20375 buffer
20376 .update(cx, |buffer, cx| {
20377 for diff in diffs.into_iter().flatten() {
20378 buffer.add_diff(diff, cx);
20379 }
20380 })
20381 .ok();
20382 })
20383}
20384
20385fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20386 let tab_size = tab_size.get() as usize;
20387 let mut width = offset;
20388
20389 for ch in text.chars() {
20390 width += if ch == '\t' {
20391 tab_size - (width % tab_size)
20392 } else {
20393 1
20394 };
20395 }
20396
20397 width - offset
20398}
20399
20400#[cfg(test)]
20401mod tests {
20402 use super::*;
20403
20404 #[test]
20405 fn test_string_size_with_expanded_tabs() {
20406 let nz = |val| NonZeroU32::new(val).unwrap();
20407 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20408 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20409 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20410 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20411 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20412 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20413 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20414 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20415 }
20416}
20417
20418/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20419struct WordBreakingTokenizer<'a> {
20420 input: &'a str,
20421}
20422
20423impl<'a> WordBreakingTokenizer<'a> {
20424 fn new(input: &'a str) -> Self {
20425 Self { input }
20426 }
20427}
20428
20429fn is_char_ideographic(ch: char) -> bool {
20430 use unicode_script::Script::*;
20431 use unicode_script::UnicodeScript;
20432 matches!(ch.script(), Han | Tangut | Yi)
20433}
20434
20435fn is_grapheme_ideographic(text: &str) -> bool {
20436 text.chars().any(is_char_ideographic)
20437}
20438
20439fn is_grapheme_whitespace(text: &str) -> bool {
20440 text.chars().any(|x| x.is_whitespace())
20441}
20442
20443fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20444 text.chars().next().map_or(false, |ch| {
20445 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20446 })
20447}
20448
20449#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20450enum WordBreakToken<'a> {
20451 Word { token: &'a str, grapheme_len: usize },
20452 InlineWhitespace { token: &'a str, grapheme_len: usize },
20453 Newline,
20454}
20455
20456impl<'a> Iterator for WordBreakingTokenizer<'a> {
20457 /// Yields a span, the count of graphemes in the token, and whether it was
20458 /// whitespace. Note that it also breaks at word boundaries.
20459 type Item = WordBreakToken<'a>;
20460
20461 fn next(&mut self) -> Option<Self::Item> {
20462 use unicode_segmentation::UnicodeSegmentation;
20463 if self.input.is_empty() {
20464 return None;
20465 }
20466
20467 let mut iter = self.input.graphemes(true).peekable();
20468 let mut offset = 0;
20469 let mut grapheme_len = 0;
20470 if let Some(first_grapheme) = iter.next() {
20471 let is_newline = first_grapheme == "\n";
20472 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20473 offset += first_grapheme.len();
20474 grapheme_len += 1;
20475 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20476 if let Some(grapheme) = iter.peek().copied() {
20477 if should_stay_with_preceding_ideograph(grapheme) {
20478 offset += grapheme.len();
20479 grapheme_len += 1;
20480 }
20481 }
20482 } else {
20483 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20484 let mut next_word_bound = words.peek().copied();
20485 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20486 next_word_bound = words.next();
20487 }
20488 while let Some(grapheme) = iter.peek().copied() {
20489 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20490 break;
20491 };
20492 if is_grapheme_whitespace(grapheme) != is_whitespace
20493 || (grapheme == "\n") != is_newline
20494 {
20495 break;
20496 };
20497 offset += grapheme.len();
20498 grapheme_len += 1;
20499 iter.next();
20500 }
20501 }
20502 let token = &self.input[..offset];
20503 self.input = &self.input[offset..];
20504 if token == "\n" {
20505 Some(WordBreakToken::Newline)
20506 } else if is_whitespace {
20507 Some(WordBreakToken::InlineWhitespace {
20508 token,
20509 grapheme_len,
20510 })
20511 } else {
20512 Some(WordBreakToken::Word {
20513 token,
20514 grapheme_len,
20515 })
20516 }
20517 } else {
20518 None
20519 }
20520 }
20521}
20522
20523#[test]
20524fn test_word_breaking_tokenizer() {
20525 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20526 ("", &[]),
20527 (" ", &[whitespace(" ", 2)]),
20528 ("Ʒ", &[word("Ʒ", 1)]),
20529 ("Ǽ", &[word("Ǽ", 1)]),
20530 ("⋑", &[word("⋑", 1)]),
20531 ("⋑⋑", &[word("⋑⋑", 2)]),
20532 (
20533 "原理,进而",
20534 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20535 ),
20536 (
20537 "hello world",
20538 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20539 ),
20540 (
20541 "hello, world",
20542 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20543 ),
20544 (
20545 " hello world",
20546 &[
20547 whitespace(" ", 2),
20548 word("hello", 5),
20549 whitespace(" ", 1),
20550 word("world", 5),
20551 ],
20552 ),
20553 (
20554 "这是什么 \n 钢笔",
20555 &[
20556 word("这", 1),
20557 word("是", 1),
20558 word("什", 1),
20559 word("么", 1),
20560 whitespace(" ", 1),
20561 newline(),
20562 whitespace(" ", 1),
20563 word("钢", 1),
20564 word("笔", 1),
20565 ],
20566 ),
20567 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20568 ];
20569
20570 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20571 WordBreakToken::Word {
20572 token,
20573 grapheme_len,
20574 }
20575 }
20576
20577 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20578 WordBreakToken::InlineWhitespace {
20579 token,
20580 grapheme_len,
20581 }
20582 }
20583
20584 fn newline() -> WordBreakToken<'static> {
20585 WordBreakToken::Newline
20586 }
20587
20588 for (input, result) in tests {
20589 assert_eq!(
20590 WordBreakingTokenizer::new(input)
20591 .collect::<Vec<_>>()
20592 .as_slice(),
20593 *result,
20594 );
20595 }
20596}
20597
20598fn wrap_with_prefix(
20599 line_prefix: String,
20600 unwrapped_text: String,
20601 wrap_column: usize,
20602 tab_size: NonZeroU32,
20603 preserve_existing_whitespace: bool,
20604) -> String {
20605 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20606 let mut wrapped_text = String::new();
20607 let mut current_line = line_prefix.clone();
20608
20609 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20610 let mut current_line_len = line_prefix_len;
20611 let mut in_whitespace = false;
20612 for token in tokenizer {
20613 let have_preceding_whitespace = in_whitespace;
20614 match token {
20615 WordBreakToken::Word {
20616 token,
20617 grapheme_len,
20618 } => {
20619 in_whitespace = false;
20620 if current_line_len + grapheme_len > wrap_column
20621 && current_line_len != line_prefix_len
20622 {
20623 wrapped_text.push_str(current_line.trim_end());
20624 wrapped_text.push('\n');
20625 current_line.truncate(line_prefix.len());
20626 current_line_len = line_prefix_len;
20627 }
20628 current_line.push_str(token);
20629 current_line_len += grapheme_len;
20630 }
20631 WordBreakToken::InlineWhitespace {
20632 mut token,
20633 mut grapheme_len,
20634 } => {
20635 in_whitespace = true;
20636 if have_preceding_whitespace && !preserve_existing_whitespace {
20637 continue;
20638 }
20639 if !preserve_existing_whitespace {
20640 token = " ";
20641 grapheme_len = 1;
20642 }
20643 if current_line_len + grapheme_len > wrap_column {
20644 wrapped_text.push_str(current_line.trim_end());
20645 wrapped_text.push('\n');
20646 current_line.truncate(line_prefix.len());
20647 current_line_len = line_prefix_len;
20648 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20649 current_line.push_str(token);
20650 current_line_len += grapheme_len;
20651 }
20652 }
20653 WordBreakToken::Newline => {
20654 in_whitespace = true;
20655 if preserve_existing_whitespace {
20656 wrapped_text.push_str(current_line.trim_end());
20657 wrapped_text.push('\n');
20658 current_line.truncate(line_prefix.len());
20659 current_line_len = line_prefix_len;
20660 } else if have_preceding_whitespace {
20661 continue;
20662 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20663 {
20664 wrapped_text.push_str(current_line.trim_end());
20665 wrapped_text.push('\n');
20666 current_line.truncate(line_prefix.len());
20667 current_line_len = line_prefix_len;
20668 } else if current_line_len != line_prefix_len {
20669 current_line.push(' ');
20670 current_line_len += 1;
20671 }
20672 }
20673 }
20674 }
20675
20676 if !current_line.is_empty() {
20677 wrapped_text.push_str(¤t_line);
20678 }
20679 wrapped_text
20680}
20681
20682#[test]
20683fn test_wrap_with_prefix() {
20684 assert_eq!(
20685 wrap_with_prefix(
20686 "# ".to_string(),
20687 "abcdefg".to_string(),
20688 4,
20689 NonZeroU32::new(4).unwrap(),
20690 false,
20691 ),
20692 "# abcdefg"
20693 );
20694 assert_eq!(
20695 wrap_with_prefix(
20696 "".to_string(),
20697 "\thello world".to_string(),
20698 8,
20699 NonZeroU32::new(4).unwrap(),
20700 false,
20701 ),
20702 "hello\nworld"
20703 );
20704 assert_eq!(
20705 wrap_with_prefix(
20706 "// ".to_string(),
20707 "xx \nyy zz aa bb cc".to_string(),
20708 12,
20709 NonZeroU32::new(4).unwrap(),
20710 false,
20711 ),
20712 "// xx yy zz\n// aa bb cc"
20713 );
20714 assert_eq!(
20715 wrap_with_prefix(
20716 String::new(),
20717 "这是什么 \n 钢笔".to_string(),
20718 3,
20719 NonZeroU32::new(4).unwrap(),
20720 false,
20721 ),
20722 "这是什\n么 钢\n笔"
20723 );
20724}
20725
20726pub trait CollaborationHub {
20727 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20728 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20729 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20730}
20731
20732impl CollaborationHub for Entity<Project> {
20733 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20734 self.read(cx).collaborators()
20735 }
20736
20737 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20738 self.read(cx).user_store().read(cx).participant_indices()
20739 }
20740
20741 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20742 let this = self.read(cx);
20743 let user_ids = this.collaborators().values().map(|c| c.user_id);
20744 this.user_store().read(cx).participant_names(user_ids, cx)
20745 }
20746}
20747
20748pub trait SemanticsProvider {
20749 fn hover(
20750 &self,
20751 buffer: &Entity<Buffer>,
20752 position: text::Anchor,
20753 cx: &mut App,
20754 ) -> Option<Task<Vec<project::Hover>>>;
20755
20756 fn inline_values(
20757 &self,
20758 buffer_handle: Entity<Buffer>,
20759 range: Range<text::Anchor>,
20760 cx: &mut App,
20761 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20762
20763 fn inlay_hints(
20764 &self,
20765 buffer_handle: Entity<Buffer>,
20766 range: Range<text::Anchor>,
20767 cx: &mut App,
20768 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20769
20770 fn resolve_inlay_hint(
20771 &self,
20772 hint: InlayHint,
20773 buffer_handle: Entity<Buffer>,
20774 server_id: LanguageServerId,
20775 cx: &mut App,
20776 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20777
20778 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20779
20780 fn document_highlights(
20781 &self,
20782 buffer: &Entity<Buffer>,
20783 position: text::Anchor,
20784 cx: &mut App,
20785 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20786
20787 fn definitions(
20788 &self,
20789 buffer: &Entity<Buffer>,
20790 position: text::Anchor,
20791 kind: GotoDefinitionKind,
20792 cx: &mut App,
20793 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20794
20795 fn range_for_rename(
20796 &self,
20797 buffer: &Entity<Buffer>,
20798 position: text::Anchor,
20799 cx: &mut App,
20800 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20801
20802 fn perform_rename(
20803 &self,
20804 buffer: &Entity<Buffer>,
20805 position: text::Anchor,
20806 new_name: String,
20807 cx: &mut App,
20808 ) -> Option<Task<Result<ProjectTransaction>>>;
20809
20810 fn pull_diagnostics_for_buffer(
20811 &self,
20812 buffer: Entity<Buffer>,
20813 cx: &mut App,
20814 ) -> Task<anyhow::Result<()>>;
20815}
20816
20817pub trait CompletionProvider {
20818 fn completions(
20819 &self,
20820 excerpt_id: ExcerptId,
20821 buffer: &Entity<Buffer>,
20822 buffer_position: text::Anchor,
20823 trigger: CompletionContext,
20824 window: &mut Window,
20825 cx: &mut Context<Editor>,
20826 ) -> Task<Result<Vec<CompletionResponse>>>;
20827
20828 fn resolve_completions(
20829 &self,
20830 _buffer: Entity<Buffer>,
20831 _completion_indices: Vec<usize>,
20832 _completions: Rc<RefCell<Box<[Completion]>>>,
20833 _cx: &mut Context<Editor>,
20834 ) -> Task<Result<bool>> {
20835 Task::ready(Ok(false))
20836 }
20837
20838 fn apply_additional_edits_for_completion(
20839 &self,
20840 _buffer: Entity<Buffer>,
20841 _completions: Rc<RefCell<Box<[Completion]>>>,
20842 _completion_index: usize,
20843 _push_to_history: bool,
20844 _cx: &mut Context<Editor>,
20845 ) -> Task<Result<Option<language::Transaction>>> {
20846 Task::ready(Ok(None))
20847 }
20848
20849 fn is_completion_trigger(
20850 &self,
20851 buffer: &Entity<Buffer>,
20852 position: language::Anchor,
20853 text: &str,
20854 trigger_in_words: bool,
20855 menu_is_open: bool,
20856 cx: &mut Context<Editor>,
20857 ) -> bool;
20858
20859 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20860
20861 fn sort_completions(&self) -> bool {
20862 true
20863 }
20864
20865 fn filter_completions(&self) -> bool {
20866 true
20867 }
20868}
20869
20870pub trait CodeActionProvider {
20871 fn id(&self) -> Arc<str>;
20872
20873 fn code_actions(
20874 &self,
20875 buffer: &Entity<Buffer>,
20876 range: Range<text::Anchor>,
20877 window: &mut Window,
20878 cx: &mut App,
20879 ) -> Task<Result<Vec<CodeAction>>>;
20880
20881 fn apply_code_action(
20882 &self,
20883 buffer_handle: Entity<Buffer>,
20884 action: CodeAction,
20885 excerpt_id: ExcerptId,
20886 push_to_history: bool,
20887 window: &mut Window,
20888 cx: &mut App,
20889 ) -> Task<Result<ProjectTransaction>>;
20890}
20891
20892impl CodeActionProvider for Entity<Project> {
20893 fn id(&self) -> Arc<str> {
20894 "project".into()
20895 }
20896
20897 fn code_actions(
20898 &self,
20899 buffer: &Entity<Buffer>,
20900 range: Range<text::Anchor>,
20901 _window: &mut Window,
20902 cx: &mut App,
20903 ) -> Task<Result<Vec<CodeAction>>> {
20904 self.update(cx, |project, cx| {
20905 let code_lens = project.code_lens(buffer, range.clone(), cx);
20906 let code_actions = project.code_actions(buffer, range, None, cx);
20907 cx.background_spawn(async move {
20908 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20909 Ok(code_lens
20910 .context("code lens fetch")?
20911 .into_iter()
20912 .chain(code_actions.context("code action fetch")?)
20913 .collect())
20914 })
20915 })
20916 }
20917
20918 fn apply_code_action(
20919 &self,
20920 buffer_handle: Entity<Buffer>,
20921 action: CodeAction,
20922 _excerpt_id: ExcerptId,
20923 push_to_history: bool,
20924 _window: &mut Window,
20925 cx: &mut App,
20926 ) -> Task<Result<ProjectTransaction>> {
20927 self.update(cx, |project, cx| {
20928 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20929 })
20930 }
20931}
20932
20933fn snippet_completions(
20934 project: &Project,
20935 buffer: &Entity<Buffer>,
20936 buffer_position: text::Anchor,
20937 cx: &mut App,
20938) -> Task<Result<CompletionResponse>> {
20939 let languages = buffer.read(cx).languages_at(buffer_position);
20940 let snippet_store = project.snippets().read(cx);
20941
20942 let scopes: Vec<_> = languages
20943 .iter()
20944 .filter_map(|language| {
20945 let language_name = language.lsp_id();
20946 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20947
20948 if snippets.is_empty() {
20949 None
20950 } else {
20951 Some((language.default_scope(), snippets))
20952 }
20953 })
20954 .collect();
20955
20956 if scopes.is_empty() {
20957 return Task::ready(Ok(CompletionResponse {
20958 completions: vec![],
20959 is_incomplete: false,
20960 }));
20961 }
20962
20963 let snapshot = buffer.read(cx).text_snapshot();
20964 let chars: String = snapshot
20965 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20966 .collect();
20967 let executor = cx.background_executor().clone();
20968
20969 cx.background_spawn(async move {
20970 let mut is_incomplete = false;
20971 let mut completions: Vec<Completion> = Vec::new();
20972 for (scope, snippets) in scopes.into_iter() {
20973 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20974 let mut last_word = chars
20975 .chars()
20976 .take_while(|c| classifier.is_word(*c))
20977 .collect::<String>();
20978 last_word = last_word.chars().rev().collect();
20979
20980 if last_word.is_empty() {
20981 return Ok(CompletionResponse {
20982 completions: vec![],
20983 is_incomplete: true,
20984 });
20985 }
20986
20987 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20988 let to_lsp = |point: &text::Anchor| {
20989 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20990 point_to_lsp(end)
20991 };
20992 let lsp_end = to_lsp(&buffer_position);
20993
20994 let candidates = snippets
20995 .iter()
20996 .enumerate()
20997 .flat_map(|(ix, snippet)| {
20998 snippet
20999 .prefix
21000 .iter()
21001 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21002 })
21003 .collect::<Vec<StringMatchCandidate>>();
21004
21005 const MAX_RESULTS: usize = 100;
21006 let mut matches = fuzzy::match_strings(
21007 &candidates,
21008 &last_word,
21009 last_word.chars().any(|c| c.is_uppercase()),
21010 MAX_RESULTS,
21011 &Default::default(),
21012 executor.clone(),
21013 )
21014 .await;
21015
21016 if matches.len() >= MAX_RESULTS {
21017 is_incomplete = true;
21018 }
21019
21020 // Remove all candidates where the query's start does not match the start of any word in the candidate
21021 if let Some(query_start) = last_word.chars().next() {
21022 matches.retain(|string_match| {
21023 split_words(&string_match.string).any(|word| {
21024 // Check that the first codepoint of the word as lowercase matches the first
21025 // codepoint of the query as lowercase
21026 word.chars()
21027 .flat_map(|codepoint| codepoint.to_lowercase())
21028 .zip(query_start.to_lowercase())
21029 .all(|(word_cp, query_cp)| word_cp == query_cp)
21030 })
21031 });
21032 }
21033
21034 let matched_strings = matches
21035 .into_iter()
21036 .map(|m| m.string)
21037 .collect::<HashSet<_>>();
21038
21039 completions.extend(snippets.iter().filter_map(|snippet| {
21040 let matching_prefix = snippet
21041 .prefix
21042 .iter()
21043 .find(|prefix| matched_strings.contains(*prefix))?;
21044 let start = as_offset - last_word.len();
21045 let start = snapshot.anchor_before(start);
21046 let range = start..buffer_position;
21047 let lsp_start = to_lsp(&start);
21048 let lsp_range = lsp::Range {
21049 start: lsp_start,
21050 end: lsp_end,
21051 };
21052 Some(Completion {
21053 replace_range: range,
21054 new_text: snippet.body.clone(),
21055 source: CompletionSource::Lsp {
21056 insert_range: None,
21057 server_id: LanguageServerId(usize::MAX),
21058 resolved: true,
21059 lsp_completion: Box::new(lsp::CompletionItem {
21060 label: snippet.prefix.first().unwrap().clone(),
21061 kind: Some(CompletionItemKind::SNIPPET),
21062 label_details: snippet.description.as_ref().map(|description| {
21063 lsp::CompletionItemLabelDetails {
21064 detail: Some(description.clone()),
21065 description: None,
21066 }
21067 }),
21068 insert_text_format: Some(InsertTextFormat::SNIPPET),
21069 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21070 lsp::InsertReplaceEdit {
21071 new_text: snippet.body.clone(),
21072 insert: lsp_range,
21073 replace: lsp_range,
21074 },
21075 )),
21076 filter_text: Some(snippet.body.clone()),
21077 sort_text: Some(char::MAX.to_string()),
21078 ..lsp::CompletionItem::default()
21079 }),
21080 lsp_defaults: None,
21081 },
21082 label: CodeLabel {
21083 text: matching_prefix.clone(),
21084 runs: Vec::new(),
21085 filter_range: 0..matching_prefix.len(),
21086 },
21087 icon_path: None,
21088 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21089 single_line: snippet.name.clone().into(),
21090 plain_text: snippet
21091 .description
21092 .clone()
21093 .map(|description| description.into()),
21094 }),
21095 insert_text_mode: None,
21096 confirm: None,
21097 })
21098 }))
21099 }
21100
21101 Ok(CompletionResponse {
21102 completions,
21103 is_incomplete,
21104 })
21105 })
21106}
21107
21108impl CompletionProvider for Entity<Project> {
21109 fn completions(
21110 &self,
21111 _excerpt_id: ExcerptId,
21112 buffer: &Entity<Buffer>,
21113 buffer_position: text::Anchor,
21114 options: CompletionContext,
21115 _window: &mut Window,
21116 cx: &mut Context<Editor>,
21117 ) -> Task<Result<Vec<CompletionResponse>>> {
21118 self.update(cx, |project, cx| {
21119 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21120 let project_completions = project.completions(buffer, buffer_position, options, cx);
21121 cx.background_spawn(async move {
21122 let mut responses = project_completions.await?;
21123 let snippets = snippets.await?;
21124 if !snippets.completions.is_empty() {
21125 responses.push(snippets);
21126 }
21127 Ok(responses)
21128 })
21129 })
21130 }
21131
21132 fn resolve_completions(
21133 &self,
21134 buffer: Entity<Buffer>,
21135 completion_indices: Vec<usize>,
21136 completions: Rc<RefCell<Box<[Completion]>>>,
21137 cx: &mut Context<Editor>,
21138 ) -> Task<Result<bool>> {
21139 self.update(cx, |project, cx| {
21140 project.lsp_store().update(cx, |lsp_store, cx| {
21141 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21142 })
21143 })
21144 }
21145
21146 fn apply_additional_edits_for_completion(
21147 &self,
21148 buffer: Entity<Buffer>,
21149 completions: Rc<RefCell<Box<[Completion]>>>,
21150 completion_index: usize,
21151 push_to_history: bool,
21152 cx: &mut Context<Editor>,
21153 ) -> Task<Result<Option<language::Transaction>>> {
21154 self.update(cx, |project, cx| {
21155 project.lsp_store().update(cx, |lsp_store, cx| {
21156 lsp_store.apply_additional_edits_for_completion(
21157 buffer,
21158 completions,
21159 completion_index,
21160 push_to_history,
21161 cx,
21162 )
21163 })
21164 })
21165 }
21166
21167 fn is_completion_trigger(
21168 &self,
21169 buffer: &Entity<Buffer>,
21170 position: language::Anchor,
21171 text: &str,
21172 trigger_in_words: bool,
21173 menu_is_open: bool,
21174 cx: &mut Context<Editor>,
21175 ) -> bool {
21176 let mut chars = text.chars();
21177 let char = if let Some(char) = chars.next() {
21178 char
21179 } else {
21180 return false;
21181 };
21182 if chars.next().is_some() {
21183 return false;
21184 }
21185
21186 let buffer = buffer.read(cx);
21187 let snapshot = buffer.snapshot();
21188 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21189 return false;
21190 }
21191 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21192 if trigger_in_words && classifier.is_word(char) {
21193 return true;
21194 }
21195
21196 buffer.completion_triggers().contains(text)
21197 }
21198}
21199
21200impl SemanticsProvider for Entity<Project> {
21201 fn hover(
21202 &self,
21203 buffer: &Entity<Buffer>,
21204 position: text::Anchor,
21205 cx: &mut App,
21206 ) -> Option<Task<Vec<project::Hover>>> {
21207 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21208 }
21209
21210 fn document_highlights(
21211 &self,
21212 buffer: &Entity<Buffer>,
21213 position: text::Anchor,
21214 cx: &mut App,
21215 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21216 Some(self.update(cx, |project, cx| {
21217 project.document_highlights(buffer, position, cx)
21218 }))
21219 }
21220
21221 fn definitions(
21222 &self,
21223 buffer: &Entity<Buffer>,
21224 position: text::Anchor,
21225 kind: GotoDefinitionKind,
21226 cx: &mut App,
21227 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21228 Some(self.update(cx, |project, cx| match kind {
21229 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21230 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21231 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21232 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21233 }))
21234 }
21235
21236 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21237 // TODO: make this work for remote projects
21238 self.update(cx, |project, cx| {
21239 if project
21240 .active_debug_session(cx)
21241 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21242 {
21243 return true;
21244 }
21245
21246 buffer.update(cx, |buffer, cx| {
21247 project.any_language_server_supports_inlay_hints(buffer, cx)
21248 })
21249 })
21250 }
21251
21252 fn inline_values(
21253 &self,
21254 buffer_handle: Entity<Buffer>,
21255
21256 range: Range<text::Anchor>,
21257 cx: &mut App,
21258 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21259 self.update(cx, |project, cx| {
21260 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21261
21262 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21263 })
21264 }
21265
21266 fn inlay_hints(
21267 &self,
21268 buffer_handle: Entity<Buffer>,
21269 range: Range<text::Anchor>,
21270 cx: &mut App,
21271 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21272 Some(self.update(cx, |project, cx| {
21273 project.inlay_hints(buffer_handle, range, cx)
21274 }))
21275 }
21276
21277 fn resolve_inlay_hint(
21278 &self,
21279 hint: InlayHint,
21280 buffer_handle: Entity<Buffer>,
21281 server_id: LanguageServerId,
21282 cx: &mut App,
21283 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21284 Some(self.update(cx, |project, cx| {
21285 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21286 }))
21287 }
21288
21289 fn range_for_rename(
21290 &self,
21291 buffer: &Entity<Buffer>,
21292 position: text::Anchor,
21293 cx: &mut App,
21294 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21295 Some(self.update(cx, |project, cx| {
21296 let buffer = buffer.clone();
21297 let task = project.prepare_rename(buffer.clone(), position, cx);
21298 cx.spawn(async move |_, cx| {
21299 Ok(match task.await? {
21300 PrepareRenameResponse::Success(range) => Some(range),
21301 PrepareRenameResponse::InvalidPosition => None,
21302 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21303 // Fallback on using TreeSitter info to determine identifier range
21304 buffer.read_with(cx, |buffer, _| {
21305 let snapshot = buffer.snapshot();
21306 let (range, kind) = snapshot.surrounding_word(position);
21307 if kind != Some(CharKind::Word) {
21308 return None;
21309 }
21310 Some(
21311 snapshot.anchor_before(range.start)
21312 ..snapshot.anchor_after(range.end),
21313 )
21314 })?
21315 }
21316 })
21317 })
21318 }))
21319 }
21320
21321 fn perform_rename(
21322 &self,
21323 buffer: &Entity<Buffer>,
21324 position: text::Anchor,
21325 new_name: String,
21326 cx: &mut App,
21327 ) -> Option<Task<Result<ProjectTransaction>>> {
21328 Some(self.update(cx, |project, cx| {
21329 project.perform_rename(buffer.clone(), position, new_name, cx)
21330 }))
21331 }
21332
21333 fn pull_diagnostics_for_buffer(
21334 &self,
21335 buffer: Entity<Buffer>,
21336 cx: &mut App,
21337 ) -> Task<anyhow::Result<()>> {
21338 let diagnostics = self.update(cx, |project, cx| {
21339 project
21340 .lsp_store()
21341 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21342 });
21343 let project = self.clone();
21344 cx.spawn(async move |cx| {
21345 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21346 project.update(cx, |project, cx| {
21347 project.lsp_store().update(cx, |lsp_store, cx| {
21348 for diagnostics_set in diagnostics {
21349 let LspPullDiagnostics::Response {
21350 server_id,
21351 uri,
21352 diagnostics,
21353 } = diagnostics_set
21354 else {
21355 continue;
21356 };
21357
21358 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21359 let disk_based_sources = adapter
21360 .as_ref()
21361 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21362 .unwrap_or(&[]);
21363 match diagnostics {
21364 PulledDiagnostics::Unchanged { result_id } => {
21365 lsp_store
21366 .merge_diagnostics(
21367 server_id,
21368 lsp::PublishDiagnosticsParams {
21369 uri: uri.clone(),
21370 diagnostics: Vec::new(),
21371 version: None,
21372 },
21373 Some(result_id),
21374 DiagnosticSourceKind::Pulled,
21375 disk_based_sources,
21376 |_, _| true,
21377 cx,
21378 )
21379 .log_err();
21380 }
21381 PulledDiagnostics::Changed {
21382 diagnostics,
21383 result_id,
21384 } => {
21385 lsp_store
21386 .merge_diagnostics(
21387 server_id,
21388 lsp::PublishDiagnosticsParams {
21389 uri: uri.clone(),
21390 diagnostics,
21391 version: None,
21392 },
21393 result_id,
21394 DiagnosticSourceKind::Pulled,
21395 disk_based_sources,
21396 |old_diagnostic, _| match old_diagnostic.source_kind {
21397 DiagnosticSourceKind::Pulled => false,
21398 DiagnosticSourceKind::Other
21399 | DiagnosticSourceKind::Pushed => true,
21400 },
21401 cx,
21402 )
21403 .log_err();
21404 }
21405 }
21406 }
21407 })
21408 })
21409 })
21410 }
21411}
21412
21413fn inlay_hint_settings(
21414 location: Anchor,
21415 snapshot: &MultiBufferSnapshot,
21416 cx: &mut Context<Editor>,
21417) -> InlayHintSettings {
21418 let file = snapshot.file_at(location);
21419 let language = snapshot.language_at(location).map(|l| l.name());
21420 language_settings(language, file, cx).inlay_hints
21421}
21422
21423fn consume_contiguous_rows(
21424 contiguous_row_selections: &mut Vec<Selection<Point>>,
21425 selection: &Selection<Point>,
21426 display_map: &DisplaySnapshot,
21427 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21428) -> (MultiBufferRow, MultiBufferRow) {
21429 contiguous_row_selections.push(selection.clone());
21430 let start_row = MultiBufferRow(selection.start.row);
21431 let mut end_row = ending_row(selection, display_map);
21432
21433 while let Some(next_selection) = selections.peek() {
21434 if next_selection.start.row <= end_row.0 {
21435 end_row = ending_row(next_selection, display_map);
21436 contiguous_row_selections.push(selections.next().unwrap().clone());
21437 } else {
21438 break;
21439 }
21440 }
21441 (start_row, end_row)
21442}
21443
21444fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21445 if next_selection.end.column > 0 || next_selection.is_empty() {
21446 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21447 } else {
21448 MultiBufferRow(next_selection.end.row)
21449 }
21450}
21451
21452impl EditorSnapshot {
21453 pub fn remote_selections_in_range<'a>(
21454 &'a self,
21455 range: &'a Range<Anchor>,
21456 collaboration_hub: &dyn CollaborationHub,
21457 cx: &'a App,
21458 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21459 let participant_names = collaboration_hub.user_names(cx);
21460 let participant_indices = collaboration_hub.user_participant_indices(cx);
21461 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21462 let collaborators_by_replica_id = collaborators_by_peer_id
21463 .values()
21464 .map(|collaborator| (collaborator.replica_id, collaborator))
21465 .collect::<HashMap<_, _>>();
21466 self.buffer_snapshot
21467 .selections_in_range(range, false)
21468 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21469 if replica_id == AGENT_REPLICA_ID {
21470 Some(RemoteSelection {
21471 replica_id,
21472 selection,
21473 cursor_shape,
21474 line_mode,
21475 collaborator_id: CollaboratorId::Agent,
21476 user_name: Some("Agent".into()),
21477 color: cx.theme().players().agent(),
21478 })
21479 } else {
21480 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21481 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21482 let user_name = participant_names.get(&collaborator.user_id).cloned();
21483 Some(RemoteSelection {
21484 replica_id,
21485 selection,
21486 cursor_shape,
21487 line_mode,
21488 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21489 user_name,
21490 color: if let Some(index) = participant_index {
21491 cx.theme().players().color_for_participant(index.0)
21492 } else {
21493 cx.theme().players().absent()
21494 },
21495 })
21496 }
21497 })
21498 }
21499
21500 pub fn hunks_for_ranges(
21501 &self,
21502 ranges: impl IntoIterator<Item = Range<Point>>,
21503 ) -> Vec<MultiBufferDiffHunk> {
21504 let mut hunks = Vec::new();
21505 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21506 HashMap::default();
21507 for query_range in ranges {
21508 let query_rows =
21509 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21510 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21511 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21512 ) {
21513 // Include deleted hunks that are adjacent to the query range, because
21514 // otherwise they would be missed.
21515 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21516 if hunk.status().is_deleted() {
21517 intersects_range |= hunk.row_range.start == query_rows.end;
21518 intersects_range |= hunk.row_range.end == query_rows.start;
21519 }
21520 if intersects_range {
21521 if !processed_buffer_rows
21522 .entry(hunk.buffer_id)
21523 .or_default()
21524 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21525 {
21526 continue;
21527 }
21528 hunks.push(hunk);
21529 }
21530 }
21531 }
21532
21533 hunks
21534 }
21535
21536 fn display_diff_hunks_for_rows<'a>(
21537 &'a self,
21538 display_rows: Range<DisplayRow>,
21539 folded_buffers: &'a HashSet<BufferId>,
21540 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21541 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21542 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21543
21544 self.buffer_snapshot
21545 .diff_hunks_in_range(buffer_start..buffer_end)
21546 .filter_map(|hunk| {
21547 if folded_buffers.contains(&hunk.buffer_id) {
21548 return None;
21549 }
21550
21551 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21552 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21553
21554 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21555 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21556
21557 let display_hunk = if hunk_display_start.column() != 0 {
21558 DisplayDiffHunk::Folded {
21559 display_row: hunk_display_start.row(),
21560 }
21561 } else {
21562 let mut end_row = hunk_display_end.row();
21563 if hunk_display_end.column() > 0 {
21564 end_row.0 += 1;
21565 }
21566 let is_created_file = hunk.is_created_file();
21567 DisplayDiffHunk::Unfolded {
21568 status: hunk.status(),
21569 diff_base_byte_range: hunk.diff_base_byte_range,
21570 display_row_range: hunk_display_start.row()..end_row,
21571 multi_buffer_range: Anchor::range_in_buffer(
21572 hunk.excerpt_id,
21573 hunk.buffer_id,
21574 hunk.buffer_range,
21575 ),
21576 is_created_file,
21577 }
21578 };
21579
21580 Some(display_hunk)
21581 })
21582 }
21583
21584 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21585 self.display_snapshot.buffer_snapshot.language_at(position)
21586 }
21587
21588 pub fn is_focused(&self) -> bool {
21589 self.is_focused
21590 }
21591
21592 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21593 self.placeholder_text.as_ref()
21594 }
21595
21596 pub fn scroll_position(&self) -> gpui::Point<f32> {
21597 self.scroll_anchor.scroll_position(&self.display_snapshot)
21598 }
21599
21600 fn gutter_dimensions(
21601 &self,
21602 font_id: FontId,
21603 font_size: Pixels,
21604 max_line_number_width: Pixels,
21605 cx: &App,
21606 ) -> Option<GutterDimensions> {
21607 if !self.show_gutter {
21608 return None;
21609 }
21610
21611 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21612 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21613
21614 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21615 matches!(
21616 ProjectSettings::get_global(cx).git.git_gutter,
21617 Some(GitGutterSetting::TrackedFiles)
21618 )
21619 });
21620 let gutter_settings = EditorSettings::get_global(cx).gutter;
21621 let show_line_numbers = self
21622 .show_line_numbers
21623 .unwrap_or(gutter_settings.line_numbers);
21624 let line_gutter_width = if show_line_numbers {
21625 // Avoid flicker-like gutter resizes when the line number gains another digit by
21626 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21627 let min_width_for_number_on_gutter =
21628 ch_advance * gutter_settings.min_line_number_digits as f32;
21629 max_line_number_width.max(min_width_for_number_on_gutter)
21630 } else {
21631 0.0.into()
21632 };
21633
21634 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21635 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21636
21637 let git_blame_entries_width =
21638 self.git_blame_gutter_max_author_length
21639 .map(|max_author_length| {
21640 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21641 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21642
21643 /// The number of characters to dedicate to gaps and margins.
21644 const SPACING_WIDTH: usize = 4;
21645
21646 let max_char_count = max_author_length.min(renderer.max_author_length())
21647 + ::git::SHORT_SHA_LENGTH
21648 + MAX_RELATIVE_TIMESTAMP.len()
21649 + SPACING_WIDTH;
21650
21651 ch_advance * max_char_count
21652 });
21653
21654 let is_singleton = self.buffer_snapshot.is_singleton();
21655
21656 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21657 left_padding += if !is_singleton {
21658 ch_width * 4.0
21659 } else if show_runnables || show_breakpoints {
21660 ch_width * 3.0
21661 } else if show_git_gutter && show_line_numbers {
21662 ch_width * 2.0
21663 } else if show_git_gutter || show_line_numbers {
21664 ch_width
21665 } else {
21666 px(0.)
21667 };
21668
21669 let shows_folds = is_singleton && gutter_settings.folds;
21670
21671 let right_padding = if shows_folds && show_line_numbers {
21672 ch_width * 4.0
21673 } else if shows_folds || (!is_singleton && show_line_numbers) {
21674 ch_width * 3.0
21675 } else if show_line_numbers {
21676 ch_width
21677 } else {
21678 px(0.)
21679 };
21680
21681 Some(GutterDimensions {
21682 left_padding,
21683 right_padding,
21684 width: line_gutter_width + left_padding + right_padding,
21685 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21686 git_blame_entries_width,
21687 })
21688 }
21689
21690 pub fn render_crease_toggle(
21691 &self,
21692 buffer_row: MultiBufferRow,
21693 row_contains_cursor: bool,
21694 editor: Entity<Editor>,
21695 window: &mut Window,
21696 cx: &mut App,
21697 ) -> Option<AnyElement> {
21698 let folded = self.is_line_folded(buffer_row);
21699 let mut is_foldable = false;
21700
21701 if let Some(crease) = self
21702 .crease_snapshot
21703 .query_row(buffer_row, &self.buffer_snapshot)
21704 {
21705 is_foldable = true;
21706 match crease {
21707 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21708 if let Some(render_toggle) = render_toggle {
21709 let toggle_callback =
21710 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21711 if folded {
21712 editor.update(cx, |editor, cx| {
21713 editor.fold_at(buffer_row, window, cx)
21714 });
21715 } else {
21716 editor.update(cx, |editor, cx| {
21717 editor.unfold_at(buffer_row, window, cx)
21718 });
21719 }
21720 });
21721 return Some((render_toggle)(
21722 buffer_row,
21723 folded,
21724 toggle_callback,
21725 window,
21726 cx,
21727 ));
21728 }
21729 }
21730 }
21731 }
21732
21733 is_foldable |= self.starts_indent(buffer_row);
21734
21735 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21736 Some(
21737 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21738 .toggle_state(folded)
21739 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21740 if folded {
21741 this.unfold_at(buffer_row, window, cx);
21742 } else {
21743 this.fold_at(buffer_row, window, cx);
21744 }
21745 }))
21746 .into_any_element(),
21747 )
21748 } else {
21749 None
21750 }
21751 }
21752
21753 pub fn render_crease_trailer(
21754 &self,
21755 buffer_row: MultiBufferRow,
21756 window: &mut Window,
21757 cx: &mut App,
21758 ) -> Option<AnyElement> {
21759 let folded = self.is_line_folded(buffer_row);
21760 if let Crease::Inline { render_trailer, .. } = self
21761 .crease_snapshot
21762 .query_row(buffer_row, &self.buffer_snapshot)?
21763 {
21764 let render_trailer = render_trailer.as_ref()?;
21765 Some(render_trailer(buffer_row, folded, window, cx))
21766 } else {
21767 None
21768 }
21769 }
21770}
21771
21772impl Deref for EditorSnapshot {
21773 type Target = DisplaySnapshot;
21774
21775 fn deref(&self) -> &Self::Target {
21776 &self.display_snapshot
21777 }
21778}
21779
21780#[derive(Clone, Debug, PartialEq, Eq)]
21781pub enum EditorEvent {
21782 InputIgnored {
21783 text: Arc<str>,
21784 },
21785 InputHandled {
21786 utf16_range_to_replace: Option<Range<isize>>,
21787 text: Arc<str>,
21788 },
21789 ExcerptsAdded {
21790 buffer: Entity<Buffer>,
21791 predecessor: ExcerptId,
21792 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21793 },
21794 ExcerptsRemoved {
21795 ids: Vec<ExcerptId>,
21796 removed_buffer_ids: Vec<BufferId>,
21797 },
21798 BufferFoldToggled {
21799 ids: Vec<ExcerptId>,
21800 folded: bool,
21801 },
21802 ExcerptsEdited {
21803 ids: Vec<ExcerptId>,
21804 },
21805 ExcerptsExpanded {
21806 ids: Vec<ExcerptId>,
21807 },
21808 BufferEdited,
21809 Edited {
21810 transaction_id: clock::Lamport,
21811 },
21812 Reparsed(BufferId),
21813 Focused,
21814 FocusedIn,
21815 Blurred,
21816 DirtyChanged,
21817 Saved,
21818 TitleChanged,
21819 DiffBaseChanged,
21820 SelectionsChanged {
21821 local: bool,
21822 },
21823 ScrollPositionChanged {
21824 local: bool,
21825 autoscroll: bool,
21826 },
21827 Closed,
21828 TransactionUndone {
21829 transaction_id: clock::Lamport,
21830 },
21831 TransactionBegun {
21832 transaction_id: clock::Lamport,
21833 },
21834 Reloaded,
21835 CursorShapeChanged,
21836 PushedToNavHistory {
21837 anchor: Anchor,
21838 is_deactivate: bool,
21839 },
21840}
21841
21842impl EventEmitter<EditorEvent> for Editor {}
21843
21844impl Focusable for Editor {
21845 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21846 self.focus_handle.clone()
21847 }
21848}
21849
21850impl Render for Editor {
21851 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21852 let settings = ThemeSettings::get_global(cx);
21853
21854 let mut text_style = match self.mode {
21855 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21856 color: cx.theme().colors().editor_foreground,
21857 font_family: settings.ui_font.family.clone(),
21858 font_features: settings.ui_font.features.clone(),
21859 font_fallbacks: settings.ui_font.fallbacks.clone(),
21860 font_size: rems(0.875).into(),
21861 font_weight: settings.ui_font.weight,
21862 line_height: relative(settings.buffer_line_height.value()),
21863 ..Default::default()
21864 },
21865 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21866 color: cx.theme().colors().editor_foreground,
21867 font_family: settings.buffer_font.family.clone(),
21868 font_features: settings.buffer_font.features.clone(),
21869 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21870 font_size: settings.buffer_font_size(cx).into(),
21871 font_weight: settings.buffer_font.weight,
21872 line_height: relative(settings.buffer_line_height.value()),
21873 ..Default::default()
21874 },
21875 };
21876 if let Some(text_style_refinement) = &self.text_style_refinement {
21877 text_style.refine(text_style_refinement)
21878 }
21879
21880 let background = match self.mode {
21881 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21882 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21883 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21884 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21885 };
21886
21887 EditorElement::new(
21888 &cx.entity(),
21889 EditorStyle {
21890 background,
21891 local_player: cx.theme().players().local(),
21892 text: text_style,
21893 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21894 syntax: cx.theme().syntax().clone(),
21895 status: cx.theme().status().clone(),
21896 inlay_hints_style: make_inlay_hints_style(cx),
21897 inline_completion_styles: make_suggestion_styles(cx),
21898 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21899 show_underlines: !self.mode.is_minimap(),
21900 },
21901 )
21902 }
21903}
21904
21905impl EntityInputHandler for Editor {
21906 fn text_for_range(
21907 &mut self,
21908 range_utf16: Range<usize>,
21909 adjusted_range: &mut Option<Range<usize>>,
21910 _: &mut Window,
21911 cx: &mut Context<Self>,
21912 ) -> Option<String> {
21913 let snapshot = self.buffer.read(cx).read(cx);
21914 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21915 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21916 if (start.0..end.0) != range_utf16 {
21917 adjusted_range.replace(start.0..end.0);
21918 }
21919 Some(snapshot.text_for_range(start..end).collect())
21920 }
21921
21922 fn selected_text_range(
21923 &mut self,
21924 ignore_disabled_input: bool,
21925 _: &mut Window,
21926 cx: &mut Context<Self>,
21927 ) -> Option<UTF16Selection> {
21928 // Prevent the IME menu from appearing when holding down an alphabetic key
21929 // while input is disabled.
21930 if !ignore_disabled_input && !self.input_enabled {
21931 return None;
21932 }
21933
21934 let selection = self.selections.newest::<OffsetUtf16>(cx);
21935 let range = selection.range();
21936
21937 Some(UTF16Selection {
21938 range: range.start.0..range.end.0,
21939 reversed: selection.reversed,
21940 })
21941 }
21942
21943 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21944 let snapshot = self.buffer.read(cx).read(cx);
21945 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21946 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21947 }
21948
21949 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21950 self.clear_highlights::<InputComposition>(cx);
21951 self.ime_transaction.take();
21952 }
21953
21954 fn replace_text_in_range(
21955 &mut self,
21956 range_utf16: Option<Range<usize>>,
21957 text: &str,
21958 window: &mut Window,
21959 cx: &mut Context<Self>,
21960 ) {
21961 if !self.input_enabled {
21962 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21963 return;
21964 }
21965
21966 self.transact(window, cx, |this, window, cx| {
21967 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21968 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21969 Some(this.selection_replacement_ranges(range_utf16, cx))
21970 } else {
21971 this.marked_text_ranges(cx)
21972 };
21973
21974 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21975 let newest_selection_id = this.selections.newest_anchor().id;
21976 this.selections
21977 .all::<OffsetUtf16>(cx)
21978 .iter()
21979 .zip(ranges_to_replace.iter())
21980 .find_map(|(selection, range)| {
21981 if selection.id == newest_selection_id {
21982 Some(
21983 (range.start.0 as isize - selection.head().0 as isize)
21984 ..(range.end.0 as isize - selection.head().0 as isize),
21985 )
21986 } else {
21987 None
21988 }
21989 })
21990 });
21991
21992 cx.emit(EditorEvent::InputHandled {
21993 utf16_range_to_replace: range_to_replace,
21994 text: text.into(),
21995 });
21996
21997 if let Some(new_selected_ranges) = new_selected_ranges {
21998 this.change_selections(None, window, cx, |selections| {
21999 selections.select_ranges(new_selected_ranges)
22000 });
22001 this.backspace(&Default::default(), window, cx);
22002 }
22003
22004 this.handle_input(text, window, cx);
22005 });
22006
22007 if let Some(transaction) = self.ime_transaction {
22008 self.buffer.update(cx, |buffer, cx| {
22009 buffer.group_until_transaction(transaction, cx);
22010 });
22011 }
22012
22013 self.unmark_text(window, cx);
22014 }
22015
22016 fn replace_and_mark_text_in_range(
22017 &mut self,
22018 range_utf16: Option<Range<usize>>,
22019 text: &str,
22020 new_selected_range_utf16: Option<Range<usize>>,
22021 window: &mut Window,
22022 cx: &mut Context<Self>,
22023 ) {
22024 if !self.input_enabled {
22025 return;
22026 }
22027
22028 let transaction = self.transact(window, cx, |this, window, cx| {
22029 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22030 let snapshot = this.buffer.read(cx).read(cx);
22031 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22032 for marked_range in &mut marked_ranges {
22033 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22034 marked_range.start.0 += relative_range_utf16.start;
22035 marked_range.start =
22036 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22037 marked_range.end =
22038 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22039 }
22040 }
22041 Some(marked_ranges)
22042 } else if let Some(range_utf16) = range_utf16 {
22043 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22044 Some(this.selection_replacement_ranges(range_utf16, cx))
22045 } else {
22046 None
22047 };
22048
22049 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22050 let newest_selection_id = this.selections.newest_anchor().id;
22051 this.selections
22052 .all::<OffsetUtf16>(cx)
22053 .iter()
22054 .zip(ranges_to_replace.iter())
22055 .find_map(|(selection, range)| {
22056 if selection.id == newest_selection_id {
22057 Some(
22058 (range.start.0 as isize - selection.head().0 as isize)
22059 ..(range.end.0 as isize - selection.head().0 as isize),
22060 )
22061 } else {
22062 None
22063 }
22064 })
22065 });
22066
22067 cx.emit(EditorEvent::InputHandled {
22068 utf16_range_to_replace: range_to_replace,
22069 text: text.into(),
22070 });
22071
22072 if let Some(ranges) = ranges_to_replace {
22073 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22074 }
22075
22076 let marked_ranges = {
22077 let snapshot = this.buffer.read(cx).read(cx);
22078 this.selections
22079 .disjoint_anchors()
22080 .iter()
22081 .map(|selection| {
22082 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22083 })
22084 .collect::<Vec<_>>()
22085 };
22086
22087 if text.is_empty() {
22088 this.unmark_text(window, cx);
22089 } else {
22090 this.highlight_text::<InputComposition>(
22091 marked_ranges.clone(),
22092 HighlightStyle {
22093 underline: Some(UnderlineStyle {
22094 thickness: px(1.),
22095 color: None,
22096 wavy: false,
22097 }),
22098 ..Default::default()
22099 },
22100 cx,
22101 );
22102 }
22103
22104 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22105 let use_autoclose = this.use_autoclose;
22106 let use_auto_surround = this.use_auto_surround;
22107 this.set_use_autoclose(false);
22108 this.set_use_auto_surround(false);
22109 this.handle_input(text, window, cx);
22110 this.set_use_autoclose(use_autoclose);
22111 this.set_use_auto_surround(use_auto_surround);
22112
22113 if let Some(new_selected_range) = new_selected_range_utf16 {
22114 let snapshot = this.buffer.read(cx).read(cx);
22115 let new_selected_ranges = marked_ranges
22116 .into_iter()
22117 .map(|marked_range| {
22118 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22119 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22120 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22121 snapshot.clip_offset_utf16(new_start, Bias::Left)
22122 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22123 })
22124 .collect::<Vec<_>>();
22125
22126 drop(snapshot);
22127 this.change_selections(None, window, cx, |selections| {
22128 selections.select_ranges(new_selected_ranges)
22129 });
22130 }
22131 });
22132
22133 self.ime_transaction = self.ime_transaction.or(transaction);
22134 if let Some(transaction) = self.ime_transaction {
22135 self.buffer.update(cx, |buffer, cx| {
22136 buffer.group_until_transaction(transaction, cx);
22137 });
22138 }
22139
22140 if self.text_highlights::<InputComposition>(cx).is_none() {
22141 self.ime_transaction.take();
22142 }
22143 }
22144
22145 fn bounds_for_range(
22146 &mut self,
22147 range_utf16: Range<usize>,
22148 element_bounds: gpui::Bounds<Pixels>,
22149 window: &mut Window,
22150 cx: &mut Context<Self>,
22151 ) -> Option<gpui::Bounds<Pixels>> {
22152 let text_layout_details = self.text_layout_details(window);
22153 let gpui::Size {
22154 width: em_width,
22155 height: line_height,
22156 } = self.character_size(window);
22157
22158 let snapshot = self.snapshot(window, cx);
22159 let scroll_position = snapshot.scroll_position();
22160 let scroll_left = scroll_position.x * em_width;
22161
22162 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22163 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22164 + self.gutter_dimensions.width
22165 + self.gutter_dimensions.margin;
22166 let y = line_height * (start.row().as_f32() - scroll_position.y);
22167
22168 Some(Bounds {
22169 origin: element_bounds.origin + point(x, y),
22170 size: size(em_width, line_height),
22171 })
22172 }
22173
22174 fn character_index_for_point(
22175 &mut self,
22176 point: gpui::Point<Pixels>,
22177 _window: &mut Window,
22178 _cx: &mut Context<Self>,
22179 ) -> Option<usize> {
22180 let position_map = self.last_position_map.as_ref()?;
22181 if !position_map.text_hitbox.contains(&point) {
22182 return None;
22183 }
22184 let display_point = position_map.point_for_position(point).previous_valid;
22185 let anchor = position_map
22186 .snapshot
22187 .display_point_to_anchor(display_point, Bias::Left);
22188 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22189 Some(utf16_offset.0)
22190 }
22191}
22192
22193trait SelectionExt {
22194 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22195 fn spanned_rows(
22196 &self,
22197 include_end_if_at_line_start: bool,
22198 map: &DisplaySnapshot,
22199 ) -> Range<MultiBufferRow>;
22200}
22201
22202impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22203 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22204 let start = self
22205 .start
22206 .to_point(&map.buffer_snapshot)
22207 .to_display_point(map);
22208 let end = self
22209 .end
22210 .to_point(&map.buffer_snapshot)
22211 .to_display_point(map);
22212 if self.reversed {
22213 end..start
22214 } else {
22215 start..end
22216 }
22217 }
22218
22219 fn spanned_rows(
22220 &self,
22221 include_end_if_at_line_start: bool,
22222 map: &DisplaySnapshot,
22223 ) -> Range<MultiBufferRow> {
22224 let start = self.start.to_point(&map.buffer_snapshot);
22225 let mut end = self.end.to_point(&map.buffer_snapshot);
22226 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22227 end.row -= 1;
22228 }
22229
22230 let buffer_start = map.prev_line_boundary(start).0;
22231 let buffer_end = map.next_line_boundary(end).0;
22232 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22233 }
22234}
22235
22236impl<T: InvalidationRegion> InvalidationStack<T> {
22237 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22238 where
22239 S: Clone + ToOffset,
22240 {
22241 while let Some(region) = self.last() {
22242 let all_selections_inside_invalidation_ranges =
22243 if selections.len() == region.ranges().len() {
22244 selections
22245 .iter()
22246 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22247 .all(|(selection, invalidation_range)| {
22248 let head = selection.head().to_offset(buffer);
22249 invalidation_range.start <= head && invalidation_range.end >= head
22250 })
22251 } else {
22252 false
22253 };
22254
22255 if all_selections_inside_invalidation_ranges {
22256 break;
22257 } else {
22258 self.pop();
22259 }
22260 }
22261 }
22262}
22263
22264impl<T> Default for InvalidationStack<T> {
22265 fn default() -> Self {
22266 Self(Default::default())
22267 }
22268}
22269
22270impl<T> Deref for InvalidationStack<T> {
22271 type Target = Vec<T>;
22272
22273 fn deref(&self) -> &Self::Target {
22274 &self.0
22275 }
22276}
22277
22278impl<T> DerefMut for InvalidationStack<T> {
22279 fn deref_mut(&mut self) -> &mut Self::Target {
22280 &mut self.0
22281 }
22282}
22283
22284impl InvalidationRegion for SnippetState {
22285 fn ranges(&self) -> &[Range<Anchor>] {
22286 &self.ranges[self.active_index]
22287 }
22288}
22289
22290fn inline_completion_edit_text(
22291 current_snapshot: &BufferSnapshot,
22292 edits: &[(Range<Anchor>, String)],
22293 edit_preview: &EditPreview,
22294 include_deletions: bool,
22295 cx: &App,
22296) -> HighlightedText {
22297 let edits = edits
22298 .iter()
22299 .map(|(anchor, text)| {
22300 (
22301 anchor.start.text_anchor..anchor.end.text_anchor,
22302 text.clone(),
22303 )
22304 })
22305 .collect::<Vec<_>>();
22306
22307 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22308}
22309
22310pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22311 match severity {
22312 lsp::DiagnosticSeverity::ERROR => colors.error,
22313 lsp::DiagnosticSeverity::WARNING => colors.warning,
22314 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22315 lsp::DiagnosticSeverity::HINT => colors.info,
22316 _ => colors.ignored,
22317 }
22318}
22319
22320pub fn styled_runs_for_code_label<'a>(
22321 label: &'a CodeLabel,
22322 syntax_theme: &'a theme::SyntaxTheme,
22323) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22324 let fade_out = HighlightStyle {
22325 fade_out: Some(0.35),
22326 ..Default::default()
22327 };
22328
22329 let mut prev_end = label.filter_range.end;
22330 label
22331 .runs
22332 .iter()
22333 .enumerate()
22334 .flat_map(move |(ix, (range, highlight_id))| {
22335 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22336 style
22337 } else {
22338 return Default::default();
22339 };
22340 let mut muted_style = style;
22341 muted_style.highlight(fade_out);
22342
22343 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22344 if range.start >= label.filter_range.end {
22345 if range.start > prev_end {
22346 runs.push((prev_end..range.start, fade_out));
22347 }
22348 runs.push((range.clone(), muted_style));
22349 } else if range.end <= label.filter_range.end {
22350 runs.push((range.clone(), style));
22351 } else {
22352 runs.push((range.start..label.filter_range.end, style));
22353 runs.push((label.filter_range.end..range.end, muted_style));
22354 }
22355 prev_end = cmp::max(prev_end, range.end);
22356
22357 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22358 runs.push((prev_end..label.text.len(), fade_out));
22359 }
22360
22361 runs
22362 })
22363}
22364
22365pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22366 let mut prev_index = 0;
22367 let mut prev_codepoint: Option<char> = None;
22368 text.char_indices()
22369 .chain([(text.len(), '\0')])
22370 .filter_map(move |(index, codepoint)| {
22371 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22372 let is_boundary = index == text.len()
22373 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22374 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22375 if is_boundary {
22376 let chunk = &text[prev_index..index];
22377 prev_index = index;
22378 Some(chunk)
22379 } else {
22380 None
22381 }
22382 })
22383}
22384
22385pub trait RangeToAnchorExt: Sized {
22386 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22387
22388 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22389 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22390 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22391 }
22392}
22393
22394impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22395 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22396 let start_offset = self.start.to_offset(snapshot);
22397 let end_offset = self.end.to_offset(snapshot);
22398 if start_offset == end_offset {
22399 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22400 } else {
22401 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22402 }
22403 }
22404}
22405
22406pub trait RowExt {
22407 fn as_f32(&self) -> f32;
22408
22409 fn next_row(&self) -> Self;
22410
22411 fn previous_row(&self) -> Self;
22412
22413 fn minus(&self, other: Self) -> u32;
22414}
22415
22416impl RowExt for DisplayRow {
22417 fn as_f32(&self) -> f32 {
22418 self.0 as f32
22419 }
22420
22421 fn next_row(&self) -> Self {
22422 Self(self.0 + 1)
22423 }
22424
22425 fn previous_row(&self) -> Self {
22426 Self(self.0.saturating_sub(1))
22427 }
22428
22429 fn minus(&self, other: Self) -> u32 {
22430 self.0 - other.0
22431 }
22432}
22433
22434impl RowExt for MultiBufferRow {
22435 fn as_f32(&self) -> f32 {
22436 self.0 as f32
22437 }
22438
22439 fn next_row(&self) -> Self {
22440 Self(self.0 + 1)
22441 }
22442
22443 fn previous_row(&self) -> Self {
22444 Self(self.0.saturating_sub(1))
22445 }
22446
22447 fn minus(&self, other: Self) -> u32 {
22448 self.0 - other.0
22449 }
22450}
22451
22452trait RowRangeExt {
22453 type Row;
22454
22455 fn len(&self) -> usize;
22456
22457 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22458}
22459
22460impl RowRangeExt for Range<MultiBufferRow> {
22461 type Row = MultiBufferRow;
22462
22463 fn len(&self) -> usize {
22464 (self.end.0 - self.start.0) as usize
22465 }
22466
22467 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22468 (self.start.0..self.end.0).map(MultiBufferRow)
22469 }
22470}
22471
22472impl RowRangeExt for Range<DisplayRow> {
22473 type Row = DisplayRow;
22474
22475 fn len(&self) -> usize {
22476 (self.end.0 - self.start.0) as usize
22477 }
22478
22479 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22480 (self.start.0..self.end.0).map(DisplayRow)
22481 }
22482}
22483
22484/// If select range has more than one line, we
22485/// just point the cursor to range.start.
22486fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22487 if range.start.row == range.end.row {
22488 range
22489 } else {
22490 range.start..range.start
22491 }
22492}
22493pub struct KillRing(ClipboardItem);
22494impl Global for KillRing {}
22495
22496const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22497
22498enum BreakpointPromptEditAction {
22499 Log,
22500 Condition,
22501 HitCondition,
22502}
22503
22504struct BreakpointPromptEditor {
22505 pub(crate) prompt: Entity<Editor>,
22506 editor: WeakEntity<Editor>,
22507 breakpoint_anchor: Anchor,
22508 breakpoint: Breakpoint,
22509 edit_action: BreakpointPromptEditAction,
22510 block_ids: HashSet<CustomBlockId>,
22511 editor_margins: Arc<Mutex<EditorMargins>>,
22512 _subscriptions: Vec<Subscription>,
22513}
22514
22515impl BreakpointPromptEditor {
22516 const MAX_LINES: u8 = 4;
22517
22518 fn new(
22519 editor: WeakEntity<Editor>,
22520 breakpoint_anchor: Anchor,
22521 breakpoint: Breakpoint,
22522 edit_action: BreakpointPromptEditAction,
22523 window: &mut Window,
22524 cx: &mut Context<Self>,
22525 ) -> Self {
22526 let base_text = match edit_action {
22527 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22528 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22529 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22530 }
22531 .map(|msg| msg.to_string())
22532 .unwrap_or_default();
22533
22534 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22535 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22536
22537 let prompt = cx.new(|cx| {
22538 let mut prompt = Editor::new(
22539 EditorMode::AutoHeight {
22540 max_lines: Self::MAX_LINES as usize,
22541 },
22542 buffer,
22543 None,
22544 window,
22545 cx,
22546 );
22547 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22548 prompt.set_show_cursor_when_unfocused(false, cx);
22549 prompt.set_placeholder_text(
22550 match edit_action {
22551 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22552 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22553 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22554 },
22555 cx,
22556 );
22557
22558 prompt
22559 });
22560
22561 Self {
22562 prompt,
22563 editor,
22564 breakpoint_anchor,
22565 breakpoint,
22566 edit_action,
22567 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22568 block_ids: Default::default(),
22569 _subscriptions: vec![],
22570 }
22571 }
22572
22573 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22574 self.block_ids.extend(block_ids)
22575 }
22576
22577 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22578 if let Some(editor) = self.editor.upgrade() {
22579 let message = self
22580 .prompt
22581 .read(cx)
22582 .buffer
22583 .read(cx)
22584 .as_singleton()
22585 .expect("A multi buffer in breakpoint prompt isn't possible")
22586 .read(cx)
22587 .as_rope()
22588 .to_string();
22589
22590 editor.update(cx, |editor, cx| {
22591 editor.edit_breakpoint_at_anchor(
22592 self.breakpoint_anchor,
22593 self.breakpoint.clone(),
22594 match self.edit_action {
22595 BreakpointPromptEditAction::Log => {
22596 BreakpointEditAction::EditLogMessage(message.into())
22597 }
22598 BreakpointPromptEditAction::Condition => {
22599 BreakpointEditAction::EditCondition(message.into())
22600 }
22601 BreakpointPromptEditAction::HitCondition => {
22602 BreakpointEditAction::EditHitCondition(message.into())
22603 }
22604 },
22605 cx,
22606 );
22607
22608 editor.remove_blocks(self.block_ids.clone(), None, cx);
22609 cx.focus_self(window);
22610 });
22611 }
22612 }
22613
22614 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22615 self.editor
22616 .update(cx, |editor, cx| {
22617 editor.remove_blocks(self.block_ids.clone(), None, cx);
22618 window.focus(&editor.focus_handle);
22619 })
22620 .log_err();
22621 }
22622
22623 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22624 let settings = ThemeSettings::get_global(cx);
22625 let text_style = TextStyle {
22626 color: if self.prompt.read(cx).read_only(cx) {
22627 cx.theme().colors().text_disabled
22628 } else {
22629 cx.theme().colors().text
22630 },
22631 font_family: settings.buffer_font.family.clone(),
22632 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22633 font_size: settings.buffer_font_size(cx).into(),
22634 font_weight: settings.buffer_font.weight,
22635 line_height: relative(settings.buffer_line_height.value()),
22636 ..Default::default()
22637 };
22638 EditorElement::new(
22639 &self.prompt,
22640 EditorStyle {
22641 background: cx.theme().colors().editor_background,
22642 local_player: cx.theme().players().local(),
22643 text: text_style,
22644 ..Default::default()
22645 },
22646 )
22647 }
22648}
22649
22650impl Render for BreakpointPromptEditor {
22651 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22652 let editor_margins = *self.editor_margins.lock();
22653 let gutter_dimensions = editor_margins.gutter;
22654 h_flex()
22655 .key_context("Editor")
22656 .bg(cx.theme().colors().editor_background)
22657 .border_y_1()
22658 .border_color(cx.theme().status().info_border)
22659 .size_full()
22660 .py(window.line_height() / 2.5)
22661 .on_action(cx.listener(Self::confirm))
22662 .on_action(cx.listener(Self::cancel))
22663 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22664 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22665 }
22666}
22667
22668impl Focusable for BreakpointPromptEditor {
22669 fn focus_handle(&self, cx: &App) -> FocusHandle {
22670 self.prompt.focus_handle(cx)
22671 }
22672}
22673
22674fn all_edits_insertions_or_deletions(
22675 edits: &Vec<(Range<Anchor>, String)>,
22676 snapshot: &MultiBufferSnapshot,
22677) -> bool {
22678 let mut all_insertions = true;
22679 let mut all_deletions = true;
22680
22681 for (range, new_text) in edits.iter() {
22682 let range_is_empty = range.to_offset(&snapshot).is_empty();
22683 let text_is_empty = new_text.is_empty();
22684
22685 if range_is_empty != text_is_empty {
22686 if range_is_empty {
22687 all_deletions = false;
22688 } else {
22689 all_insertions = false;
22690 }
22691 } else {
22692 return false;
22693 }
22694
22695 if !all_insertions && !all_deletions {
22696 return false;
22697 }
22698 }
22699 all_insertions || all_deletions
22700}
22701
22702struct MissingEditPredictionKeybindingTooltip;
22703
22704impl Render for MissingEditPredictionKeybindingTooltip {
22705 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22706 ui::tooltip_container(window, cx, |container, _, cx| {
22707 container
22708 .flex_shrink_0()
22709 .max_w_80()
22710 .min_h(rems_from_px(124.))
22711 .justify_between()
22712 .child(
22713 v_flex()
22714 .flex_1()
22715 .text_ui_sm(cx)
22716 .child(Label::new("Conflict with Accept Keybinding"))
22717 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22718 )
22719 .child(
22720 h_flex()
22721 .pb_1()
22722 .gap_1()
22723 .items_end()
22724 .w_full()
22725 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22726 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22727 }))
22728 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22729 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22730 })),
22731 )
22732 })
22733 }
22734}
22735
22736#[derive(Debug, Clone, Copy, PartialEq)]
22737pub struct LineHighlight {
22738 pub background: Background,
22739 pub border: Option<gpui::Hsla>,
22740 pub include_gutter: bool,
22741 pub type_id: Option<TypeId>,
22742}
22743
22744fn render_diff_hunk_controls(
22745 row: u32,
22746 status: &DiffHunkStatus,
22747 hunk_range: Range<Anchor>,
22748 is_created_file: bool,
22749 line_height: Pixels,
22750 editor: &Entity<Editor>,
22751 _window: &mut Window,
22752 cx: &mut App,
22753) -> AnyElement {
22754 h_flex()
22755 .h(line_height)
22756 .mr_1()
22757 .gap_1()
22758 .px_0p5()
22759 .pb_1()
22760 .border_x_1()
22761 .border_b_1()
22762 .border_color(cx.theme().colors().border_variant)
22763 .rounded_b_lg()
22764 .bg(cx.theme().colors().editor_background)
22765 .gap_1()
22766 .block_mouse_except_scroll()
22767 .shadow_md()
22768 .child(if status.has_secondary_hunk() {
22769 Button::new(("stage", row as u64), "Stage")
22770 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22771 .tooltip({
22772 let focus_handle = editor.focus_handle(cx);
22773 move |window, cx| {
22774 Tooltip::for_action_in(
22775 "Stage Hunk",
22776 &::git::ToggleStaged,
22777 &focus_handle,
22778 window,
22779 cx,
22780 )
22781 }
22782 })
22783 .on_click({
22784 let editor = editor.clone();
22785 move |_event, _window, cx| {
22786 editor.update(cx, |editor, cx| {
22787 editor.stage_or_unstage_diff_hunks(
22788 true,
22789 vec![hunk_range.start..hunk_range.start],
22790 cx,
22791 );
22792 });
22793 }
22794 })
22795 } else {
22796 Button::new(("unstage", row as u64), "Unstage")
22797 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22798 .tooltip({
22799 let focus_handle = editor.focus_handle(cx);
22800 move |window, cx| {
22801 Tooltip::for_action_in(
22802 "Unstage Hunk",
22803 &::git::ToggleStaged,
22804 &focus_handle,
22805 window,
22806 cx,
22807 )
22808 }
22809 })
22810 .on_click({
22811 let editor = editor.clone();
22812 move |_event, _window, cx| {
22813 editor.update(cx, |editor, cx| {
22814 editor.stage_or_unstage_diff_hunks(
22815 false,
22816 vec![hunk_range.start..hunk_range.start],
22817 cx,
22818 );
22819 });
22820 }
22821 })
22822 })
22823 .child(
22824 Button::new(("restore", row as u64), "Restore")
22825 .tooltip({
22826 let focus_handle = editor.focus_handle(cx);
22827 move |window, cx| {
22828 Tooltip::for_action_in(
22829 "Restore Hunk",
22830 &::git::Restore,
22831 &focus_handle,
22832 window,
22833 cx,
22834 )
22835 }
22836 })
22837 .on_click({
22838 let editor = editor.clone();
22839 move |_event, window, cx| {
22840 editor.update(cx, |editor, cx| {
22841 let snapshot = editor.snapshot(window, cx);
22842 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22843 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22844 });
22845 }
22846 })
22847 .disabled(is_created_file),
22848 )
22849 .when(
22850 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22851 |el| {
22852 el.child(
22853 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22854 .shape(IconButtonShape::Square)
22855 .icon_size(IconSize::Small)
22856 // .disabled(!has_multiple_hunks)
22857 .tooltip({
22858 let focus_handle = editor.focus_handle(cx);
22859 move |window, cx| {
22860 Tooltip::for_action_in(
22861 "Next Hunk",
22862 &GoToHunk,
22863 &focus_handle,
22864 window,
22865 cx,
22866 )
22867 }
22868 })
22869 .on_click({
22870 let editor = editor.clone();
22871 move |_event, window, cx| {
22872 editor.update(cx, |editor, cx| {
22873 let snapshot = editor.snapshot(window, cx);
22874 let position =
22875 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22876 editor.go_to_hunk_before_or_after_position(
22877 &snapshot,
22878 position,
22879 Direction::Next,
22880 window,
22881 cx,
22882 );
22883 editor.expand_selected_diff_hunks(cx);
22884 });
22885 }
22886 }),
22887 )
22888 .child(
22889 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22890 .shape(IconButtonShape::Square)
22891 .icon_size(IconSize::Small)
22892 // .disabled(!has_multiple_hunks)
22893 .tooltip({
22894 let focus_handle = editor.focus_handle(cx);
22895 move |window, cx| {
22896 Tooltip::for_action_in(
22897 "Previous Hunk",
22898 &GoToPreviousHunk,
22899 &focus_handle,
22900 window,
22901 cx,
22902 )
22903 }
22904 })
22905 .on_click({
22906 let editor = editor.clone();
22907 move |_event, window, cx| {
22908 editor.update(cx, |editor, cx| {
22909 let snapshot = editor.snapshot(window, cx);
22910 let point =
22911 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22912 editor.go_to_hunk_before_or_after_position(
22913 &snapshot,
22914 point,
22915 Direction::Prev,
22916 window,
22917 cx,
22918 );
22919 editor.expand_selected_diff_hunks(cx);
22920 });
22921 }
22922 }),
22923 )
22924 },
22925 )
22926 .into_any_element()
22927}