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_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
113 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
114 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
115 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, 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, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::DiagnosticSeverity,
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221use crate::{
222 editor_settings::MultiCursorModifier,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 DebuggerValue(usize),
279 // LSP
280 Hint(usize),
281 Color(usize),
282}
283
284impl InlayId {
285 fn id(&self) -> usize {
286 match self {
287 Self::InlineCompletion(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 Self::Hint(id) => *id,
290 Self::Color(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300pub enum PendingInput {}
301enum SelectedTextHighlight {}
302
303pub enum ConflictsOuter {}
304pub enum ConflictsOurs {}
305pub enum ConflictsTheirs {}
306pub enum ConflictsOursMarker {}
307pub enum ConflictsTheirsMarker {}
308
309#[derive(Debug, Copy, Clone, PartialEq, Eq)]
310pub enum Navigated {
311 Yes,
312 No,
313}
314
315impl Navigated {
316 pub fn from_bool(yes: bool) -> Navigated {
317 if yes { Navigated::Yes } else { Navigated::No }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322enum DisplayDiffHunk {
323 Folded {
324 display_row: DisplayRow,
325 },
326 Unfolded {
327 is_created_file: bool,
328 diff_base_byte_range: Range<usize>,
329 display_row_range: Range<DisplayRow>,
330 multi_buffer_range: Range<Anchor>,
331 status: DiffHunkStatus,
332 },
333}
334
335pub enum HideMouseCursorOrigin {
336 TypingAction,
337 MovementAction,
338}
339
340pub fn init_settings(cx: &mut App) {
341 EditorSettings::register(cx);
342}
343
344pub fn init(cx: &mut App) {
345 init_settings(cx);
346
347 cx.set_global(GlobalBlameRenderer(Arc::new(())));
348
349 workspace::register_project_item::<Editor>(cx);
350 workspace::FollowableViewRegistry::register::<Editor>(cx);
351 workspace::register_serializable_item::<Editor>(cx);
352
353 cx.observe_new(
354 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
355 workspace.register_action(Editor::new_file);
356 workspace.register_action(Editor::new_file_vertical);
357 workspace.register_action(Editor::new_file_horizontal);
358 workspace.register_action(Editor::cancel_language_server_work);
359 },
360 )
361 .detach();
362
363 cx.on_action(move |_: &workspace::NewFile, cx| {
364 let app_state = workspace::AppState::global(cx);
365 if let Some(app_state) = app_state.upgrade() {
366 workspace::open_new(
367 Default::default(),
368 app_state,
369 cx,
370 |workspace, window, cx| {
371 Editor::new_file(workspace, &Default::default(), window, cx)
372 },
373 )
374 .detach();
375 }
376 });
377 cx.on_action(move |_: &workspace::NewWindow, cx| {
378 let app_state = workspace::AppState::global(cx);
379 if let Some(app_state) = app_state.upgrade() {
380 workspace::open_new(
381 Default::default(),
382 app_state,
383 cx,
384 |workspace, window, cx| {
385 cx.activate(true);
386 Editor::new_file(workspace, &Default::default(), window, cx)
387 },
388 )
389 .detach();
390 }
391 });
392}
393
394pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
395 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
396}
397
398pub trait DiagnosticRenderer {
399 fn render_group(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntry<Point>>,
402 buffer_id: BufferId,
403 snapshot: EditorSnapshot,
404 editor: WeakEntity<Editor>,
405 cx: &mut App,
406 ) -> Vec<BlockProperties<Anchor>>;
407
408 fn render_hover(
409 &self,
410 diagnostic_group: Vec<DiagnosticEntry<Point>>,
411 range: Range<Point>,
412 buffer_id: BufferId,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Clone, PartialEq, Eq, Debug)]
484pub enum EditorMode {
485 SingleLine {
486 auto_width: bool,
487 },
488 AutoHeight {
489 min_lines: usize,
490 max_lines: usize,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 pub fn is_full(&self) -> bool {
515 matches!(self, Self::Full { .. })
516 }
517
518 fn is_minimap(&self) -> bool {
519 matches!(self, Self::Minimap { .. })
520 }
521}
522
523#[derive(Copy, Clone, Debug)]
524pub enum SoftWrap {
525 /// Prefer not to wrap at all.
526 ///
527 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
528 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
529 GitDiff,
530 /// Prefer a single line generally, unless an overly long line is encountered.
531 None,
532 /// Soft wrap lines that exceed the editor width.
533 EditorWidth,
534 /// Soft wrap lines at the preferred line length.
535 Column(u32),
536 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
537 Bounded(u32),
538}
539
540#[derive(Clone)]
541pub struct EditorStyle {
542 pub background: Hsla,
543 pub local_player: PlayerColor,
544 pub text: TextStyle,
545 pub scrollbar_width: Pixels,
546 pub syntax: Arc<SyntaxTheme>,
547 pub status: StatusColors,
548 pub inlay_hints_style: HighlightStyle,
549 pub inline_completion_styles: InlineCompletionStyles,
550 pub unnecessary_code_fade: f32,
551 pub show_underlines: bool,
552}
553
554impl Default for EditorStyle {
555 fn default() -> Self {
556 Self {
557 background: Hsla::default(),
558 local_player: PlayerColor::default(),
559 text: TextStyle::default(),
560 scrollbar_width: Pixels::default(),
561 syntax: Default::default(),
562 // HACK: Status colors don't have a real default.
563 // We should look into removing the status colors from the editor
564 // style and retrieve them directly from the theme.
565 status: StatusColors::dark(),
566 inlay_hints_style: HighlightStyle::default(),
567 inline_completion_styles: InlineCompletionStyles {
568 insertion: HighlightStyle::default(),
569 whitespace: HighlightStyle::default(),
570 },
571 unnecessary_code_fade: Default::default(),
572 show_underlines: true,
573 }
574 }
575}
576
577pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
578 let show_background = language_settings::language_settings(None, None, cx)
579 .inlay_hints
580 .show_background;
581
582 HighlightStyle {
583 color: Some(cx.theme().status().hint),
584 background_color: show_background.then(|| cx.theme().status().hint_background),
585 ..HighlightStyle::default()
586 }
587}
588
589pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
590 InlineCompletionStyles {
591 insertion: HighlightStyle {
592 color: Some(cx.theme().status().predictive),
593 ..HighlightStyle::default()
594 },
595 whitespace: HighlightStyle {
596 background_color: Some(cx.theme().status().created_background),
597 ..HighlightStyle::default()
598 },
599 }
600}
601
602type CompletionId = usize;
603
604pub(crate) enum EditDisplayMode {
605 TabAccept,
606 DiffPopover,
607 Inline,
608}
609
610enum InlineCompletion {
611 Edit {
612 edits: Vec<(Range<Anchor>, String)>,
613 edit_preview: Option<EditPreview>,
614 display_mode: EditDisplayMode,
615 snapshot: BufferSnapshot,
616 },
617 Move {
618 target: Anchor,
619 snapshot: BufferSnapshot,
620 },
621}
622
623struct InlineCompletionState {
624 inlay_ids: Vec<InlayId>,
625 completion: InlineCompletion,
626 completion_id: Option<SharedString>,
627 invalidation_range: Range<Anchor>,
628}
629
630enum EditPredictionSettings {
631 Disabled,
632 Enabled {
633 show_in_menu: bool,
634 preview_requires_modifier: bool,
635 },
636}
637
638enum InlineCompletionHighlight {}
639
640#[derive(Debug, Clone)]
641struct InlineDiagnostic {
642 message: SharedString,
643 group_id: usize,
644 is_primary: bool,
645 start: Point,
646 severity: lsp::DiagnosticSeverity,
647}
648
649pub enum MenuInlineCompletionsPolicy {
650 Never,
651 ByProvider,
652}
653
654pub enum EditPredictionPreview {
655 /// Modifier is not pressed
656 Inactive { released_too_fast: bool },
657 /// Modifier pressed
658 Active {
659 since: Instant,
660 previous_scroll_position: Option<ScrollAnchor>,
661 },
662}
663
664impl EditPredictionPreview {
665 pub fn released_too_fast(&self) -> bool {
666 match self {
667 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
668 EditPredictionPreview::Active { .. } => false,
669 }
670 }
671
672 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
673 if let EditPredictionPreview::Active {
674 previous_scroll_position,
675 ..
676 } = self
677 {
678 *previous_scroll_position = scroll_position;
679 }
680 }
681}
682
683pub struct ContextMenuOptions {
684 pub min_entries_visible: usize,
685 pub max_entries_visible: usize,
686 pub placement: Option<ContextMenuPlacement>,
687}
688
689#[derive(Debug, Clone, PartialEq, Eq)]
690pub enum ContextMenuPlacement {
691 Above,
692 Below,
693}
694
695#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
696struct EditorActionId(usize);
697
698impl EditorActionId {
699 pub fn post_inc(&mut self) -> Self {
700 let answer = self.0;
701
702 *self = Self(answer + 1);
703
704 Self(answer)
705 }
706}
707
708// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
709// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
710
711type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
712type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
713
714#[derive(Default)]
715struct ScrollbarMarkerState {
716 scrollbar_size: Size<Pixels>,
717 dirty: bool,
718 markers: Arc<[PaintQuad]>,
719 pending_refresh: Option<Task<Result<()>>>,
720}
721
722impl ScrollbarMarkerState {
723 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
724 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
725 }
726}
727
728#[derive(Clone, Copy, PartialEq, Eq)]
729pub enum MinimapVisibility {
730 Disabled,
731 Enabled {
732 /// The configuration currently present in the users settings.
733 setting_configuration: bool,
734 /// Whether to override the currently set visibility from the users setting.
735 toggle_override: bool,
736 },
737}
738
739impl MinimapVisibility {
740 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
741 if mode.is_full() {
742 Self::Enabled {
743 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
744 toggle_override: false,
745 }
746 } else {
747 Self::Disabled
748 }
749 }
750
751 fn hidden(&self) -> Self {
752 match *self {
753 Self::Enabled {
754 setting_configuration,
755 ..
756 } => Self::Enabled {
757 setting_configuration,
758 toggle_override: setting_configuration,
759 },
760 Self::Disabled => Self::Disabled,
761 }
762 }
763
764 fn disabled(&self) -> bool {
765 match *self {
766 Self::Disabled => true,
767 _ => false,
768 }
769 }
770
771 fn settings_visibility(&self) -> bool {
772 match *self {
773 Self::Enabled {
774 setting_configuration,
775 ..
776 } => setting_configuration,
777 _ => false,
778 }
779 }
780
781 fn visible(&self) -> bool {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 toggle_override,
786 } => setting_configuration ^ toggle_override,
787 _ => false,
788 }
789 }
790
791 fn toggle_visibility(&self) -> Self {
792 match *self {
793 Self::Enabled {
794 toggle_override,
795 setting_configuration,
796 } => Self::Enabled {
797 setting_configuration,
798 toggle_override: !toggle_override,
799 },
800 Self::Disabled => Self::Disabled,
801 }
802 }
803}
804
805#[derive(Clone, Debug)]
806struct RunnableTasks {
807 templates: Vec<(TaskSourceKind, TaskTemplate)>,
808 offset: multi_buffer::Anchor,
809 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
810 column: u32,
811 // Values of all named captures, including those starting with '_'
812 extra_variables: HashMap<String, String>,
813 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
814 context_range: Range<BufferOffset>,
815}
816
817impl RunnableTasks {
818 fn resolve<'a>(
819 &'a self,
820 cx: &'a task::TaskContext,
821 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
822 self.templates.iter().filter_map(|(kind, template)| {
823 template
824 .resolve_task(&kind.to_id_base(), cx)
825 .map(|task| (kind.clone(), task))
826 })
827 }
828}
829
830#[derive(Clone)]
831pub struct ResolvedTasks {
832 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
833 position: Anchor,
834}
835
836#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
837struct BufferOffset(usize);
838
839// Addons allow storing per-editor state in other crates (e.g. Vim)
840pub trait Addon: 'static {
841 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
842
843 fn render_buffer_header_controls(
844 &self,
845 _: &ExcerptInfo,
846 _: &Window,
847 _: &App,
848 ) -> Option<AnyElement> {
849 None
850 }
851
852 fn to_any(&self) -> &dyn std::any::Any;
853
854 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
855 None
856 }
857}
858
859/// A set of caret positions, registered when the editor was edited.
860pub struct ChangeList {
861 changes: Vec<Vec<Anchor>>,
862 /// Currently "selected" change.
863 position: Option<usize>,
864}
865
866impl ChangeList {
867 pub fn new() -> Self {
868 Self {
869 changes: Vec::new(),
870 position: None,
871 }
872 }
873
874 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
875 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
876 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
877 if self.changes.is_empty() {
878 return None;
879 }
880
881 let prev = self.position.unwrap_or(self.changes.len());
882 let next = if direction == Direction::Prev {
883 prev.saturating_sub(count)
884 } else {
885 (prev + count).min(self.changes.len() - 1)
886 };
887 self.position = Some(next);
888 self.changes.get(next).map(|anchors| anchors.as_slice())
889 }
890
891 /// Adds a new change to the list, resetting the change list position.
892 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
893 self.position.take();
894 if pop_state {
895 self.changes.pop();
896 }
897 self.changes.push(new_positions.clone());
898 }
899
900 pub fn last(&self) -> Option<&[Anchor]> {
901 self.changes.last().map(|anchors| anchors.as_slice())
902 }
903}
904
905#[derive(Clone)]
906struct InlineBlamePopoverState {
907 scroll_handle: ScrollHandle,
908 commit_message: Option<ParsedCommitMessage>,
909 markdown: Entity<Markdown>,
910}
911
912struct InlineBlamePopover {
913 position: gpui::Point<Pixels>,
914 hide_task: Option<Task<()>>,
915 popover_bounds: Option<Bounds<Pixels>>,
916 popover_state: InlineBlamePopoverState,
917}
918
919enum SelectionDragState {
920 /// State when no drag related activity is detected.
921 None,
922 /// State when the mouse is down on a selection that is about to be dragged.
923 ReadyToDrag {
924 selection: Selection<Anchor>,
925 click_position: gpui::Point<Pixels>,
926 mouse_down_time: Instant,
927 },
928 /// State when the mouse is dragging the selection in the editor.
929 Dragging {
930 selection: Selection<Anchor>,
931 drop_cursor: Selection<Anchor>,
932 hide_drop_cursor: bool,
933 },
934}
935
936enum ColumnarSelectionState {
937 FromMouse {
938 selection_tail: Anchor,
939 display_point: Option<DisplayPoint>,
940 },
941 FromSelection {
942 selection_tail: Anchor,
943 },
944}
945
946/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
947/// a breakpoint on them.
948#[derive(Clone, Copy, Debug, PartialEq, Eq)]
949struct PhantomBreakpointIndicator {
950 display_row: DisplayRow,
951 /// There's a small debounce between hovering over the line and showing the indicator.
952 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
953 is_active: bool,
954 collides_with_existing_breakpoint: bool,
955}
956
957/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
958///
959/// See the [module level documentation](self) for more information.
960pub struct Editor {
961 focus_handle: FocusHandle,
962 last_focused_descendant: Option<WeakFocusHandle>,
963 /// The text buffer being edited
964 buffer: Entity<MultiBuffer>,
965 /// Map of how text in the buffer should be displayed.
966 /// Handles soft wraps, folds, fake inlay text insertions, etc.
967 pub display_map: Entity<DisplayMap>,
968 pub selections: SelectionsCollection,
969 pub scroll_manager: ScrollManager,
970 /// When inline assist editors are linked, they all render cursors because
971 /// typing enters text into each of them, even the ones that aren't focused.
972 pub(crate) show_cursor_when_unfocused: bool,
973 columnar_selection_state: Option<ColumnarSelectionState>,
974 add_selections_state: Option<AddSelectionsState>,
975 select_next_state: Option<SelectNextState>,
976 select_prev_state: Option<SelectNextState>,
977 selection_history: SelectionHistory,
978 defer_selection_effects: bool,
979 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
980 autoclose_regions: Vec<AutocloseRegion>,
981 snippet_stack: InvalidationStack<SnippetState>,
982 select_syntax_node_history: SelectSyntaxNodeHistory,
983 ime_transaction: Option<TransactionId>,
984 pub diagnostics_max_severity: DiagnosticSeverity,
985 active_diagnostics: ActiveDiagnostic,
986 show_inline_diagnostics: bool,
987 inline_diagnostics_update: Task<()>,
988 inline_diagnostics_enabled: bool,
989 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
990 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
991 hard_wrap: Option<usize>,
992
993 // TODO: make this a access method
994 pub project: Option<Entity<Project>>,
995 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
996 completion_provider: Option<Rc<dyn CompletionProvider>>,
997 collaboration_hub: Option<Box<dyn CollaborationHub>>,
998 blink_manager: Entity<BlinkManager>,
999 show_cursor_names: bool,
1000 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1001 pub show_local_selections: bool,
1002 mode: EditorMode,
1003 show_breadcrumbs: bool,
1004 show_gutter: bool,
1005 show_scrollbars: ScrollbarAxes,
1006 minimap_visibility: MinimapVisibility,
1007 offset_content: bool,
1008 disable_expand_excerpt_buttons: bool,
1009 show_line_numbers: Option<bool>,
1010 use_relative_line_numbers: Option<bool>,
1011 show_git_diff_gutter: Option<bool>,
1012 show_code_actions: Option<bool>,
1013 show_runnables: Option<bool>,
1014 show_breakpoints: Option<bool>,
1015 show_wrap_guides: Option<bool>,
1016 show_indent_guides: Option<bool>,
1017 placeholder_text: Option<Arc<str>>,
1018 highlight_order: usize,
1019 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1020 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1021 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1022 scrollbar_marker_state: ScrollbarMarkerState,
1023 active_indent_guides_state: ActiveIndentGuidesState,
1024 nav_history: Option<ItemNavHistory>,
1025 context_menu: RefCell<Option<CodeContextMenu>>,
1026 context_menu_options: Option<ContextMenuOptions>,
1027 mouse_context_menu: Option<MouseContextMenu>,
1028 completion_tasks: Vec<(CompletionId, Task<()>)>,
1029 inline_blame_popover: Option<InlineBlamePopover>,
1030 inline_blame_popover_show_task: Option<Task<()>>,
1031 signature_help_state: SignatureHelpState,
1032 auto_signature_help: Option<bool>,
1033 find_all_references_task_sources: Vec<Anchor>,
1034 next_completion_id: CompletionId,
1035 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1036 code_actions_task: Option<Task<Result<()>>>,
1037 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1038 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1039 document_highlights_task: Option<Task<()>>,
1040 linked_editing_range_task: Option<Task<Option<()>>>,
1041 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1042 pending_rename: Option<RenameState>,
1043 searchable: bool,
1044 cursor_shape: CursorShape,
1045 current_line_highlight: Option<CurrentLineHighlight>,
1046 collapse_matches: bool,
1047 autoindent_mode: Option<AutoindentMode>,
1048 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1049 input_enabled: bool,
1050 use_modal_editing: bool,
1051 read_only: bool,
1052 leader_id: Option<CollaboratorId>,
1053 remote_id: Option<ViewId>,
1054 pub hover_state: HoverState,
1055 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1056 gutter_hovered: bool,
1057 hovered_link_state: Option<HoveredLinkState>,
1058 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1059 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1060 active_inline_completion: Option<InlineCompletionState>,
1061 /// Used to prevent flickering as the user types while the menu is open
1062 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1063 edit_prediction_settings: EditPredictionSettings,
1064 inline_completions_hidden_for_vim_mode: bool,
1065 show_inline_completions_override: Option<bool>,
1066 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1067 edit_prediction_preview: EditPredictionPreview,
1068 edit_prediction_indent_conflict: bool,
1069 edit_prediction_requires_modifier_in_indent_conflict: bool,
1070 inlay_hint_cache: InlayHintCache,
1071 next_inlay_id: usize,
1072 _subscriptions: Vec<Subscription>,
1073 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1074 gutter_dimensions: GutterDimensions,
1075 style: Option<EditorStyle>,
1076 text_style_refinement: Option<TextStyleRefinement>,
1077 next_editor_action_id: EditorActionId,
1078 editor_actions: Rc<
1079 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1080 >,
1081 use_autoclose: bool,
1082 use_auto_surround: bool,
1083 auto_replace_emoji_shortcode: bool,
1084 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1085 show_git_blame_gutter: bool,
1086 show_git_blame_inline: bool,
1087 show_git_blame_inline_delay_task: Option<Task<()>>,
1088 git_blame_inline_enabled: bool,
1089 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1090 serialize_dirty_buffers: bool,
1091 show_selection_menu: Option<bool>,
1092 blame: Option<Entity<GitBlame>>,
1093 blame_subscription: Option<Subscription>,
1094 custom_context_menu: Option<
1095 Box<
1096 dyn 'static
1097 + Fn(
1098 &mut Self,
1099 DisplayPoint,
1100 &mut Window,
1101 &mut Context<Self>,
1102 ) -> Option<Entity<ui::ContextMenu>>,
1103 >,
1104 >,
1105 last_bounds: Option<Bounds<Pixels>>,
1106 last_position_map: Option<Rc<PositionMap>>,
1107 expect_bounds_change: Option<Bounds<Pixels>>,
1108 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1109 tasks_update_task: Option<Task<()>>,
1110 breakpoint_store: Option<Entity<BreakpointStore>>,
1111 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1112 hovered_diff_hunk_row: Option<DisplayRow>,
1113 pull_diagnostics_task: Task<()>,
1114 in_project_search: bool,
1115 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1116 breadcrumb_header: Option<String>,
1117 focused_block: Option<FocusedBlock>,
1118 next_scroll_position: NextScrollCursorCenterTopBottom,
1119 addons: HashMap<TypeId, Box<dyn Addon>>,
1120 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1121 load_diff_task: Option<Shared<Task<()>>>,
1122 /// Whether we are temporarily displaying a diff other than git's
1123 temporary_diff_override: bool,
1124 selection_mark_mode: bool,
1125 toggle_fold_multiple_buffers: Task<()>,
1126 _scroll_cursor_center_top_bottom_task: Task<()>,
1127 serialize_selections: Task<()>,
1128 serialize_folds: Task<()>,
1129 mouse_cursor_hidden: bool,
1130 minimap: Option<Entity<Self>>,
1131 hide_mouse_mode: HideMouseMode,
1132 pub change_list: ChangeList,
1133 inline_value_cache: InlineValueCache,
1134 selection_drag_state: SelectionDragState,
1135 drag_and_drop_selection_enabled: bool,
1136 next_color_inlay_id: usize,
1137 colors: Option<LspColorData>,
1138}
1139
1140#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1141enum NextScrollCursorCenterTopBottom {
1142 #[default]
1143 Center,
1144 Top,
1145 Bottom,
1146}
1147
1148impl NextScrollCursorCenterTopBottom {
1149 fn next(&self) -> Self {
1150 match self {
1151 Self::Center => Self::Top,
1152 Self::Top => Self::Bottom,
1153 Self::Bottom => Self::Center,
1154 }
1155 }
1156}
1157
1158#[derive(Clone)]
1159pub struct EditorSnapshot {
1160 pub mode: EditorMode,
1161 show_gutter: bool,
1162 show_line_numbers: Option<bool>,
1163 show_git_diff_gutter: Option<bool>,
1164 show_code_actions: Option<bool>,
1165 show_runnables: Option<bool>,
1166 show_breakpoints: Option<bool>,
1167 git_blame_gutter_max_author_length: Option<usize>,
1168 pub display_snapshot: DisplaySnapshot,
1169 pub placeholder_text: Option<Arc<str>>,
1170 is_focused: bool,
1171 scroll_anchor: ScrollAnchor,
1172 ongoing_scroll: OngoingScroll,
1173 current_line_highlight: CurrentLineHighlight,
1174 gutter_hovered: bool,
1175}
1176
1177#[derive(Default, Debug, Clone, Copy)]
1178pub struct GutterDimensions {
1179 pub left_padding: Pixels,
1180 pub right_padding: Pixels,
1181 pub width: Pixels,
1182 pub margin: Pixels,
1183 pub git_blame_entries_width: Option<Pixels>,
1184}
1185
1186impl GutterDimensions {
1187 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1188 Self {
1189 margin: Self::default_gutter_margin(font_id, font_size, cx),
1190 ..Default::default()
1191 }
1192 }
1193
1194 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1195 -cx.text_system().descent(font_id, font_size)
1196 }
1197 /// The full width of the space taken up by the gutter.
1198 pub fn full_width(&self) -> Pixels {
1199 self.margin + self.width
1200 }
1201
1202 /// The width of the space reserved for the fold indicators,
1203 /// use alongside 'justify_end' and `gutter_width` to
1204 /// right align content with the line numbers
1205 pub fn fold_area_width(&self) -> Pixels {
1206 self.margin + self.right_padding
1207 }
1208}
1209
1210#[derive(Debug)]
1211pub struct RemoteSelection {
1212 pub replica_id: ReplicaId,
1213 pub selection: Selection<Anchor>,
1214 pub cursor_shape: CursorShape,
1215 pub collaborator_id: CollaboratorId,
1216 pub line_mode: bool,
1217 pub user_name: Option<SharedString>,
1218 pub color: PlayerColor,
1219}
1220
1221#[derive(Clone, Debug)]
1222struct SelectionHistoryEntry {
1223 selections: Arc<[Selection<Anchor>]>,
1224 select_next_state: Option<SelectNextState>,
1225 select_prev_state: Option<SelectNextState>,
1226 add_selections_state: Option<AddSelectionsState>,
1227}
1228
1229#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1230enum SelectionHistoryMode {
1231 Normal,
1232 Undoing,
1233 Redoing,
1234 Skipping,
1235}
1236
1237#[derive(Clone, PartialEq, Eq, Hash)]
1238struct HoveredCursor {
1239 replica_id: u16,
1240 selection_id: usize,
1241}
1242
1243impl Default for SelectionHistoryMode {
1244 fn default() -> Self {
1245 Self::Normal
1246 }
1247}
1248
1249#[derive(Debug)]
1250pub struct SelectionEffects {
1251 nav_history: bool,
1252 completions: bool,
1253 scroll: Option<Autoscroll>,
1254}
1255
1256impl Default for SelectionEffects {
1257 fn default() -> Self {
1258 Self {
1259 nav_history: true,
1260 completions: true,
1261 scroll: Some(Autoscroll::fit()),
1262 }
1263 }
1264}
1265impl SelectionEffects {
1266 pub fn scroll(scroll: Autoscroll) -> Self {
1267 Self {
1268 scroll: Some(scroll),
1269 ..Default::default()
1270 }
1271 }
1272
1273 pub fn no_scroll() -> Self {
1274 Self {
1275 scroll: None,
1276 ..Default::default()
1277 }
1278 }
1279
1280 pub fn completions(self, completions: bool) -> Self {
1281 Self {
1282 completions,
1283 ..self
1284 }
1285 }
1286
1287 pub fn nav_history(self, nav_history: bool) -> Self {
1288 Self {
1289 nav_history,
1290 ..self
1291 }
1292 }
1293}
1294
1295struct DeferredSelectionEffectsState {
1296 changed: bool,
1297 effects: SelectionEffects,
1298 old_cursor_position: Anchor,
1299 history_entry: SelectionHistoryEntry,
1300}
1301
1302#[derive(Default)]
1303struct SelectionHistory {
1304 #[allow(clippy::type_complexity)]
1305 selections_by_transaction:
1306 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1307 mode: SelectionHistoryMode,
1308 undo_stack: VecDeque<SelectionHistoryEntry>,
1309 redo_stack: VecDeque<SelectionHistoryEntry>,
1310}
1311
1312impl SelectionHistory {
1313 #[track_caller]
1314 fn insert_transaction(
1315 &mut self,
1316 transaction_id: TransactionId,
1317 selections: Arc<[Selection<Anchor>]>,
1318 ) {
1319 if selections.is_empty() {
1320 log::error!(
1321 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1322 std::panic::Location::caller()
1323 );
1324 return;
1325 }
1326 self.selections_by_transaction
1327 .insert(transaction_id, (selections, None));
1328 }
1329
1330 #[allow(clippy::type_complexity)]
1331 fn transaction(
1332 &self,
1333 transaction_id: TransactionId,
1334 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1335 self.selections_by_transaction.get(&transaction_id)
1336 }
1337
1338 #[allow(clippy::type_complexity)]
1339 fn transaction_mut(
1340 &mut self,
1341 transaction_id: TransactionId,
1342 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1343 self.selections_by_transaction.get_mut(&transaction_id)
1344 }
1345
1346 fn push(&mut self, entry: SelectionHistoryEntry) {
1347 if !entry.selections.is_empty() {
1348 match self.mode {
1349 SelectionHistoryMode::Normal => {
1350 self.push_undo(entry);
1351 self.redo_stack.clear();
1352 }
1353 SelectionHistoryMode::Undoing => self.push_redo(entry),
1354 SelectionHistoryMode::Redoing => self.push_undo(entry),
1355 SelectionHistoryMode::Skipping => {}
1356 }
1357 }
1358 }
1359
1360 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1361 if self
1362 .undo_stack
1363 .back()
1364 .map_or(true, |e| e.selections != entry.selections)
1365 {
1366 self.undo_stack.push_back(entry);
1367 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1368 self.undo_stack.pop_front();
1369 }
1370 }
1371 }
1372
1373 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1374 if self
1375 .redo_stack
1376 .back()
1377 .map_or(true, |e| e.selections != entry.selections)
1378 {
1379 self.redo_stack.push_back(entry);
1380 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1381 self.redo_stack.pop_front();
1382 }
1383 }
1384 }
1385}
1386
1387#[derive(Clone, Copy)]
1388pub struct RowHighlightOptions {
1389 pub autoscroll: bool,
1390 pub include_gutter: bool,
1391}
1392
1393impl Default for RowHighlightOptions {
1394 fn default() -> Self {
1395 Self {
1396 autoscroll: Default::default(),
1397 include_gutter: true,
1398 }
1399 }
1400}
1401
1402struct RowHighlight {
1403 index: usize,
1404 range: Range<Anchor>,
1405 color: Hsla,
1406 options: RowHighlightOptions,
1407 type_id: TypeId,
1408}
1409
1410#[derive(Clone, Debug)]
1411struct AddSelectionsState {
1412 groups: Vec<AddSelectionsGroup>,
1413}
1414
1415#[derive(Clone, Debug)]
1416struct AddSelectionsGroup {
1417 above: bool,
1418 stack: Vec<usize>,
1419}
1420
1421#[derive(Clone)]
1422struct SelectNextState {
1423 query: AhoCorasick,
1424 wordwise: bool,
1425 done: bool,
1426}
1427
1428impl std::fmt::Debug for SelectNextState {
1429 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1430 f.debug_struct(std::any::type_name::<Self>())
1431 .field("wordwise", &self.wordwise)
1432 .field("done", &self.done)
1433 .finish()
1434 }
1435}
1436
1437#[derive(Debug)]
1438struct AutocloseRegion {
1439 selection_id: usize,
1440 range: Range<Anchor>,
1441 pair: BracketPair,
1442}
1443
1444#[derive(Debug)]
1445struct SnippetState {
1446 ranges: Vec<Vec<Range<Anchor>>>,
1447 active_index: usize,
1448 choices: Vec<Option<Vec<String>>>,
1449}
1450
1451#[doc(hidden)]
1452pub struct RenameState {
1453 pub range: Range<Anchor>,
1454 pub old_name: Arc<str>,
1455 pub editor: Entity<Editor>,
1456 block_id: CustomBlockId,
1457}
1458
1459struct InvalidationStack<T>(Vec<T>);
1460
1461struct RegisteredInlineCompletionProvider {
1462 provider: Arc<dyn InlineCompletionProviderHandle>,
1463 _subscription: Subscription,
1464}
1465
1466#[derive(Debug, PartialEq, Eq)]
1467pub struct ActiveDiagnosticGroup {
1468 pub active_range: Range<Anchor>,
1469 pub active_message: String,
1470 pub group_id: usize,
1471 pub blocks: HashSet<CustomBlockId>,
1472}
1473
1474#[derive(Debug, PartialEq, Eq)]
1475
1476pub(crate) enum ActiveDiagnostic {
1477 None,
1478 All,
1479 Group(ActiveDiagnosticGroup),
1480}
1481
1482#[derive(Serialize, Deserialize, Clone, Debug)]
1483pub struct ClipboardSelection {
1484 /// The number of bytes in this selection.
1485 pub len: usize,
1486 /// Whether this was a full-line selection.
1487 pub is_entire_line: bool,
1488 /// The indentation of the first line when this content was originally copied.
1489 pub first_line_indent: u32,
1490}
1491
1492// selections, scroll behavior, was newest selection reversed
1493type SelectSyntaxNodeHistoryState = (
1494 Box<[Selection<usize>]>,
1495 SelectSyntaxNodeScrollBehavior,
1496 bool,
1497);
1498
1499#[derive(Default)]
1500struct SelectSyntaxNodeHistory {
1501 stack: Vec<SelectSyntaxNodeHistoryState>,
1502 // disable temporarily to allow changing selections without losing the stack
1503 pub disable_clearing: bool,
1504}
1505
1506impl SelectSyntaxNodeHistory {
1507 pub fn try_clear(&mut self) {
1508 if !self.disable_clearing {
1509 self.stack.clear();
1510 }
1511 }
1512
1513 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1514 self.stack.push(selection);
1515 }
1516
1517 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1518 self.stack.pop()
1519 }
1520}
1521
1522enum SelectSyntaxNodeScrollBehavior {
1523 CursorTop,
1524 FitSelection,
1525 CursorBottom,
1526}
1527
1528#[derive(Debug)]
1529pub(crate) struct NavigationData {
1530 cursor_anchor: Anchor,
1531 cursor_position: Point,
1532 scroll_anchor: ScrollAnchor,
1533 scroll_top_row: u32,
1534}
1535
1536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1537pub enum GotoDefinitionKind {
1538 Symbol,
1539 Declaration,
1540 Type,
1541 Implementation,
1542}
1543
1544#[derive(Debug, Clone)]
1545enum InlayHintRefreshReason {
1546 ModifiersChanged(bool),
1547 Toggle(bool),
1548 SettingsChange(InlayHintSettings),
1549 NewLinesShown,
1550 BufferEdited(HashSet<Arc<Language>>),
1551 RefreshRequested,
1552 ExcerptsRemoved(Vec<ExcerptId>),
1553}
1554
1555impl InlayHintRefreshReason {
1556 fn description(&self) -> &'static str {
1557 match self {
1558 Self::ModifiersChanged(_) => "modifiers changed",
1559 Self::Toggle(_) => "toggle",
1560 Self::SettingsChange(_) => "settings change",
1561 Self::NewLinesShown => "new lines shown",
1562 Self::BufferEdited(_) => "buffer edited",
1563 Self::RefreshRequested => "refresh requested",
1564 Self::ExcerptsRemoved(_) => "excerpts removed",
1565 }
1566 }
1567}
1568
1569pub enum FormatTarget {
1570 Buffers(HashSet<Entity<Buffer>>),
1571 Ranges(Vec<Range<MultiBufferPoint>>),
1572}
1573
1574pub(crate) struct FocusedBlock {
1575 id: BlockId,
1576 focus_handle: WeakFocusHandle,
1577}
1578
1579#[derive(Clone)]
1580enum JumpData {
1581 MultiBufferRow {
1582 row: MultiBufferRow,
1583 line_offset_from_top: u32,
1584 },
1585 MultiBufferPoint {
1586 excerpt_id: ExcerptId,
1587 position: Point,
1588 anchor: text::Anchor,
1589 line_offset_from_top: u32,
1590 },
1591}
1592
1593pub enum MultibufferSelectionMode {
1594 First,
1595 All,
1596}
1597
1598#[derive(Clone, Copy, Debug, Default)]
1599pub struct RewrapOptions {
1600 pub override_language_settings: bool,
1601 pub preserve_existing_whitespace: bool,
1602}
1603
1604impl Editor {
1605 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1606 let buffer = cx.new(|cx| Buffer::local("", cx));
1607 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1608 Self::new(
1609 EditorMode::SingleLine { auto_width: false },
1610 buffer,
1611 None,
1612 window,
1613 cx,
1614 )
1615 }
1616
1617 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1618 let buffer = cx.new(|cx| Buffer::local("", cx));
1619 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1620 Self::new(EditorMode::full(), buffer, None, window, cx)
1621 }
1622
1623 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1624 let buffer = cx.new(|cx| Buffer::local("", cx));
1625 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1626 Self::new(
1627 EditorMode::SingleLine { auto_width: true },
1628 buffer,
1629 None,
1630 window,
1631 cx,
1632 )
1633 }
1634
1635 pub fn auto_height(
1636 min_lines: usize,
1637 max_lines: usize,
1638 window: &mut Window,
1639 cx: &mut Context<Self>,
1640 ) -> Self {
1641 let buffer = cx.new(|cx| Buffer::local("", cx));
1642 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1643 Self::new(
1644 EditorMode::AutoHeight {
1645 min_lines,
1646 max_lines,
1647 },
1648 buffer,
1649 None,
1650 window,
1651 cx,
1652 )
1653 }
1654
1655 pub fn for_buffer(
1656 buffer: Entity<Buffer>,
1657 project: Option<Entity<Project>>,
1658 window: &mut Window,
1659 cx: &mut Context<Self>,
1660 ) -> Self {
1661 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1662 Self::new(EditorMode::full(), buffer, project, window, cx)
1663 }
1664
1665 pub fn for_multibuffer(
1666 buffer: Entity<MultiBuffer>,
1667 project: Option<Entity<Project>>,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) -> Self {
1671 Self::new(EditorMode::full(), buffer, project, window, cx)
1672 }
1673
1674 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let mut clone = Self::new(
1676 self.mode.clone(),
1677 self.buffer.clone(),
1678 self.project.clone(),
1679 window,
1680 cx,
1681 );
1682 self.display_map.update(cx, |display_map, cx| {
1683 let snapshot = display_map.snapshot(cx);
1684 clone.display_map.update(cx, |display_map, cx| {
1685 display_map.set_state(&snapshot, cx);
1686 });
1687 });
1688 clone.folds_did_change(cx);
1689 clone.selections.clone_state(&self.selections);
1690 clone.scroll_manager.clone_state(&self.scroll_manager);
1691 clone.searchable = self.searchable;
1692 clone.read_only = self.read_only;
1693 clone
1694 }
1695
1696 pub fn new(
1697 mode: EditorMode,
1698 buffer: Entity<MultiBuffer>,
1699 project: Option<Entity<Project>>,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 Editor::new_internal(mode, buffer, project, None, window, cx)
1704 }
1705
1706 fn new_internal(
1707 mode: EditorMode,
1708 buffer: Entity<MultiBuffer>,
1709 project: Option<Entity<Project>>,
1710 display_map: Option<Entity<DisplayMap>>,
1711 window: &mut Window,
1712 cx: &mut Context<Self>,
1713 ) -> Self {
1714 debug_assert!(
1715 display_map.is_none() || mode.is_minimap(),
1716 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1717 );
1718
1719 let full_mode = mode.is_full();
1720 let diagnostics_max_severity = if full_mode {
1721 EditorSettings::get_global(cx)
1722 .diagnostics_max_severity
1723 .unwrap_or(DiagnosticSeverity::Hint)
1724 } else {
1725 DiagnosticSeverity::Off
1726 };
1727 let style = window.text_style();
1728 let font_size = style.font_size.to_pixels(window.rem_size());
1729 let editor = cx.entity().downgrade();
1730 let fold_placeholder = FoldPlaceholder {
1731 constrain_width: true,
1732 render: Arc::new(move |fold_id, fold_range, cx| {
1733 let editor = editor.clone();
1734 div()
1735 .id(fold_id)
1736 .bg(cx.theme().colors().ghost_element_background)
1737 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1738 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1739 .rounded_xs()
1740 .size_full()
1741 .cursor_pointer()
1742 .child("⋯")
1743 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1744 .on_click(move |_, _window, cx| {
1745 editor
1746 .update(cx, |editor, cx| {
1747 editor.unfold_ranges(
1748 &[fold_range.start..fold_range.end],
1749 true,
1750 false,
1751 cx,
1752 );
1753 cx.stop_propagation();
1754 })
1755 .ok();
1756 })
1757 .into_any()
1758 }),
1759 merge_adjacent: true,
1760 ..FoldPlaceholder::default()
1761 };
1762 let display_map = display_map.unwrap_or_else(|| {
1763 cx.new(|cx| {
1764 DisplayMap::new(
1765 buffer.clone(),
1766 style.font(),
1767 font_size,
1768 None,
1769 FILE_HEADER_HEIGHT,
1770 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1771 fold_placeholder,
1772 diagnostics_max_severity,
1773 cx,
1774 )
1775 })
1776 });
1777
1778 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1779
1780 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1781
1782 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1783 .then(|| language_settings::SoftWrap::None);
1784
1785 let mut project_subscriptions = Vec::new();
1786 if mode.is_full() {
1787 if let Some(project) = project.as_ref() {
1788 project_subscriptions.push(cx.subscribe_in(
1789 project,
1790 window,
1791 |editor, _, event, window, cx| match event {
1792 project::Event::RefreshCodeLens => {
1793 // we always query lens with actions, without storing them, always refreshing them
1794 }
1795 project::Event::RefreshInlayHints => {
1796 editor
1797 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1798 }
1799 project::Event::LanguageServerAdded(..)
1800 | project::Event::LanguageServerRemoved(..) => {
1801 if editor.tasks_update_task.is_none() {
1802 editor.tasks_update_task =
1803 Some(editor.refresh_runnables(window, cx));
1804 }
1805 editor.update_lsp_data(true, None, window, cx);
1806 }
1807 project::Event::SnippetEdit(id, snippet_edits) => {
1808 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1809 let focus_handle = editor.focus_handle(cx);
1810 if focus_handle.is_focused(window) {
1811 let snapshot = buffer.read(cx).snapshot();
1812 for (range, snippet) in snippet_edits {
1813 let editor_range =
1814 language::range_from_lsp(*range).to_offset(&snapshot);
1815 editor
1816 .insert_snippet(
1817 &[editor_range],
1818 snippet.clone(),
1819 window,
1820 cx,
1821 )
1822 .ok();
1823 }
1824 }
1825 }
1826 }
1827 _ => {}
1828 },
1829 ));
1830 if let Some(task_inventory) = project
1831 .read(cx)
1832 .task_store()
1833 .read(cx)
1834 .task_inventory()
1835 .cloned()
1836 {
1837 project_subscriptions.push(cx.observe_in(
1838 &task_inventory,
1839 window,
1840 |editor, _, window, cx| {
1841 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1842 },
1843 ));
1844 };
1845
1846 project_subscriptions.push(cx.subscribe_in(
1847 &project.read(cx).breakpoint_store(),
1848 window,
1849 |editor, _, event, window, cx| match event {
1850 BreakpointStoreEvent::ClearDebugLines => {
1851 editor.clear_row_highlights::<ActiveDebugLine>();
1852 editor.refresh_inline_values(cx);
1853 }
1854 BreakpointStoreEvent::SetDebugLine => {
1855 if editor.go_to_active_debug_line(window, cx) {
1856 cx.stop_propagation();
1857 }
1858
1859 editor.refresh_inline_values(cx);
1860 }
1861 _ => {}
1862 },
1863 ));
1864 let git_store = project.read(cx).git_store().clone();
1865 let project = project.clone();
1866 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1867 match event {
1868 GitStoreEvent::RepositoryUpdated(
1869 _,
1870 RepositoryEvent::Updated {
1871 new_instance: true, ..
1872 },
1873 _,
1874 ) => {
1875 this.load_diff_task = Some(
1876 update_uncommitted_diff_for_buffer(
1877 cx.entity(),
1878 &project,
1879 this.buffer.read(cx).all_buffers(),
1880 this.buffer.clone(),
1881 cx,
1882 )
1883 .shared(),
1884 );
1885 }
1886 _ => {}
1887 }
1888 }));
1889 }
1890 }
1891
1892 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1893
1894 let inlay_hint_settings =
1895 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1896 let focus_handle = cx.focus_handle();
1897 cx.on_focus(&focus_handle, window, Self::handle_focus)
1898 .detach();
1899 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1900 .detach();
1901 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1902 .detach();
1903 cx.on_blur(&focus_handle, window, Self::handle_blur)
1904 .detach();
1905 cx.observe_pending_input(window, Self::observe_pending_input)
1906 .detach();
1907
1908 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1909 Some(false)
1910 } else {
1911 None
1912 };
1913
1914 let breakpoint_store = match (&mode, project.as_ref()) {
1915 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1916 _ => None,
1917 };
1918
1919 let mut code_action_providers = Vec::new();
1920 let mut load_uncommitted_diff = None;
1921 if let Some(project) = project.clone() {
1922 load_uncommitted_diff = Some(
1923 update_uncommitted_diff_for_buffer(
1924 cx.entity(),
1925 &project,
1926 buffer.read(cx).all_buffers(),
1927 buffer.clone(),
1928 cx,
1929 )
1930 .shared(),
1931 );
1932 code_action_providers.push(Rc::new(project) as Rc<_>);
1933 }
1934
1935 let mut editor = Self {
1936 focus_handle,
1937 show_cursor_when_unfocused: false,
1938 last_focused_descendant: None,
1939 buffer: buffer.clone(),
1940 display_map: display_map.clone(),
1941 selections,
1942 scroll_manager: ScrollManager::new(cx),
1943 columnar_selection_state: None,
1944 add_selections_state: None,
1945 select_next_state: None,
1946 select_prev_state: None,
1947 selection_history: SelectionHistory::default(),
1948 defer_selection_effects: false,
1949 deferred_selection_effects_state: None,
1950 autoclose_regions: Vec::new(),
1951 snippet_stack: InvalidationStack::default(),
1952 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1953 ime_transaction: None,
1954 active_diagnostics: ActiveDiagnostic::None,
1955 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1956 inline_diagnostics_update: Task::ready(()),
1957 inline_diagnostics: Vec::new(),
1958 soft_wrap_mode_override,
1959 diagnostics_max_severity,
1960 hard_wrap: None,
1961 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1962 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1963 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1964 project,
1965 blink_manager: blink_manager.clone(),
1966 show_local_selections: true,
1967 show_scrollbars: ScrollbarAxes {
1968 horizontal: full_mode,
1969 vertical: full_mode,
1970 },
1971 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1972 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1973 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1974 show_gutter: mode.is_full(),
1975 show_line_numbers: None,
1976 use_relative_line_numbers: None,
1977 disable_expand_excerpt_buttons: false,
1978 show_git_diff_gutter: None,
1979 show_code_actions: None,
1980 show_runnables: None,
1981 show_breakpoints: None,
1982 show_wrap_guides: None,
1983 show_indent_guides,
1984 placeholder_text: None,
1985 highlight_order: 0,
1986 highlighted_rows: HashMap::default(),
1987 background_highlights: TreeMap::default(),
1988 gutter_highlights: TreeMap::default(),
1989 scrollbar_marker_state: ScrollbarMarkerState::default(),
1990 active_indent_guides_state: ActiveIndentGuidesState::default(),
1991 nav_history: None,
1992 context_menu: RefCell::new(None),
1993 context_menu_options: None,
1994 mouse_context_menu: None,
1995 completion_tasks: Vec::new(),
1996 inline_blame_popover: None,
1997 inline_blame_popover_show_task: None,
1998 signature_help_state: SignatureHelpState::default(),
1999 auto_signature_help: None,
2000 find_all_references_task_sources: Vec::new(),
2001 next_completion_id: 0,
2002 next_inlay_id: 0,
2003 code_action_providers,
2004 available_code_actions: None,
2005 code_actions_task: None,
2006 quick_selection_highlight_task: None,
2007 debounced_selection_highlight_task: None,
2008 document_highlights_task: None,
2009 linked_editing_range_task: None,
2010 pending_rename: None,
2011 searchable: true,
2012 cursor_shape: EditorSettings::get_global(cx)
2013 .cursor_shape
2014 .unwrap_or_default(),
2015 current_line_highlight: None,
2016 autoindent_mode: Some(AutoindentMode::EachLine),
2017 collapse_matches: false,
2018 workspace: None,
2019 input_enabled: true,
2020 use_modal_editing: mode.is_full(),
2021 read_only: mode.is_minimap(),
2022 use_autoclose: true,
2023 use_auto_surround: true,
2024 auto_replace_emoji_shortcode: false,
2025 jsx_tag_auto_close_enabled_in_any_buffer: false,
2026 leader_id: None,
2027 remote_id: None,
2028 hover_state: HoverState::default(),
2029 pending_mouse_down: None,
2030 hovered_link_state: None,
2031 edit_prediction_provider: None,
2032 active_inline_completion: None,
2033 stale_inline_completion_in_menu: None,
2034 edit_prediction_preview: EditPredictionPreview::Inactive {
2035 released_too_fast: false,
2036 },
2037 inline_diagnostics_enabled: mode.is_full(),
2038 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2039 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2040
2041 gutter_hovered: false,
2042 pixel_position_of_newest_cursor: None,
2043 last_bounds: None,
2044 last_position_map: None,
2045 expect_bounds_change: None,
2046 gutter_dimensions: GutterDimensions::default(),
2047 style: None,
2048 show_cursor_names: false,
2049 hovered_cursors: HashMap::default(),
2050 next_editor_action_id: EditorActionId::default(),
2051 editor_actions: Rc::default(),
2052 inline_completions_hidden_for_vim_mode: false,
2053 show_inline_completions_override: None,
2054 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2055 edit_prediction_settings: EditPredictionSettings::Disabled,
2056 edit_prediction_indent_conflict: false,
2057 edit_prediction_requires_modifier_in_indent_conflict: true,
2058 custom_context_menu: None,
2059 show_git_blame_gutter: false,
2060 show_git_blame_inline: false,
2061 show_selection_menu: None,
2062 show_git_blame_inline_delay_task: None,
2063 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2064 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2065 serialize_dirty_buffers: !mode.is_minimap()
2066 && ProjectSettings::get_global(cx)
2067 .session
2068 .restore_unsaved_buffers,
2069 blame: None,
2070 blame_subscription: None,
2071 tasks: BTreeMap::default(),
2072
2073 breakpoint_store,
2074 gutter_breakpoint_indicator: (None, None),
2075 hovered_diff_hunk_row: None,
2076 _subscriptions: vec![
2077 cx.observe(&buffer, Self::on_buffer_changed),
2078 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2079 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2080 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2081 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2082 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2083 cx.observe_window_activation(window, |editor, window, cx| {
2084 let active = window.is_window_active();
2085 editor.blink_manager.update(cx, |blink_manager, cx| {
2086 if active {
2087 blink_manager.enable(cx);
2088 } else {
2089 blink_manager.disable(cx);
2090 }
2091 });
2092 if active {
2093 editor.show_mouse_cursor(cx);
2094 }
2095 }),
2096 ],
2097 tasks_update_task: None,
2098 pull_diagnostics_task: Task::ready(()),
2099 colors: None,
2100 next_color_inlay_id: 0,
2101 linked_edit_ranges: Default::default(),
2102 in_project_search: false,
2103 previous_search_ranges: None,
2104 breadcrumb_header: None,
2105 focused_block: None,
2106 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2107 addons: HashMap::default(),
2108 registered_buffers: HashMap::default(),
2109 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2110 selection_mark_mode: false,
2111 toggle_fold_multiple_buffers: Task::ready(()),
2112 serialize_selections: Task::ready(()),
2113 serialize_folds: Task::ready(()),
2114 text_style_refinement: None,
2115 load_diff_task: load_uncommitted_diff,
2116 temporary_diff_override: false,
2117 mouse_cursor_hidden: false,
2118 minimap: None,
2119 hide_mouse_mode: EditorSettings::get_global(cx)
2120 .hide_mouse
2121 .unwrap_or_default(),
2122 change_list: ChangeList::new(),
2123 mode,
2124 selection_drag_state: SelectionDragState::None,
2125 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2126 };
2127 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2128 editor
2129 ._subscriptions
2130 .push(cx.observe(breakpoints, |_, _, cx| {
2131 cx.notify();
2132 }));
2133 }
2134 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2135 editor._subscriptions.extend(project_subscriptions);
2136
2137 editor._subscriptions.push(cx.subscribe_in(
2138 &cx.entity(),
2139 window,
2140 |editor, _, e: &EditorEvent, window, cx| match e {
2141 EditorEvent::ScrollPositionChanged { local, .. } => {
2142 if *local {
2143 let new_anchor = editor.scroll_manager.anchor();
2144 let snapshot = editor.snapshot(window, cx);
2145 editor.update_restoration_data(cx, move |data| {
2146 data.scroll_position = (
2147 new_anchor.top_row(&snapshot.buffer_snapshot),
2148 new_anchor.offset,
2149 );
2150 });
2151 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2152 editor.inline_blame_popover.take();
2153 }
2154 }
2155 EditorEvent::Edited { .. } => {
2156 if !vim_enabled(cx) {
2157 let (map, selections) = editor.selections.all_adjusted_display(cx);
2158 let pop_state = editor
2159 .change_list
2160 .last()
2161 .map(|previous| {
2162 previous.len() == selections.len()
2163 && previous.iter().enumerate().all(|(ix, p)| {
2164 p.to_display_point(&map).row()
2165 == selections[ix].head().row()
2166 })
2167 })
2168 .unwrap_or(false);
2169 let new_positions = selections
2170 .into_iter()
2171 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2172 .collect();
2173 editor
2174 .change_list
2175 .push_to_change_list(pop_state, new_positions);
2176 }
2177 }
2178 _ => (),
2179 },
2180 ));
2181
2182 if let Some(dap_store) = editor
2183 .project
2184 .as_ref()
2185 .map(|project| project.read(cx).dap_store())
2186 {
2187 let weak_editor = cx.weak_entity();
2188
2189 editor
2190 ._subscriptions
2191 .push(
2192 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2193 let session_entity = cx.entity();
2194 weak_editor
2195 .update(cx, |editor, cx| {
2196 editor._subscriptions.push(
2197 cx.subscribe(&session_entity, Self::on_debug_session_event),
2198 );
2199 })
2200 .ok();
2201 }),
2202 );
2203
2204 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2205 editor
2206 ._subscriptions
2207 .push(cx.subscribe(&session, Self::on_debug_session_event));
2208 }
2209 }
2210
2211 // skip adding the initial selection to selection history
2212 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2213 editor.end_selection(window, cx);
2214 editor.selection_history.mode = SelectionHistoryMode::Normal;
2215
2216 editor.scroll_manager.show_scrollbars(window, cx);
2217 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2218
2219 if full_mode {
2220 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2221 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2222
2223 if editor.git_blame_inline_enabled {
2224 editor.start_git_blame_inline(false, window, cx);
2225 }
2226
2227 editor.go_to_active_debug_line(window, cx);
2228
2229 if let Some(buffer) = buffer.read(cx).as_singleton() {
2230 if let Some(project) = editor.project.as_ref() {
2231 let handle = project.update(cx, |project, cx| {
2232 project.register_buffer_with_language_servers(&buffer, cx)
2233 });
2234 editor
2235 .registered_buffers
2236 .insert(buffer.read(cx).remote_id(), handle);
2237 }
2238 }
2239
2240 editor.minimap =
2241 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2242 editor.colors = Some(LspColorData::new(cx));
2243 editor.update_lsp_data(false, None, window, cx);
2244 }
2245
2246 editor.report_editor_event("Editor Opened", None, cx);
2247 editor
2248 }
2249
2250 pub fn deploy_mouse_context_menu(
2251 &mut self,
2252 position: gpui::Point<Pixels>,
2253 context_menu: Entity<ContextMenu>,
2254 window: &mut Window,
2255 cx: &mut Context<Self>,
2256 ) {
2257 self.mouse_context_menu = Some(MouseContextMenu::new(
2258 self,
2259 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2260 context_menu,
2261 window,
2262 cx,
2263 ));
2264 }
2265
2266 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2267 self.mouse_context_menu
2268 .as_ref()
2269 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2270 }
2271
2272 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2273 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2274 }
2275
2276 fn key_context_internal(
2277 &self,
2278 has_active_edit_prediction: bool,
2279 window: &Window,
2280 cx: &App,
2281 ) -> KeyContext {
2282 let mut key_context = KeyContext::new_with_defaults();
2283 key_context.add("Editor");
2284 let mode = match self.mode {
2285 EditorMode::SingleLine { .. } => "single_line",
2286 EditorMode::AutoHeight { .. } => "auto_height",
2287 EditorMode::Minimap { .. } => "minimap",
2288 EditorMode::Full { .. } => "full",
2289 };
2290
2291 if EditorSettings::jupyter_enabled(cx) {
2292 key_context.add("jupyter");
2293 }
2294
2295 key_context.set("mode", mode);
2296 if self.pending_rename.is_some() {
2297 key_context.add("renaming");
2298 }
2299
2300 match self.context_menu.borrow().as_ref() {
2301 Some(CodeContextMenu::Completions(_)) => {
2302 key_context.add("menu");
2303 key_context.add("showing_completions");
2304 }
2305 Some(CodeContextMenu::CodeActions(_)) => {
2306 key_context.add("menu");
2307 key_context.add("showing_code_actions")
2308 }
2309 None => {}
2310 }
2311
2312 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2313 if !self.focus_handle(cx).contains_focused(window, cx)
2314 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2315 {
2316 for addon in self.addons.values() {
2317 addon.extend_key_context(&mut key_context, cx)
2318 }
2319 }
2320
2321 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2322 if let Some(extension) = singleton_buffer
2323 .read(cx)
2324 .file()
2325 .and_then(|file| file.path().extension()?.to_str())
2326 {
2327 key_context.set("extension", extension.to_string());
2328 }
2329 } else {
2330 key_context.add("multibuffer");
2331 }
2332
2333 if has_active_edit_prediction {
2334 if self.edit_prediction_in_conflict() {
2335 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2336 } else {
2337 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2338 key_context.add("copilot_suggestion");
2339 }
2340 }
2341
2342 if self.selection_mark_mode {
2343 key_context.add("selection_mode");
2344 }
2345
2346 key_context
2347 }
2348
2349 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2350 if self.mouse_cursor_hidden {
2351 self.mouse_cursor_hidden = false;
2352 cx.notify();
2353 }
2354 }
2355
2356 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2357 let hide_mouse_cursor = match origin {
2358 HideMouseCursorOrigin::TypingAction => {
2359 matches!(
2360 self.hide_mouse_mode,
2361 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2362 )
2363 }
2364 HideMouseCursorOrigin::MovementAction => {
2365 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2366 }
2367 };
2368 if self.mouse_cursor_hidden != hide_mouse_cursor {
2369 self.mouse_cursor_hidden = hide_mouse_cursor;
2370 cx.notify();
2371 }
2372 }
2373
2374 pub fn edit_prediction_in_conflict(&self) -> bool {
2375 if !self.show_edit_predictions_in_menu() {
2376 return false;
2377 }
2378
2379 let showing_completions = self
2380 .context_menu
2381 .borrow()
2382 .as_ref()
2383 .map_or(false, |context| {
2384 matches!(context, CodeContextMenu::Completions(_))
2385 });
2386
2387 showing_completions
2388 || self.edit_prediction_requires_modifier()
2389 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2390 // bindings to insert tab characters.
2391 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2392 }
2393
2394 pub fn accept_edit_prediction_keybind(
2395 &self,
2396 accept_partial: bool,
2397 window: &Window,
2398 cx: &App,
2399 ) -> AcceptEditPredictionBinding {
2400 let key_context = self.key_context_internal(true, window, cx);
2401 let in_conflict = self.edit_prediction_in_conflict();
2402
2403 let bindings = if accept_partial {
2404 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2405 } else {
2406 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2407 };
2408
2409 // TODO: if the binding contains multiple keystrokes, display all of them, not
2410 // just the first one.
2411 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2412 !in_conflict
2413 || binding
2414 .keystrokes()
2415 .first()
2416 .map_or(false, |keystroke| keystroke.modifiers.modified())
2417 }))
2418 }
2419
2420 pub fn new_file(
2421 workspace: &mut Workspace,
2422 _: &workspace::NewFile,
2423 window: &mut Window,
2424 cx: &mut Context<Workspace>,
2425 ) {
2426 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2427 "Failed to create buffer",
2428 window,
2429 cx,
2430 |e, _, _| match e.error_code() {
2431 ErrorCode::RemoteUpgradeRequired => Some(format!(
2432 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2433 e.error_tag("required").unwrap_or("the latest version")
2434 )),
2435 _ => None,
2436 },
2437 );
2438 }
2439
2440 pub fn new_in_workspace(
2441 workspace: &mut Workspace,
2442 window: &mut Window,
2443 cx: &mut Context<Workspace>,
2444 ) -> Task<Result<Entity<Editor>>> {
2445 let project = workspace.project().clone();
2446 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2447
2448 cx.spawn_in(window, async move |workspace, cx| {
2449 let buffer = create.await?;
2450 workspace.update_in(cx, |workspace, window, cx| {
2451 let editor =
2452 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2453 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2454 editor
2455 })
2456 })
2457 }
2458
2459 fn new_file_vertical(
2460 workspace: &mut Workspace,
2461 _: &workspace::NewFileSplitVertical,
2462 window: &mut Window,
2463 cx: &mut Context<Workspace>,
2464 ) {
2465 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2466 }
2467
2468 fn new_file_horizontal(
2469 workspace: &mut Workspace,
2470 _: &workspace::NewFileSplitHorizontal,
2471 window: &mut Window,
2472 cx: &mut Context<Workspace>,
2473 ) {
2474 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2475 }
2476
2477 fn new_file_in_direction(
2478 workspace: &mut Workspace,
2479 direction: SplitDirection,
2480 window: &mut Window,
2481 cx: &mut Context<Workspace>,
2482 ) {
2483 let project = workspace.project().clone();
2484 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2485
2486 cx.spawn_in(window, async move |workspace, cx| {
2487 let buffer = create.await?;
2488 workspace.update_in(cx, move |workspace, window, cx| {
2489 workspace.split_item(
2490 direction,
2491 Box::new(
2492 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2493 ),
2494 window,
2495 cx,
2496 )
2497 })?;
2498 anyhow::Ok(())
2499 })
2500 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2501 match e.error_code() {
2502 ErrorCode::RemoteUpgradeRequired => Some(format!(
2503 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2504 e.error_tag("required").unwrap_or("the latest version")
2505 )),
2506 _ => None,
2507 }
2508 });
2509 }
2510
2511 pub fn leader_id(&self) -> Option<CollaboratorId> {
2512 self.leader_id
2513 }
2514
2515 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2516 &self.buffer
2517 }
2518
2519 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2520 self.workspace.as_ref()?.0.upgrade()
2521 }
2522
2523 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2524 self.buffer().read(cx).title(cx)
2525 }
2526
2527 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2528 let git_blame_gutter_max_author_length = self
2529 .render_git_blame_gutter(cx)
2530 .then(|| {
2531 if let Some(blame) = self.blame.as_ref() {
2532 let max_author_length =
2533 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2534 Some(max_author_length)
2535 } else {
2536 None
2537 }
2538 })
2539 .flatten();
2540
2541 EditorSnapshot {
2542 mode: self.mode.clone(),
2543 show_gutter: self.show_gutter,
2544 show_line_numbers: self.show_line_numbers,
2545 show_git_diff_gutter: self.show_git_diff_gutter,
2546 show_code_actions: self.show_code_actions,
2547 show_runnables: self.show_runnables,
2548 show_breakpoints: self.show_breakpoints,
2549 git_blame_gutter_max_author_length,
2550 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2551 scroll_anchor: self.scroll_manager.anchor(),
2552 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2553 placeholder_text: self.placeholder_text.clone(),
2554 is_focused: self.focus_handle.is_focused(window),
2555 current_line_highlight: self
2556 .current_line_highlight
2557 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2558 gutter_hovered: self.gutter_hovered,
2559 }
2560 }
2561
2562 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2563 self.buffer.read(cx).language_at(point, cx)
2564 }
2565
2566 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2567 self.buffer.read(cx).read(cx).file_at(point).cloned()
2568 }
2569
2570 pub fn active_excerpt(
2571 &self,
2572 cx: &App,
2573 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2574 self.buffer
2575 .read(cx)
2576 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2577 }
2578
2579 pub fn mode(&self) -> &EditorMode {
2580 &self.mode
2581 }
2582
2583 pub fn set_mode(&mut self, mode: EditorMode) {
2584 self.mode = mode;
2585 }
2586
2587 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2588 self.collaboration_hub.as_deref()
2589 }
2590
2591 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2592 self.collaboration_hub = Some(hub);
2593 }
2594
2595 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2596 self.in_project_search = in_project_search;
2597 }
2598
2599 pub fn set_custom_context_menu(
2600 &mut self,
2601 f: impl 'static
2602 + Fn(
2603 &mut Self,
2604 DisplayPoint,
2605 &mut Window,
2606 &mut Context<Self>,
2607 ) -> Option<Entity<ui::ContextMenu>>,
2608 ) {
2609 self.custom_context_menu = Some(Box::new(f))
2610 }
2611
2612 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2613 self.completion_provider = provider;
2614 }
2615
2616 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2617 self.semantics_provider.clone()
2618 }
2619
2620 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2621 self.semantics_provider = provider;
2622 }
2623
2624 pub fn set_edit_prediction_provider<T>(
2625 &mut self,
2626 provider: Option<Entity<T>>,
2627 window: &mut Window,
2628 cx: &mut Context<Self>,
2629 ) where
2630 T: EditPredictionProvider,
2631 {
2632 self.edit_prediction_provider =
2633 provider.map(|provider| RegisteredInlineCompletionProvider {
2634 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2635 if this.focus_handle.is_focused(window) {
2636 this.update_visible_inline_completion(window, cx);
2637 }
2638 }),
2639 provider: Arc::new(provider),
2640 });
2641 self.update_edit_prediction_settings(cx);
2642 self.refresh_inline_completion(false, false, window, cx);
2643 }
2644
2645 pub fn placeholder_text(&self) -> Option<&str> {
2646 self.placeholder_text.as_deref()
2647 }
2648
2649 pub fn set_placeholder_text(
2650 &mut self,
2651 placeholder_text: impl Into<Arc<str>>,
2652 cx: &mut Context<Self>,
2653 ) {
2654 let placeholder_text = Some(placeholder_text.into());
2655 if self.placeholder_text != placeholder_text {
2656 self.placeholder_text = placeholder_text;
2657 cx.notify();
2658 }
2659 }
2660
2661 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2662 self.cursor_shape = cursor_shape;
2663
2664 // Disrupt blink for immediate user feedback that the cursor shape has changed
2665 self.blink_manager.update(cx, BlinkManager::show_cursor);
2666
2667 cx.notify();
2668 }
2669
2670 pub fn set_current_line_highlight(
2671 &mut self,
2672 current_line_highlight: Option<CurrentLineHighlight>,
2673 ) {
2674 self.current_line_highlight = current_line_highlight;
2675 }
2676
2677 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2678 self.collapse_matches = collapse_matches;
2679 }
2680
2681 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2682 let buffers = self.buffer.read(cx).all_buffers();
2683 let Some(project) = self.project.as_ref() else {
2684 return;
2685 };
2686 project.update(cx, |project, cx| {
2687 for buffer in buffers {
2688 self.registered_buffers
2689 .entry(buffer.read(cx).remote_id())
2690 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2691 }
2692 })
2693 }
2694
2695 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2696 if self.collapse_matches {
2697 return range.start..range.start;
2698 }
2699 range.clone()
2700 }
2701
2702 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2703 if self.display_map.read(cx).clip_at_line_ends != clip {
2704 self.display_map
2705 .update(cx, |map, _| map.clip_at_line_ends = clip);
2706 }
2707 }
2708
2709 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2710 self.input_enabled = input_enabled;
2711 }
2712
2713 pub fn set_inline_completions_hidden_for_vim_mode(
2714 &mut self,
2715 hidden: bool,
2716 window: &mut Window,
2717 cx: &mut Context<Self>,
2718 ) {
2719 if hidden != self.inline_completions_hidden_for_vim_mode {
2720 self.inline_completions_hidden_for_vim_mode = hidden;
2721 if hidden {
2722 self.update_visible_inline_completion(window, cx);
2723 } else {
2724 self.refresh_inline_completion(true, false, window, cx);
2725 }
2726 }
2727 }
2728
2729 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2730 self.menu_inline_completions_policy = value;
2731 }
2732
2733 pub fn set_autoindent(&mut self, autoindent: bool) {
2734 if autoindent {
2735 self.autoindent_mode = Some(AutoindentMode::EachLine);
2736 } else {
2737 self.autoindent_mode = None;
2738 }
2739 }
2740
2741 pub fn read_only(&self, cx: &App) -> bool {
2742 self.read_only || self.buffer.read(cx).read_only()
2743 }
2744
2745 pub fn set_read_only(&mut self, read_only: bool) {
2746 self.read_only = read_only;
2747 }
2748
2749 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2750 self.use_autoclose = autoclose;
2751 }
2752
2753 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2754 self.use_auto_surround = auto_surround;
2755 }
2756
2757 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2758 self.auto_replace_emoji_shortcode = auto_replace;
2759 }
2760
2761 pub fn toggle_edit_predictions(
2762 &mut self,
2763 _: &ToggleEditPrediction,
2764 window: &mut Window,
2765 cx: &mut Context<Self>,
2766 ) {
2767 if self.show_inline_completions_override.is_some() {
2768 self.set_show_edit_predictions(None, window, cx);
2769 } else {
2770 let show_edit_predictions = !self.edit_predictions_enabled();
2771 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2772 }
2773 }
2774
2775 pub fn set_show_edit_predictions(
2776 &mut self,
2777 show_edit_predictions: Option<bool>,
2778 window: &mut Window,
2779 cx: &mut Context<Self>,
2780 ) {
2781 self.show_inline_completions_override = show_edit_predictions;
2782 self.update_edit_prediction_settings(cx);
2783
2784 if let Some(false) = show_edit_predictions {
2785 self.discard_inline_completion(false, cx);
2786 } else {
2787 self.refresh_inline_completion(false, true, window, cx);
2788 }
2789 }
2790
2791 fn inline_completions_disabled_in_scope(
2792 &self,
2793 buffer: &Entity<Buffer>,
2794 buffer_position: language::Anchor,
2795 cx: &App,
2796 ) -> bool {
2797 let snapshot = buffer.read(cx).snapshot();
2798 let settings = snapshot.settings_at(buffer_position, cx);
2799
2800 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2801 return false;
2802 };
2803
2804 scope.override_name().map_or(false, |scope_name| {
2805 settings
2806 .edit_predictions_disabled_in
2807 .iter()
2808 .any(|s| s == scope_name)
2809 })
2810 }
2811
2812 pub fn set_use_modal_editing(&mut self, to: bool) {
2813 self.use_modal_editing = to;
2814 }
2815
2816 pub fn use_modal_editing(&self) -> bool {
2817 self.use_modal_editing
2818 }
2819
2820 fn selections_did_change(
2821 &mut self,
2822 local: bool,
2823 old_cursor_position: &Anchor,
2824 effects: SelectionEffects,
2825 window: &mut Window,
2826 cx: &mut Context<Self>,
2827 ) {
2828 window.invalidate_character_coordinates();
2829
2830 // Copy selections to primary selection buffer
2831 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2832 if local {
2833 let selections = self.selections.all::<usize>(cx);
2834 let buffer_handle = self.buffer.read(cx).read(cx);
2835
2836 let mut text = String::new();
2837 for (index, selection) in selections.iter().enumerate() {
2838 let text_for_selection = buffer_handle
2839 .text_for_range(selection.start..selection.end)
2840 .collect::<String>();
2841
2842 text.push_str(&text_for_selection);
2843 if index != selections.len() - 1 {
2844 text.push('\n');
2845 }
2846 }
2847
2848 if !text.is_empty() {
2849 cx.write_to_primary(ClipboardItem::new_string(text));
2850 }
2851 }
2852
2853 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2854 self.buffer.update(cx, |buffer, cx| {
2855 buffer.set_active_selections(
2856 &self.selections.disjoint_anchors(),
2857 self.selections.line_mode,
2858 self.cursor_shape,
2859 cx,
2860 )
2861 });
2862 }
2863 let display_map = self
2864 .display_map
2865 .update(cx, |display_map, cx| display_map.snapshot(cx));
2866 let buffer = &display_map.buffer_snapshot;
2867 if self.selections.count() == 1 {
2868 self.add_selections_state = None;
2869 }
2870 self.select_next_state = None;
2871 self.select_prev_state = None;
2872 self.select_syntax_node_history.try_clear();
2873 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2874 self.snippet_stack
2875 .invalidate(&self.selections.disjoint_anchors(), buffer);
2876 self.take_rename(false, window, cx);
2877
2878 let newest_selection = self.selections.newest_anchor();
2879 let new_cursor_position = newest_selection.head();
2880 let selection_start = newest_selection.start;
2881
2882 if effects.nav_history {
2883 self.push_to_nav_history(
2884 *old_cursor_position,
2885 Some(new_cursor_position.to_point(buffer)),
2886 false,
2887 cx,
2888 );
2889 }
2890
2891 if local {
2892 if let Some(buffer_id) = new_cursor_position.buffer_id {
2893 if !self.registered_buffers.contains_key(&buffer_id) {
2894 if let Some(project) = self.project.as_ref() {
2895 project.update(cx, |project, cx| {
2896 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2897 return;
2898 };
2899 self.registered_buffers.insert(
2900 buffer_id,
2901 project.register_buffer_with_language_servers(&buffer, cx),
2902 );
2903 })
2904 }
2905 }
2906 }
2907
2908 let mut context_menu = self.context_menu.borrow_mut();
2909 let completion_menu = match context_menu.as_ref() {
2910 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2911 Some(CodeContextMenu::CodeActions(_)) => {
2912 *context_menu = None;
2913 None
2914 }
2915 None => None,
2916 };
2917 let completion_position = completion_menu.map(|menu| menu.initial_position);
2918 drop(context_menu);
2919
2920 if effects.completions {
2921 if let Some(completion_position) = completion_position {
2922 let start_offset = selection_start.to_offset(buffer);
2923 let position_matches = start_offset == completion_position.to_offset(buffer);
2924 let continue_showing = if position_matches {
2925 if self.snippet_stack.is_empty() {
2926 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2927 } else {
2928 // Snippet choices can be shown even when the cursor is in whitespace.
2929 // Dismissing the menu with actions like backspace is handled by
2930 // invalidation regions.
2931 true
2932 }
2933 } else {
2934 false
2935 };
2936
2937 if continue_showing {
2938 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2939 } else {
2940 self.hide_context_menu(window, cx);
2941 }
2942 }
2943 }
2944
2945 hide_hover(self, cx);
2946
2947 if old_cursor_position.to_display_point(&display_map).row()
2948 != new_cursor_position.to_display_point(&display_map).row()
2949 {
2950 self.available_code_actions.take();
2951 }
2952 self.refresh_code_actions(window, cx);
2953 self.refresh_document_highlights(cx);
2954 self.refresh_selected_text_highlights(false, window, cx);
2955 refresh_matching_bracket_highlights(self, window, cx);
2956 self.update_visible_inline_completion(window, cx);
2957 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2958 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2959 self.inline_blame_popover.take();
2960 if self.git_blame_inline_enabled {
2961 self.start_inline_blame_timer(window, cx);
2962 }
2963 }
2964
2965 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2966 cx.emit(EditorEvent::SelectionsChanged { local });
2967
2968 let selections = &self.selections.disjoint;
2969 if selections.len() == 1 {
2970 cx.emit(SearchEvent::ActiveMatchChanged)
2971 }
2972 if local {
2973 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2974 let inmemory_selections = selections
2975 .iter()
2976 .map(|s| {
2977 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2978 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2979 })
2980 .collect();
2981 self.update_restoration_data(cx, |data| {
2982 data.selections = inmemory_selections;
2983 });
2984
2985 if WorkspaceSettings::get(None, cx).restore_on_startup
2986 != RestoreOnStartupBehavior::None
2987 {
2988 if let Some(workspace_id) =
2989 self.workspace.as_ref().and_then(|workspace| workspace.1)
2990 {
2991 let snapshot = self.buffer().read(cx).snapshot(cx);
2992 let selections = selections.clone();
2993 let background_executor = cx.background_executor().clone();
2994 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2995 self.serialize_selections = cx.background_spawn(async move {
2996 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2997 let db_selections = selections
2998 .iter()
2999 .map(|selection| {
3000 (
3001 selection.start.to_offset(&snapshot),
3002 selection.end.to_offset(&snapshot),
3003 )
3004 })
3005 .collect();
3006
3007 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3008 .await
3009 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3010 .log_err();
3011 });
3012 }
3013 }
3014 }
3015 }
3016
3017 cx.notify();
3018 }
3019
3020 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3021 use text::ToOffset as _;
3022 use text::ToPoint as _;
3023
3024 if self.mode.is_minimap()
3025 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3026 {
3027 return;
3028 }
3029
3030 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3031 return;
3032 };
3033
3034 let snapshot = singleton.read(cx).snapshot();
3035 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3036 let display_snapshot = display_map.snapshot(cx);
3037
3038 display_snapshot
3039 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3040 .map(|fold| {
3041 fold.range.start.text_anchor.to_point(&snapshot)
3042 ..fold.range.end.text_anchor.to_point(&snapshot)
3043 })
3044 .collect()
3045 });
3046 self.update_restoration_data(cx, |data| {
3047 data.folds = inmemory_folds;
3048 });
3049
3050 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3051 return;
3052 };
3053 let background_executor = cx.background_executor().clone();
3054 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3055 let db_folds = self.display_map.update(cx, |display_map, cx| {
3056 display_map
3057 .snapshot(cx)
3058 .folds_in_range(0..snapshot.len())
3059 .map(|fold| {
3060 (
3061 fold.range.start.text_anchor.to_offset(&snapshot),
3062 fold.range.end.text_anchor.to_offset(&snapshot),
3063 )
3064 })
3065 .collect()
3066 });
3067 self.serialize_folds = cx.background_spawn(async move {
3068 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3069 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3070 .await
3071 .with_context(|| {
3072 format!(
3073 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3074 )
3075 })
3076 .log_err();
3077 });
3078 }
3079
3080 pub fn sync_selections(
3081 &mut self,
3082 other: Entity<Editor>,
3083 cx: &mut Context<Self>,
3084 ) -> gpui::Subscription {
3085 let other_selections = other.read(cx).selections.disjoint.to_vec();
3086 self.selections.change_with(cx, |selections| {
3087 selections.select_anchors(other_selections);
3088 });
3089
3090 let other_subscription =
3091 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3092 EditorEvent::SelectionsChanged { local: true } => {
3093 let other_selections = other.read(cx).selections.disjoint.to_vec();
3094 if other_selections.is_empty() {
3095 return;
3096 }
3097 this.selections.change_with(cx, |selections| {
3098 selections.select_anchors(other_selections);
3099 });
3100 }
3101 _ => {}
3102 });
3103
3104 let this_subscription =
3105 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3106 EditorEvent::SelectionsChanged { local: true } => {
3107 let these_selections = this.selections.disjoint.to_vec();
3108 if these_selections.is_empty() {
3109 return;
3110 }
3111 other.update(cx, |other_editor, cx| {
3112 other_editor.selections.change_with(cx, |selections| {
3113 selections.select_anchors(these_selections);
3114 })
3115 });
3116 }
3117 _ => {}
3118 });
3119
3120 Subscription::join(other_subscription, this_subscription)
3121 }
3122
3123 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3124 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3125 /// effects of selection change occur at the end of the transaction.
3126 pub fn change_selections<R>(
3127 &mut self,
3128 effects: impl Into<SelectionEffects>,
3129 window: &mut Window,
3130 cx: &mut Context<Self>,
3131 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3132 ) -> R {
3133 let effects = effects.into();
3134 if let Some(state) = &mut self.deferred_selection_effects_state {
3135 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3136 state.effects.completions = effects.completions;
3137 state.effects.nav_history |= effects.nav_history;
3138 let (changed, result) = self.selections.change_with(cx, change);
3139 state.changed |= changed;
3140 return result;
3141 }
3142 let mut state = DeferredSelectionEffectsState {
3143 changed: false,
3144 effects,
3145 old_cursor_position: self.selections.newest_anchor().head(),
3146 history_entry: SelectionHistoryEntry {
3147 selections: self.selections.disjoint_anchors(),
3148 select_next_state: self.select_next_state.clone(),
3149 select_prev_state: self.select_prev_state.clone(),
3150 add_selections_state: self.add_selections_state.clone(),
3151 },
3152 };
3153 let (changed, result) = self.selections.change_with(cx, change);
3154 state.changed = state.changed || changed;
3155 if self.defer_selection_effects {
3156 self.deferred_selection_effects_state = Some(state);
3157 } else {
3158 self.apply_selection_effects(state, window, cx);
3159 }
3160 result
3161 }
3162
3163 /// Defers the effects of selection change, so that the effects of multiple calls to
3164 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3165 /// to selection history and the state of popovers based on selection position aren't
3166 /// erroneously updated.
3167 pub fn with_selection_effects_deferred<R>(
3168 &mut self,
3169 window: &mut Window,
3170 cx: &mut Context<Self>,
3171 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3172 ) -> R {
3173 let already_deferred = self.defer_selection_effects;
3174 self.defer_selection_effects = true;
3175 let result = update(self, window, cx);
3176 if !already_deferred {
3177 self.defer_selection_effects = false;
3178 if let Some(state) = self.deferred_selection_effects_state.take() {
3179 self.apply_selection_effects(state, window, cx);
3180 }
3181 }
3182 result
3183 }
3184
3185 fn apply_selection_effects(
3186 &mut self,
3187 state: DeferredSelectionEffectsState,
3188 window: &mut Window,
3189 cx: &mut Context<Self>,
3190 ) {
3191 if state.changed {
3192 self.selection_history.push(state.history_entry);
3193
3194 if let Some(autoscroll) = state.effects.scroll {
3195 self.request_autoscroll(autoscroll, cx);
3196 }
3197
3198 let old_cursor_position = &state.old_cursor_position;
3199
3200 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3201
3202 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3203 self.show_signature_help(&ShowSignatureHelp, window, cx);
3204 }
3205 }
3206 }
3207
3208 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3209 where
3210 I: IntoIterator<Item = (Range<S>, T)>,
3211 S: ToOffset,
3212 T: Into<Arc<str>>,
3213 {
3214 if self.read_only(cx) {
3215 return;
3216 }
3217
3218 self.buffer
3219 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3220 }
3221
3222 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3223 where
3224 I: IntoIterator<Item = (Range<S>, T)>,
3225 S: ToOffset,
3226 T: Into<Arc<str>>,
3227 {
3228 if self.read_only(cx) {
3229 return;
3230 }
3231
3232 self.buffer.update(cx, |buffer, cx| {
3233 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3234 });
3235 }
3236
3237 pub fn edit_with_block_indent<I, S, T>(
3238 &mut self,
3239 edits: I,
3240 original_indent_columns: Vec<Option<u32>>,
3241 cx: &mut Context<Self>,
3242 ) where
3243 I: IntoIterator<Item = (Range<S>, T)>,
3244 S: ToOffset,
3245 T: Into<Arc<str>>,
3246 {
3247 if self.read_only(cx) {
3248 return;
3249 }
3250
3251 self.buffer.update(cx, |buffer, cx| {
3252 buffer.edit(
3253 edits,
3254 Some(AutoindentMode::Block {
3255 original_indent_columns,
3256 }),
3257 cx,
3258 )
3259 });
3260 }
3261
3262 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3263 self.hide_context_menu(window, cx);
3264
3265 match phase {
3266 SelectPhase::Begin {
3267 position,
3268 add,
3269 click_count,
3270 } => self.begin_selection(position, add, click_count, window, cx),
3271 SelectPhase::BeginColumnar {
3272 position,
3273 goal_column,
3274 reset,
3275 mode,
3276 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3277 SelectPhase::Extend {
3278 position,
3279 click_count,
3280 } => self.extend_selection(position, click_count, window, cx),
3281 SelectPhase::Update {
3282 position,
3283 goal_column,
3284 scroll_delta,
3285 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3286 SelectPhase::End => self.end_selection(window, cx),
3287 }
3288 }
3289
3290 fn extend_selection(
3291 &mut self,
3292 position: DisplayPoint,
3293 click_count: usize,
3294 window: &mut Window,
3295 cx: &mut Context<Self>,
3296 ) {
3297 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3298 let tail = self.selections.newest::<usize>(cx).tail();
3299 self.begin_selection(position, false, click_count, window, cx);
3300
3301 let position = position.to_offset(&display_map, Bias::Left);
3302 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3303
3304 let mut pending_selection = self
3305 .selections
3306 .pending_anchor()
3307 .expect("extend_selection not called with pending selection");
3308 if position >= tail {
3309 pending_selection.start = tail_anchor;
3310 } else {
3311 pending_selection.end = tail_anchor;
3312 pending_selection.reversed = true;
3313 }
3314
3315 let mut pending_mode = self.selections.pending_mode().unwrap();
3316 match &mut pending_mode {
3317 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3318 _ => {}
3319 }
3320
3321 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3322 SelectionEffects::scroll(Autoscroll::fit())
3323 } else {
3324 SelectionEffects::no_scroll()
3325 };
3326
3327 self.change_selections(effects, window, cx, |s| {
3328 s.set_pending(pending_selection, pending_mode)
3329 });
3330 }
3331
3332 fn begin_selection(
3333 &mut self,
3334 position: DisplayPoint,
3335 add: bool,
3336 click_count: usize,
3337 window: &mut Window,
3338 cx: &mut Context<Self>,
3339 ) {
3340 if !self.focus_handle.is_focused(window) {
3341 self.last_focused_descendant = None;
3342 window.focus(&self.focus_handle);
3343 }
3344
3345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3346 let buffer = &display_map.buffer_snapshot;
3347 let position = display_map.clip_point(position, Bias::Left);
3348
3349 let start;
3350 let end;
3351 let mode;
3352 let mut auto_scroll;
3353 match click_count {
3354 1 => {
3355 start = buffer.anchor_before(position.to_point(&display_map));
3356 end = start;
3357 mode = SelectMode::Character;
3358 auto_scroll = true;
3359 }
3360 2 => {
3361 let range = movement::surrounding_word(&display_map, position);
3362 start = buffer.anchor_before(range.start.to_point(&display_map));
3363 end = buffer.anchor_before(range.end.to_point(&display_map));
3364 mode = SelectMode::Word(start..end);
3365 auto_scroll = true;
3366 }
3367 3 => {
3368 let position = display_map
3369 .clip_point(position, Bias::Left)
3370 .to_point(&display_map);
3371 let line_start = display_map.prev_line_boundary(position).0;
3372 let next_line_start = buffer.clip_point(
3373 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3374 Bias::Left,
3375 );
3376 start = buffer.anchor_before(line_start);
3377 end = buffer.anchor_before(next_line_start);
3378 mode = SelectMode::Line(start..end);
3379 auto_scroll = true;
3380 }
3381 _ => {
3382 start = buffer.anchor_before(0);
3383 end = buffer.anchor_before(buffer.len());
3384 mode = SelectMode::All;
3385 auto_scroll = false;
3386 }
3387 }
3388 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3389
3390 let point_to_delete: Option<usize> = {
3391 let selected_points: Vec<Selection<Point>> =
3392 self.selections.disjoint_in_range(start..end, cx);
3393
3394 if !add || click_count > 1 {
3395 None
3396 } else if !selected_points.is_empty() {
3397 Some(selected_points[0].id)
3398 } else {
3399 let clicked_point_already_selected =
3400 self.selections.disjoint.iter().find(|selection| {
3401 selection.start.to_point(buffer) == start.to_point(buffer)
3402 || selection.end.to_point(buffer) == end.to_point(buffer)
3403 });
3404
3405 clicked_point_already_selected.map(|selection| selection.id)
3406 }
3407 };
3408
3409 let selections_count = self.selections.count();
3410
3411 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3412 if let Some(point_to_delete) = point_to_delete {
3413 s.delete(point_to_delete);
3414
3415 if selections_count == 1 {
3416 s.set_pending_anchor_range(start..end, mode);
3417 }
3418 } else {
3419 if !add {
3420 s.clear_disjoint();
3421 }
3422
3423 s.set_pending_anchor_range(start..end, mode);
3424 }
3425 });
3426 }
3427
3428 fn begin_columnar_selection(
3429 &mut self,
3430 position: DisplayPoint,
3431 goal_column: u32,
3432 reset: bool,
3433 mode: ColumnarMode,
3434 window: &mut Window,
3435 cx: &mut Context<Self>,
3436 ) {
3437 if !self.focus_handle.is_focused(window) {
3438 self.last_focused_descendant = None;
3439 window.focus(&self.focus_handle);
3440 }
3441
3442 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3443
3444 if reset {
3445 let pointer_position = display_map
3446 .buffer_snapshot
3447 .anchor_before(position.to_point(&display_map));
3448
3449 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3450 s.clear_disjoint();
3451 s.set_pending_anchor_range(
3452 pointer_position..pointer_position,
3453 SelectMode::Character,
3454 );
3455 });
3456 };
3457
3458 let tail = self.selections.newest::<Point>(cx).tail();
3459 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3460 self.columnar_selection_state = match mode {
3461 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3462 selection_tail: selection_anchor,
3463 display_point: if reset {
3464 if position.column() != goal_column {
3465 Some(DisplayPoint::new(position.row(), goal_column))
3466 } else {
3467 None
3468 }
3469 } else {
3470 None
3471 },
3472 }),
3473 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3474 selection_tail: selection_anchor,
3475 }),
3476 };
3477
3478 if !reset {
3479 self.select_columns(position, goal_column, &display_map, window, cx);
3480 }
3481 }
3482
3483 fn update_selection(
3484 &mut self,
3485 position: DisplayPoint,
3486 goal_column: u32,
3487 scroll_delta: gpui::Point<f32>,
3488 window: &mut Window,
3489 cx: &mut Context<Self>,
3490 ) {
3491 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3492
3493 if self.columnar_selection_state.is_some() {
3494 self.select_columns(position, goal_column, &display_map, window, cx);
3495 } else if let Some(mut pending) = self.selections.pending_anchor() {
3496 let buffer = self.buffer.read(cx).snapshot(cx);
3497 let head;
3498 let tail;
3499 let mode = self.selections.pending_mode().unwrap();
3500 match &mode {
3501 SelectMode::Character => {
3502 head = position.to_point(&display_map);
3503 tail = pending.tail().to_point(&buffer);
3504 }
3505 SelectMode::Word(original_range) => {
3506 let original_display_range = original_range.start.to_display_point(&display_map)
3507 ..original_range.end.to_display_point(&display_map);
3508 let original_buffer_range = original_display_range.start.to_point(&display_map)
3509 ..original_display_range.end.to_point(&display_map);
3510 if movement::is_inside_word(&display_map, position)
3511 || original_display_range.contains(&position)
3512 {
3513 let word_range = movement::surrounding_word(&display_map, position);
3514 if word_range.start < original_display_range.start {
3515 head = word_range.start.to_point(&display_map);
3516 } else {
3517 head = word_range.end.to_point(&display_map);
3518 }
3519 } else {
3520 head = position.to_point(&display_map);
3521 }
3522
3523 if head <= original_buffer_range.start {
3524 tail = original_buffer_range.end;
3525 } else {
3526 tail = original_buffer_range.start;
3527 }
3528 }
3529 SelectMode::Line(original_range) => {
3530 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3531
3532 let position = display_map
3533 .clip_point(position, Bias::Left)
3534 .to_point(&display_map);
3535 let line_start = display_map.prev_line_boundary(position).0;
3536 let next_line_start = buffer.clip_point(
3537 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3538 Bias::Left,
3539 );
3540
3541 if line_start < original_range.start {
3542 head = line_start
3543 } else {
3544 head = next_line_start
3545 }
3546
3547 if head <= original_range.start {
3548 tail = original_range.end;
3549 } else {
3550 tail = original_range.start;
3551 }
3552 }
3553 SelectMode::All => {
3554 return;
3555 }
3556 };
3557
3558 if head < tail {
3559 pending.start = buffer.anchor_before(head);
3560 pending.end = buffer.anchor_before(tail);
3561 pending.reversed = true;
3562 } else {
3563 pending.start = buffer.anchor_before(tail);
3564 pending.end = buffer.anchor_before(head);
3565 pending.reversed = false;
3566 }
3567
3568 self.change_selections(None, window, cx, |s| {
3569 s.set_pending(pending, mode);
3570 });
3571 } else {
3572 log::error!("update_selection dispatched with no pending selection");
3573 return;
3574 }
3575
3576 self.apply_scroll_delta(scroll_delta, window, cx);
3577 cx.notify();
3578 }
3579
3580 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3581 self.columnar_selection_state.take();
3582 if self.selections.pending_anchor().is_some() {
3583 let selections = self.selections.all::<usize>(cx);
3584 self.change_selections(None, window, cx, |s| {
3585 s.select(selections);
3586 s.clear_pending();
3587 });
3588 }
3589 }
3590
3591 fn select_columns(
3592 &mut self,
3593 head: DisplayPoint,
3594 goal_column: u32,
3595 display_map: &DisplaySnapshot,
3596 window: &mut Window,
3597 cx: &mut Context<Self>,
3598 ) {
3599 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3600 return;
3601 };
3602
3603 let tail = match columnar_state {
3604 ColumnarSelectionState::FromMouse {
3605 selection_tail,
3606 display_point,
3607 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3608 ColumnarSelectionState::FromSelection { selection_tail } => {
3609 selection_tail.to_display_point(&display_map)
3610 }
3611 };
3612
3613 let start_row = cmp::min(tail.row(), head.row());
3614 let end_row = cmp::max(tail.row(), head.row());
3615 let start_column = cmp::min(tail.column(), goal_column);
3616 let end_column = cmp::max(tail.column(), goal_column);
3617 let reversed = start_column < tail.column();
3618
3619 let selection_ranges = (start_row.0..=end_row.0)
3620 .map(DisplayRow)
3621 .filter_map(|row| {
3622 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3623 || start_column <= display_map.line_len(row))
3624 && !display_map.is_block_line(row)
3625 {
3626 let start = display_map
3627 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3628 .to_point(display_map);
3629 let end = display_map
3630 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3631 .to_point(display_map);
3632 if reversed {
3633 Some(end..start)
3634 } else {
3635 Some(start..end)
3636 }
3637 } else {
3638 None
3639 }
3640 })
3641 .collect::<Vec<_>>();
3642
3643 let ranges = match columnar_state {
3644 ColumnarSelectionState::FromMouse { .. } => {
3645 let mut non_empty_ranges = selection_ranges
3646 .iter()
3647 .filter(|selection_range| selection_range.start != selection_range.end)
3648 .peekable();
3649 if non_empty_ranges.peek().is_some() {
3650 non_empty_ranges.cloned().collect()
3651 } else {
3652 selection_ranges
3653 }
3654 }
3655 _ => selection_ranges,
3656 };
3657
3658 self.change_selections(None, window, cx, |s| {
3659 s.select_ranges(ranges);
3660 });
3661 cx.notify();
3662 }
3663
3664 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3665 self.selections
3666 .all_adjusted(cx)
3667 .iter()
3668 .any(|selection| !selection.is_empty())
3669 }
3670
3671 pub fn has_pending_nonempty_selection(&self) -> bool {
3672 let pending_nonempty_selection = match self.selections.pending_anchor() {
3673 Some(Selection { start, end, .. }) => start != end,
3674 None => false,
3675 };
3676
3677 pending_nonempty_selection
3678 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3679 }
3680
3681 pub fn has_pending_selection(&self) -> bool {
3682 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3683 }
3684
3685 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3686 self.selection_mark_mode = false;
3687 self.selection_drag_state = SelectionDragState::None;
3688
3689 if self.clear_expanded_diff_hunks(cx) {
3690 cx.notify();
3691 return;
3692 }
3693 if self.dismiss_menus_and_popups(true, window, cx) {
3694 return;
3695 }
3696
3697 if self.mode.is_full()
3698 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3699 {
3700 return;
3701 }
3702
3703 cx.propagate();
3704 }
3705
3706 pub fn dismiss_menus_and_popups(
3707 &mut self,
3708 is_user_requested: bool,
3709 window: &mut Window,
3710 cx: &mut Context<Self>,
3711 ) -> bool {
3712 if self.take_rename(false, window, cx).is_some() {
3713 return true;
3714 }
3715
3716 if hide_hover(self, cx) {
3717 return true;
3718 }
3719
3720 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3721 return true;
3722 }
3723
3724 if self.hide_context_menu(window, cx).is_some() {
3725 return true;
3726 }
3727
3728 if self.mouse_context_menu.take().is_some() {
3729 return true;
3730 }
3731
3732 if is_user_requested && self.discard_inline_completion(true, cx) {
3733 return true;
3734 }
3735
3736 if self.snippet_stack.pop().is_some() {
3737 return true;
3738 }
3739
3740 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3741 self.dismiss_diagnostics(cx);
3742 return true;
3743 }
3744
3745 false
3746 }
3747
3748 fn linked_editing_ranges_for(
3749 &self,
3750 selection: Range<text::Anchor>,
3751 cx: &App,
3752 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3753 if self.linked_edit_ranges.is_empty() {
3754 return None;
3755 }
3756 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3757 selection.end.buffer_id.and_then(|end_buffer_id| {
3758 if selection.start.buffer_id != Some(end_buffer_id) {
3759 return None;
3760 }
3761 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3762 let snapshot = buffer.read(cx).snapshot();
3763 self.linked_edit_ranges
3764 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3765 .map(|ranges| (ranges, snapshot, buffer))
3766 })?;
3767 use text::ToOffset as TO;
3768 // find offset from the start of current range to current cursor position
3769 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3770
3771 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3772 let start_difference = start_offset - start_byte_offset;
3773 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3774 let end_difference = end_offset - start_byte_offset;
3775 // Current range has associated linked ranges.
3776 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3777 for range in linked_ranges.iter() {
3778 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3779 let end_offset = start_offset + end_difference;
3780 let start_offset = start_offset + start_difference;
3781 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3782 continue;
3783 }
3784 if self.selections.disjoint_anchor_ranges().any(|s| {
3785 if s.start.buffer_id != selection.start.buffer_id
3786 || s.end.buffer_id != selection.end.buffer_id
3787 {
3788 return false;
3789 }
3790 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3791 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3792 }) {
3793 continue;
3794 }
3795 let start = buffer_snapshot.anchor_after(start_offset);
3796 let end = buffer_snapshot.anchor_after(end_offset);
3797 linked_edits
3798 .entry(buffer.clone())
3799 .or_default()
3800 .push(start..end);
3801 }
3802 Some(linked_edits)
3803 }
3804
3805 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3806 let text: Arc<str> = text.into();
3807
3808 if self.read_only(cx) {
3809 return;
3810 }
3811
3812 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3813
3814 let selections = self.selections.all_adjusted(cx);
3815 let mut bracket_inserted = false;
3816 let mut edits = Vec::new();
3817 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3818 let mut new_selections = Vec::with_capacity(selections.len());
3819 let mut new_autoclose_regions = Vec::new();
3820 let snapshot = self.buffer.read(cx).read(cx);
3821 let mut clear_linked_edit_ranges = false;
3822
3823 for (selection, autoclose_region) in
3824 self.selections_with_autoclose_regions(selections, &snapshot)
3825 {
3826 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3827 // Determine if the inserted text matches the opening or closing
3828 // bracket of any of this language's bracket pairs.
3829 let mut bracket_pair = None;
3830 let mut is_bracket_pair_start = false;
3831 let mut is_bracket_pair_end = false;
3832 if !text.is_empty() {
3833 let mut bracket_pair_matching_end = None;
3834 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3835 // and they are removing the character that triggered IME popup.
3836 for (pair, enabled) in scope.brackets() {
3837 if !pair.close && !pair.surround {
3838 continue;
3839 }
3840
3841 if enabled && pair.start.ends_with(text.as_ref()) {
3842 let prefix_len = pair.start.len() - text.len();
3843 let preceding_text_matches_prefix = prefix_len == 0
3844 || (selection.start.column >= (prefix_len as u32)
3845 && snapshot.contains_str_at(
3846 Point::new(
3847 selection.start.row,
3848 selection.start.column - (prefix_len as u32),
3849 ),
3850 &pair.start[..prefix_len],
3851 ));
3852 if preceding_text_matches_prefix {
3853 bracket_pair = Some(pair.clone());
3854 is_bracket_pair_start = true;
3855 break;
3856 }
3857 }
3858 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3859 {
3860 // take first bracket pair matching end, but don't break in case a later bracket
3861 // pair matches start
3862 bracket_pair_matching_end = Some(pair.clone());
3863 }
3864 }
3865 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3866 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3867 is_bracket_pair_end = true;
3868 }
3869 }
3870
3871 if let Some(bracket_pair) = bracket_pair {
3872 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3873 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3874 let auto_surround =
3875 self.use_auto_surround && snapshot_settings.use_auto_surround;
3876 if selection.is_empty() {
3877 if is_bracket_pair_start {
3878 // If the inserted text is a suffix of an opening bracket and the
3879 // selection is preceded by the rest of the opening bracket, then
3880 // insert the closing bracket.
3881 let following_text_allows_autoclose = snapshot
3882 .chars_at(selection.start)
3883 .next()
3884 .map_or(true, |c| scope.should_autoclose_before(c));
3885
3886 let preceding_text_allows_autoclose = selection.start.column == 0
3887 || snapshot.reversed_chars_at(selection.start).next().map_or(
3888 true,
3889 |c| {
3890 bracket_pair.start != bracket_pair.end
3891 || !snapshot
3892 .char_classifier_at(selection.start)
3893 .is_word(c)
3894 },
3895 );
3896
3897 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3898 && bracket_pair.start.len() == 1
3899 {
3900 let target = bracket_pair.start.chars().next().unwrap();
3901 let current_line_count = snapshot
3902 .reversed_chars_at(selection.start)
3903 .take_while(|&c| c != '\n')
3904 .filter(|&c| c == target)
3905 .count();
3906 current_line_count % 2 == 1
3907 } else {
3908 false
3909 };
3910
3911 if autoclose
3912 && bracket_pair.close
3913 && following_text_allows_autoclose
3914 && preceding_text_allows_autoclose
3915 && !is_closing_quote
3916 {
3917 let anchor = snapshot.anchor_before(selection.end);
3918 new_selections.push((selection.map(|_| anchor), text.len()));
3919 new_autoclose_regions.push((
3920 anchor,
3921 text.len(),
3922 selection.id,
3923 bracket_pair.clone(),
3924 ));
3925 edits.push((
3926 selection.range(),
3927 format!("{}{}", text, bracket_pair.end).into(),
3928 ));
3929 bracket_inserted = true;
3930 continue;
3931 }
3932 }
3933
3934 if let Some(region) = autoclose_region {
3935 // If the selection is followed by an auto-inserted closing bracket,
3936 // then don't insert that closing bracket again; just move the selection
3937 // past the closing bracket.
3938 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3939 && text.as_ref() == region.pair.end.as_str();
3940 if should_skip {
3941 let anchor = snapshot.anchor_after(selection.end);
3942 new_selections
3943 .push((selection.map(|_| anchor), region.pair.end.len()));
3944 continue;
3945 }
3946 }
3947
3948 let always_treat_brackets_as_autoclosed = snapshot
3949 .language_settings_at(selection.start, cx)
3950 .always_treat_brackets_as_autoclosed;
3951 if always_treat_brackets_as_autoclosed
3952 && is_bracket_pair_end
3953 && snapshot.contains_str_at(selection.end, text.as_ref())
3954 {
3955 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3956 // and the inserted text is a closing bracket and the selection is followed
3957 // by the closing bracket then move the selection past the closing bracket.
3958 let anchor = snapshot.anchor_after(selection.end);
3959 new_selections.push((selection.map(|_| anchor), text.len()));
3960 continue;
3961 }
3962 }
3963 // If an opening bracket is 1 character long and is typed while
3964 // text is selected, then surround that text with the bracket pair.
3965 else if auto_surround
3966 && bracket_pair.surround
3967 && is_bracket_pair_start
3968 && bracket_pair.start.chars().count() == 1
3969 {
3970 edits.push((selection.start..selection.start, text.clone()));
3971 edits.push((
3972 selection.end..selection.end,
3973 bracket_pair.end.as_str().into(),
3974 ));
3975 bracket_inserted = true;
3976 new_selections.push((
3977 Selection {
3978 id: selection.id,
3979 start: snapshot.anchor_after(selection.start),
3980 end: snapshot.anchor_before(selection.end),
3981 reversed: selection.reversed,
3982 goal: selection.goal,
3983 },
3984 0,
3985 ));
3986 continue;
3987 }
3988 }
3989 }
3990
3991 if self.auto_replace_emoji_shortcode
3992 && selection.is_empty()
3993 && text.as_ref().ends_with(':')
3994 {
3995 if let Some(possible_emoji_short_code) =
3996 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3997 {
3998 if !possible_emoji_short_code.is_empty() {
3999 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4000 let emoji_shortcode_start = Point::new(
4001 selection.start.row,
4002 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4003 );
4004
4005 // Remove shortcode from buffer
4006 edits.push((
4007 emoji_shortcode_start..selection.start,
4008 "".to_string().into(),
4009 ));
4010 new_selections.push((
4011 Selection {
4012 id: selection.id,
4013 start: snapshot.anchor_after(emoji_shortcode_start),
4014 end: snapshot.anchor_before(selection.start),
4015 reversed: selection.reversed,
4016 goal: selection.goal,
4017 },
4018 0,
4019 ));
4020
4021 // Insert emoji
4022 let selection_start_anchor = snapshot.anchor_after(selection.start);
4023 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4024 edits.push((selection.start..selection.end, emoji.to_string().into()));
4025
4026 continue;
4027 }
4028 }
4029 }
4030 }
4031
4032 // If not handling any auto-close operation, then just replace the selected
4033 // text with the given input and move the selection to the end of the
4034 // newly inserted text.
4035 let anchor = snapshot.anchor_after(selection.end);
4036 if !self.linked_edit_ranges.is_empty() {
4037 let start_anchor = snapshot.anchor_before(selection.start);
4038
4039 let is_word_char = text.chars().next().map_or(true, |char| {
4040 let classifier = snapshot
4041 .char_classifier_at(start_anchor.to_offset(&snapshot))
4042 .ignore_punctuation(true);
4043 classifier.is_word(char)
4044 });
4045
4046 if is_word_char {
4047 if let Some(ranges) = self
4048 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4049 {
4050 for (buffer, edits) in ranges {
4051 linked_edits
4052 .entry(buffer.clone())
4053 .or_default()
4054 .extend(edits.into_iter().map(|range| (range, text.clone())));
4055 }
4056 }
4057 } else {
4058 clear_linked_edit_ranges = true;
4059 }
4060 }
4061
4062 new_selections.push((selection.map(|_| anchor), 0));
4063 edits.push((selection.start..selection.end, text.clone()));
4064 }
4065
4066 drop(snapshot);
4067
4068 self.transact(window, cx, |this, window, cx| {
4069 if clear_linked_edit_ranges {
4070 this.linked_edit_ranges.clear();
4071 }
4072 let initial_buffer_versions =
4073 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4074
4075 this.buffer.update(cx, |buffer, cx| {
4076 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4077 });
4078 for (buffer, edits) in linked_edits {
4079 buffer.update(cx, |buffer, cx| {
4080 let snapshot = buffer.snapshot();
4081 let edits = edits
4082 .into_iter()
4083 .map(|(range, text)| {
4084 use text::ToPoint as TP;
4085 let end_point = TP::to_point(&range.end, &snapshot);
4086 let start_point = TP::to_point(&range.start, &snapshot);
4087 (start_point..end_point, text)
4088 })
4089 .sorted_by_key(|(range, _)| range.start);
4090 buffer.edit(edits, None, cx);
4091 })
4092 }
4093 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4094 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4095 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4096 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4097 .zip(new_selection_deltas)
4098 .map(|(selection, delta)| Selection {
4099 id: selection.id,
4100 start: selection.start + delta,
4101 end: selection.end + delta,
4102 reversed: selection.reversed,
4103 goal: SelectionGoal::None,
4104 })
4105 .collect::<Vec<_>>();
4106
4107 let mut i = 0;
4108 for (position, delta, selection_id, pair) in new_autoclose_regions {
4109 let position = position.to_offset(&map.buffer_snapshot) + delta;
4110 let start = map.buffer_snapshot.anchor_before(position);
4111 let end = map.buffer_snapshot.anchor_after(position);
4112 while let Some(existing_state) = this.autoclose_regions.get(i) {
4113 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4114 Ordering::Less => i += 1,
4115 Ordering::Greater => break,
4116 Ordering::Equal => {
4117 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4118 Ordering::Less => i += 1,
4119 Ordering::Equal => break,
4120 Ordering::Greater => break,
4121 }
4122 }
4123 }
4124 }
4125 this.autoclose_regions.insert(
4126 i,
4127 AutocloseRegion {
4128 selection_id,
4129 range: start..end,
4130 pair,
4131 },
4132 );
4133 }
4134
4135 let had_active_inline_completion = this.has_active_inline_completion();
4136 this.change_selections(
4137 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4138 window,
4139 cx,
4140 |s| s.select(new_selections),
4141 );
4142
4143 if !bracket_inserted {
4144 if let Some(on_type_format_task) =
4145 this.trigger_on_type_formatting(text.to_string(), window, cx)
4146 {
4147 on_type_format_task.detach_and_log_err(cx);
4148 }
4149 }
4150
4151 let editor_settings = EditorSettings::get_global(cx);
4152 if bracket_inserted
4153 && (editor_settings.auto_signature_help
4154 || editor_settings.show_signature_help_after_edits)
4155 {
4156 this.show_signature_help(&ShowSignatureHelp, window, cx);
4157 }
4158
4159 let trigger_in_words =
4160 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4161 if this.hard_wrap.is_some() {
4162 let latest: Range<Point> = this.selections.newest(cx).range();
4163 if latest.is_empty()
4164 && this
4165 .buffer()
4166 .read(cx)
4167 .snapshot(cx)
4168 .line_len(MultiBufferRow(latest.start.row))
4169 == latest.start.column
4170 {
4171 this.rewrap_impl(
4172 RewrapOptions {
4173 override_language_settings: true,
4174 preserve_existing_whitespace: true,
4175 },
4176 cx,
4177 )
4178 }
4179 }
4180 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4181 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4182 this.refresh_inline_completion(true, false, window, cx);
4183 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4184 });
4185 }
4186
4187 fn find_possible_emoji_shortcode_at_position(
4188 snapshot: &MultiBufferSnapshot,
4189 position: Point,
4190 ) -> Option<String> {
4191 let mut chars = Vec::new();
4192 let mut found_colon = false;
4193 for char in snapshot.reversed_chars_at(position).take(100) {
4194 // Found a possible emoji shortcode in the middle of the buffer
4195 if found_colon {
4196 if char.is_whitespace() {
4197 chars.reverse();
4198 return Some(chars.iter().collect());
4199 }
4200 // If the previous character is not a whitespace, we are in the middle of a word
4201 // and we only want to complete the shortcode if the word is made up of other emojis
4202 let mut containing_word = String::new();
4203 for ch in snapshot
4204 .reversed_chars_at(position)
4205 .skip(chars.len() + 1)
4206 .take(100)
4207 {
4208 if ch.is_whitespace() {
4209 break;
4210 }
4211 containing_word.push(ch);
4212 }
4213 let containing_word = containing_word.chars().rev().collect::<String>();
4214 if util::word_consists_of_emojis(containing_word.as_str()) {
4215 chars.reverse();
4216 return Some(chars.iter().collect());
4217 }
4218 }
4219
4220 if char.is_whitespace() || !char.is_ascii() {
4221 return None;
4222 }
4223 if char == ':' {
4224 found_colon = true;
4225 } else {
4226 chars.push(char);
4227 }
4228 }
4229 // Found a possible emoji shortcode at the beginning of the buffer
4230 chars.reverse();
4231 Some(chars.iter().collect())
4232 }
4233
4234 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4236 self.transact(window, cx, |this, window, cx| {
4237 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4238 let selections = this.selections.all::<usize>(cx);
4239 let multi_buffer = this.buffer.read(cx);
4240 let buffer = multi_buffer.snapshot(cx);
4241 selections
4242 .iter()
4243 .map(|selection| {
4244 let start_point = selection.start.to_point(&buffer);
4245 let mut existing_indent =
4246 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4247 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4248 let start = selection.start;
4249 let end = selection.end;
4250 let selection_is_empty = start == end;
4251 let language_scope = buffer.language_scope_at(start);
4252 let (
4253 comment_delimiter,
4254 doc_delimiter,
4255 insert_extra_newline,
4256 indent_on_newline,
4257 indent_on_extra_newline,
4258 ) = if let Some(language) = &language_scope {
4259 let mut insert_extra_newline =
4260 insert_extra_newline_brackets(&buffer, start..end, language)
4261 || insert_extra_newline_tree_sitter(&buffer, start..end);
4262
4263 // Comment extension on newline is allowed only for cursor selections
4264 let comment_delimiter = maybe!({
4265 if !selection_is_empty {
4266 return None;
4267 }
4268
4269 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4270 return None;
4271 }
4272
4273 let delimiters = language.line_comment_prefixes();
4274 let max_len_of_delimiter =
4275 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4276 let (snapshot, range) =
4277 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4278
4279 let num_of_whitespaces = snapshot
4280 .chars_for_range(range.clone())
4281 .take_while(|c| c.is_whitespace())
4282 .count();
4283 let comment_candidate = snapshot
4284 .chars_for_range(range)
4285 .skip(num_of_whitespaces)
4286 .take(max_len_of_delimiter)
4287 .collect::<String>();
4288 let (delimiter, trimmed_len) = delimiters
4289 .iter()
4290 .filter_map(|delimiter| {
4291 let prefix = delimiter.trim_end();
4292 if comment_candidate.starts_with(prefix) {
4293 Some((delimiter, prefix.len()))
4294 } else {
4295 None
4296 }
4297 })
4298 .max_by_key(|(_, len)| *len)?;
4299
4300 let cursor_is_placed_after_comment_marker =
4301 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4302 if cursor_is_placed_after_comment_marker {
4303 Some(delimiter.clone())
4304 } else {
4305 None
4306 }
4307 });
4308
4309 let mut indent_on_newline = IndentSize::spaces(0);
4310 let mut indent_on_extra_newline = IndentSize::spaces(0);
4311
4312 let doc_delimiter = maybe!({
4313 if !selection_is_empty {
4314 return None;
4315 }
4316
4317 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4318 return None;
4319 }
4320
4321 let DocumentationConfig {
4322 start: start_tag,
4323 end: end_tag,
4324 prefix: delimiter,
4325 tab_size: len,
4326 } = language.documentation()?;
4327
4328 let is_within_block_comment = buffer
4329 .language_scope_at(start_point)
4330 .is_some_and(|scope| scope.override_name() == Some("comment"));
4331 if !is_within_block_comment {
4332 return None;
4333 }
4334
4335 let (snapshot, range) =
4336 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4337
4338 let num_of_whitespaces = snapshot
4339 .chars_for_range(range.clone())
4340 .take_while(|c| c.is_whitespace())
4341 .count();
4342
4343 // 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.
4344 let column = start_point.column;
4345 let cursor_is_after_start_tag = {
4346 let start_tag_len = start_tag.len();
4347 let start_tag_line = snapshot
4348 .chars_for_range(range.clone())
4349 .skip(num_of_whitespaces)
4350 .take(start_tag_len)
4351 .collect::<String>();
4352 if start_tag_line.starts_with(start_tag.as_ref()) {
4353 num_of_whitespaces + start_tag_len <= column as usize
4354 } else {
4355 false
4356 }
4357 };
4358
4359 let cursor_is_after_delimiter = {
4360 let delimiter_trim = delimiter.trim_end();
4361 let delimiter_line = snapshot
4362 .chars_for_range(range.clone())
4363 .skip(num_of_whitespaces)
4364 .take(delimiter_trim.len())
4365 .collect::<String>();
4366 if delimiter_line.starts_with(delimiter_trim) {
4367 num_of_whitespaces + delimiter_trim.len() <= column as usize
4368 } else {
4369 false
4370 }
4371 };
4372
4373 let cursor_is_before_end_tag_if_exists = {
4374 let mut char_position = 0u32;
4375 let mut end_tag_offset = None;
4376
4377 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4378 if let Some(byte_pos) = chunk.find(&**end_tag) {
4379 let chars_before_match =
4380 chunk[..byte_pos].chars().count() as u32;
4381 end_tag_offset =
4382 Some(char_position + chars_before_match);
4383 break 'outer;
4384 }
4385 char_position += chunk.chars().count() as u32;
4386 }
4387
4388 if let Some(end_tag_offset) = end_tag_offset {
4389 let cursor_is_before_end_tag = column <= end_tag_offset;
4390 if cursor_is_after_start_tag {
4391 if cursor_is_before_end_tag {
4392 insert_extra_newline = true;
4393 }
4394 let cursor_is_at_start_of_end_tag =
4395 column == end_tag_offset;
4396 if cursor_is_at_start_of_end_tag {
4397 indent_on_extra_newline.len = (*len).into();
4398 }
4399 }
4400 cursor_is_before_end_tag
4401 } else {
4402 true
4403 }
4404 };
4405
4406 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4407 && cursor_is_before_end_tag_if_exists
4408 {
4409 if cursor_is_after_start_tag {
4410 indent_on_newline.len = (*len).into();
4411 }
4412 Some(delimiter.clone())
4413 } else {
4414 None
4415 }
4416 });
4417
4418 (
4419 comment_delimiter,
4420 doc_delimiter,
4421 insert_extra_newline,
4422 indent_on_newline,
4423 indent_on_extra_newline,
4424 )
4425 } else {
4426 (
4427 None,
4428 None,
4429 false,
4430 IndentSize::default(),
4431 IndentSize::default(),
4432 )
4433 };
4434
4435 let prevent_auto_indent = doc_delimiter.is_some();
4436 let delimiter = comment_delimiter.or(doc_delimiter);
4437
4438 let capacity_for_delimiter =
4439 delimiter.as_deref().map(str::len).unwrap_or_default();
4440 let mut new_text = String::with_capacity(
4441 1 + capacity_for_delimiter
4442 + existing_indent.len as usize
4443 + indent_on_newline.len as usize
4444 + indent_on_extra_newline.len as usize,
4445 );
4446 new_text.push('\n');
4447 new_text.extend(existing_indent.chars());
4448 new_text.extend(indent_on_newline.chars());
4449
4450 if let Some(delimiter) = &delimiter {
4451 new_text.push_str(delimiter);
4452 }
4453
4454 if insert_extra_newline {
4455 new_text.push('\n');
4456 new_text.extend(existing_indent.chars());
4457 new_text.extend(indent_on_extra_newline.chars());
4458 }
4459
4460 let anchor = buffer.anchor_after(end);
4461 let new_selection = selection.map(|_| anchor);
4462 (
4463 ((start..end, new_text), prevent_auto_indent),
4464 (insert_extra_newline, new_selection),
4465 )
4466 })
4467 .unzip()
4468 };
4469
4470 let mut auto_indent_edits = Vec::new();
4471 let mut edits = Vec::new();
4472 for (edit, prevent_auto_indent) in edits_with_flags {
4473 if prevent_auto_indent {
4474 edits.push(edit);
4475 } else {
4476 auto_indent_edits.push(edit);
4477 }
4478 }
4479 if !edits.is_empty() {
4480 this.edit(edits, cx);
4481 }
4482 if !auto_indent_edits.is_empty() {
4483 this.edit_with_autoindent(auto_indent_edits, cx);
4484 }
4485
4486 let buffer = this.buffer.read(cx).snapshot(cx);
4487 let new_selections = selection_info
4488 .into_iter()
4489 .map(|(extra_newline_inserted, new_selection)| {
4490 let mut cursor = new_selection.end.to_point(&buffer);
4491 if extra_newline_inserted {
4492 cursor.row -= 1;
4493 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4494 }
4495 new_selection.map(|_| cursor)
4496 })
4497 .collect();
4498
4499 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4500 s.select(new_selections)
4501 });
4502 this.refresh_inline_completion(true, false, window, cx);
4503 });
4504 }
4505
4506 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4508
4509 let buffer = self.buffer.read(cx);
4510 let snapshot = buffer.snapshot(cx);
4511
4512 let mut edits = Vec::new();
4513 let mut rows = Vec::new();
4514
4515 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4516 let cursor = selection.head();
4517 let row = cursor.row;
4518
4519 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4520
4521 let newline = "\n".to_string();
4522 edits.push((start_of_line..start_of_line, newline));
4523
4524 rows.push(row + rows_inserted as u32);
4525 }
4526
4527 self.transact(window, cx, |editor, window, cx| {
4528 editor.edit(edits, cx);
4529
4530 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4531 let mut index = 0;
4532 s.move_cursors_with(|map, _, _| {
4533 let row = rows[index];
4534 index += 1;
4535
4536 let point = Point::new(row, 0);
4537 let boundary = map.next_line_boundary(point).1;
4538 let clipped = map.clip_point(boundary, Bias::Left);
4539
4540 (clipped, SelectionGoal::None)
4541 });
4542 });
4543
4544 let mut indent_edits = Vec::new();
4545 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4546 for row in rows {
4547 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4548 for (row, indent) in indents {
4549 if indent.len == 0 {
4550 continue;
4551 }
4552
4553 let text = match indent.kind {
4554 IndentKind::Space => " ".repeat(indent.len as usize),
4555 IndentKind::Tab => "\t".repeat(indent.len as usize),
4556 };
4557 let point = Point::new(row.0, 0);
4558 indent_edits.push((point..point, text));
4559 }
4560 }
4561 editor.edit(indent_edits, cx);
4562 });
4563 }
4564
4565 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4566 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4567
4568 let buffer = self.buffer.read(cx);
4569 let snapshot = buffer.snapshot(cx);
4570
4571 let mut edits = Vec::new();
4572 let mut rows = Vec::new();
4573 let mut rows_inserted = 0;
4574
4575 for selection in self.selections.all_adjusted(cx) {
4576 let cursor = selection.head();
4577 let row = cursor.row;
4578
4579 let point = Point::new(row + 1, 0);
4580 let start_of_line = snapshot.clip_point(point, Bias::Left);
4581
4582 let newline = "\n".to_string();
4583 edits.push((start_of_line..start_of_line, newline));
4584
4585 rows_inserted += 1;
4586 rows.push(row + rows_inserted);
4587 }
4588
4589 self.transact(window, cx, |editor, window, cx| {
4590 editor.edit(edits, cx);
4591
4592 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4593 let mut index = 0;
4594 s.move_cursors_with(|map, _, _| {
4595 let row = rows[index];
4596 index += 1;
4597
4598 let point = Point::new(row, 0);
4599 let boundary = map.next_line_boundary(point).1;
4600 let clipped = map.clip_point(boundary, Bias::Left);
4601
4602 (clipped, SelectionGoal::None)
4603 });
4604 });
4605
4606 let mut indent_edits = Vec::new();
4607 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4608 for row in rows {
4609 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4610 for (row, indent) in indents {
4611 if indent.len == 0 {
4612 continue;
4613 }
4614
4615 let text = match indent.kind {
4616 IndentKind::Space => " ".repeat(indent.len as usize),
4617 IndentKind::Tab => "\t".repeat(indent.len as usize),
4618 };
4619 let point = Point::new(row.0, 0);
4620 indent_edits.push((point..point, text));
4621 }
4622 }
4623 editor.edit(indent_edits, cx);
4624 });
4625 }
4626
4627 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4628 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4629 original_indent_columns: Vec::new(),
4630 });
4631 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4632 }
4633
4634 fn insert_with_autoindent_mode(
4635 &mut self,
4636 text: &str,
4637 autoindent_mode: Option<AutoindentMode>,
4638 window: &mut Window,
4639 cx: &mut Context<Self>,
4640 ) {
4641 if self.read_only(cx) {
4642 return;
4643 }
4644
4645 let text: Arc<str> = text.into();
4646 self.transact(window, cx, |this, window, cx| {
4647 let old_selections = this.selections.all_adjusted(cx);
4648 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4649 let anchors = {
4650 let snapshot = buffer.read(cx);
4651 old_selections
4652 .iter()
4653 .map(|s| {
4654 let anchor = snapshot.anchor_after(s.head());
4655 s.map(|_| anchor)
4656 })
4657 .collect::<Vec<_>>()
4658 };
4659 buffer.edit(
4660 old_selections
4661 .iter()
4662 .map(|s| (s.start..s.end, text.clone())),
4663 autoindent_mode,
4664 cx,
4665 );
4666 anchors
4667 });
4668
4669 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4670 s.select_anchors(selection_anchors);
4671 });
4672
4673 cx.notify();
4674 });
4675 }
4676
4677 fn trigger_completion_on_input(
4678 &mut self,
4679 text: &str,
4680 trigger_in_words: bool,
4681 window: &mut Window,
4682 cx: &mut Context<Self>,
4683 ) {
4684 let completions_source = self
4685 .context_menu
4686 .borrow()
4687 .as_ref()
4688 .and_then(|menu| match menu {
4689 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4690 CodeContextMenu::CodeActions(_) => None,
4691 });
4692
4693 match completions_source {
4694 Some(CompletionsMenuSource::Words) => {
4695 self.show_word_completions(&ShowWordCompletions, window, cx)
4696 }
4697 Some(CompletionsMenuSource::Normal)
4698 | Some(CompletionsMenuSource::SnippetChoices)
4699 | None
4700 if self.is_completion_trigger(
4701 text,
4702 trigger_in_words,
4703 completions_source.is_some(),
4704 cx,
4705 ) =>
4706 {
4707 self.show_completions(
4708 &ShowCompletions {
4709 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4710 },
4711 window,
4712 cx,
4713 )
4714 }
4715 _ => {
4716 self.hide_context_menu(window, cx);
4717 }
4718 }
4719 }
4720
4721 fn is_completion_trigger(
4722 &self,
4723 text: &str,
4724 trigger_in_words: bool,
4725 menu_is_open: bool,
4726 cx: &mut Context<Self>,
4727 ) -> bool {
4728 let position = self.selections.newest_anchor().head();
4729 let multibuffer = self.buffer.read(cx);
4730 let Some(buffer) = position
4731 .buffer_id
4732 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4733 else {
4734 return false;
4735 };
4736
4737 if let Some(completion_provider) = &self.completion_provider {
4738 completion_provider.is_completion_trigger(
4739 &buffer,
4740 position.text_anchor,
4741 text,
4742 trigger_in_words,
4743 menu_is_open,
4744 cx,
4745 )
4746 } else {
4747 false
4748 }
4749 }
4750
4751 /// If any empty selections is touching the start of its innermost containing autoclose
4752 /// region, expand it to select the brackets.
4753 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4754 let selections = self.selections.all::<usize>(cx);
4755 let buffer = self.buffer.read(cx).read(cx);
4756 let new_selections = self
4757 .selections_with_autoclose_regions(selections, &buffer)
4758 .map(|(mut selection, region)| {
4759 if !selection.is_empty() {
4760 return selection;
4761 }
4762
4763 if let Some(region) = region {
4764 let mut range = region.range.to_offset(&buffer);
4765 if selection.start == range.start && range.start >= region.pair.start.len() {
4766 range.start -= region.pair.start.len();
4767 if buffer.contains_str_at(range.start, ®ion.pair.start)
4768 && buffer.contains_str_at(range.end, ®ion.pair.end)
4769 {
4770 range.end += region.pair.end.len();
4771 selection.start = range.start;
4772 selection.end = range.end;
4773
4774 return selection;
4775 }
4776 }
4777 }
4778
4779 let always_treat_brackets_as_autoclosed = buffer
4780 .language_settings_at(selection.start, cx)
4781 .always_treat_brackets_as_autoclosed;
4782
4783 if !always_treat_brackets_as_autoclosed {
4784 return selection;
4785 }
4786
4787 if let Some(scope) = buffer.language_scope_at(selection.start) {
4788 for (pair, enabled) in scope.brackets() {
4789 if !enabled || !pair.close {
4790 continue;
4791 }
4792
4793 if buffer.contains_str_at(selection.start, &pair.end) {
4794 let pair_start_len = pair.start.len();
4795 if buffer.contains_str_at(
4796 selection.start.saturating_sub(pair_start_len),
4797 &pair.start,
4798 ) {
4799 selection.start -= pair_start_len;
4800 selection.end += pair.end.len();
4801
4802 return selection;
4803 }
4804 }
4805 }
4806 }
4807
4808 selection
4809 })
4810 .collect();
4811
4812 drop(buffer);
4813 self.change_selections(None, window, cx, |selections| {
4814 selections.select(new_selections)
4815 });
4816 }
4817
4818 /// Iterate the given selections, and for each one, find the smallest surrounding
4819 /// autoclose region. This uses the ordering of the selections and the autoclose
4820 /// regions to avoid repeated comparisons.
4821 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4822 &'a self,
4823 selections: impl IntoIterator<Item = Selection<D>>,
4824 buffer: &'a MultiBufferSnapshot,
4825 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4826 let mut i = 0;
4827 let mut regions = self.autoclose_regions.as_slice();
4828 selections.into_iter().map(move |selection| {
4829 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4830
4831 let mut enclosing = None;
4832 while let Some(pair_state) = regions.get(i) {
4833 if pair_state.range.end.to_offset(buffer) < range.start {
4834 regions = ®ions[i + 1..];
4835 i = 0;
4836 } else if pair_state.range.start.to_offset(buffer) > range.end {
4837 break;
4838 } else {
4839 if pair_state.selection_id == selection.id {
4840 enclosing = Some(pair_state);
4841 }
4842 i += 1;
4843 }
4844 }
4845
4846 (selection, enclosing)
4847 })
4848 }
4849
4850 /// Remove any autoclose regions that no longer contain their selection.
4851 fn invalidate_autoclose_regions(
4852 &mut self,
4853 mut selections: &[Selection<Anchor>],
4854 buffer: &MultiBufferSnapshot,
4855 ) {
4856 self.autoclose_regions.retain(|state| {
4857 let mut i = 0;
4858 while let Some(selection) = selections.get(i) {
4859 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4860 selections = &selections[1..];
4861 continue;
4862 }
4863 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4864 break;
4865 }
4866 if selection.id == state.selection_id {
4867 return true;
4868 } else {
4869 i += 1;
4870 }
4871 }
4872 false
4873 });
4874 }
4875
4876 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4877 let offset = position.to_offset(buffer);
4878 let (word_range, kind) = buffer.surrounding_word(offset, true);
4879 if offset > word_range.start && kind == Some(CharKind::Word) {
4880 Some(
4881 buffer
4882 .text_for_range(word_range.start..offset)
4883 .collect::<String>(),
4884 )
4885 } else {
4886 None
4887 }
4888 }
4889
4890 pub fn toggle_inline_values(
4891 &mut self,
4892 _: &ToggleInlineValues,
4893 _: &mut Window,
4894 cx: &mut Context<Self>,
4895 ) {
4896 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4897
4898 self.refresh_inline_values(cx);
4899 }
4900
4901 pub fn toggle_inlay_hints(
4902 &mut self,
4903 _: &ToggleInlayHints,
4904 _: &mut Window,
4905 cx: &mut Context<Self>,
4906 ) {
4907 self.refresh_inlay_hints(
4908 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4909 cx,
4910 );
4911 }
4912
4913 pub fn inlay_hints_enabled(&self) -> bool {
4914 self.inlay_hint_cache.enabled
4915 }
4916
4917 pub fn inline_values_enabled(&self) -> bool {
4918 self.inline_value_cache.enabled
4919 }
4920
4921 #[cfg(any(test, feature = "test-support"))]
4922 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4923 self.display_map
4924 .read(cx)
4925 .current_inlays()
4926 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4927 .cloned()
4928 .collect()
4929 }
4930
4931 #[cfg(any(test, feature = "test-support"))]
4932 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
4933 self.display_map
4934 .read(cx)
4935 .current_inlays()
4936 .cloned()
4937 .collect()
4938 }
4939
4940 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4941 if self.semantics_provider.is_none() || !self.mode.is_full() {
4942 return;
4943 }
4944
4945 let reason_description = reason.description();
4946 let ignore_debounce = matches!(
4947 reason,
4948 InlayHintRefreshReason::SettingsChange(_)
4949 | InlayHintRefreshReason::Toggle(_)
4950 | InlayHintRefreshReason::ExcerptsRemoved(_)
4951 | InlayHintRefreshReason::ModifiersChanged(_)
4952 );
4953 let (invalidate_cache, required_languages) = match reason {
4954 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4955 match self.inlay_hint_cache.modifiers_override(enabled) {
4956 Some(enabled) => {
4957 if enabled {
4958 (InvalidationStrategy::RefreshRequested, None)
4959 } else {
4960 self.splice_inlays(
4961 &self
4962 .visible_inlay_hints(cx)
4963 .iter()
4964 .map(|inlay| inlay.id)
4965 .collect::<Vec<InlayId>>(),
4966 Vec::new(),
4967 cx,
4968 );
4969 return;
4970 }
4971 }
4972 None => return,
4973 }
4974 }
4975 InlayHintRefreshReason::Toggle(enabled) => {
4976 if self.inlay_hint_cache.toggle(enabled) {
4977 if enabled {
4978 (InvalidationStrategy::RefreshRequested, None)
4979 } else {
4980 self.splice_inlays(
4981 &self
4982 .visible_inlay_hints(cx)
4983 .iter()
4984 .map(|inlay| inlay.id)
4985 .collect::<Vec<InlayId>>(),
4986 Vec::new(),
4987 cx,
4988 );
4989 return;
4990 }
4991 } else {
4992 return;
4993 }
4994 }
4995 InlayHintRefreshReason::SettingsChange(new_settings) => {
4996 match self.inlay_hint_cache.update_settings(
4997 &self.buffer,
4998 new_settings,
4999 self.visible_inlay_hints(cx),
5000 cx,
5001 ) {
5002 ControlFlow::Break(Some(InlaySplice {
5003 to_remove,
5004 to_insert,
5005 })) => {
5006 self.splice_inlays(&to_remove, to_insert, cx);
5007 return;
5008 }
5009 ControlFlow::Break(None) => return,
5010 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5011 }
5012 }
5013 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5014 if let Some(InlaySplice {
5015 to_remove,
5016 to_insert,
5017 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5018 {
5019 self.splice_inlays(&to_remove, to_insert, cx);
5020 }
5021 self.display_map.update(cx, |display_map, _| {
5022 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5023 });
5024 return;
5025 }
5026 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5027 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5028 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5029 }
5030 InlayHintRefreshReason::RefreshRequested => {
5031 (InvalidationStrategy::RefreshRequested, None)
5032 }
5033 };
5034
5035 if let Some(InlaySplice {
5036 to_remove,
5037 to_insert,
5038 }) = self.inlay_hint_cache.spawn_hint_refresh(
5039 reason_description,
5040 self.visible_excerpts(required_languages.as_ref(), cx),
5041 invalidate_cache,
5042 ignore_debounce,
5043 cx,
5044 ) {
5045 self.splice_inlays(&to_remove, to_insert, cx);
5046 }
5047 }
5048
5049 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5050 self.display_map
5051 .read(cx)
5052 .current_inlays()
5053 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5054 .cloned()
5055 .collect()
5056 }
5057
5058 pub fn visible_excerpts(
5059 &self,
5060 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5061 cx: &mut Context<Editor>,
5062 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5063 let Some(project) = self.project.as_ref() else {
5064 return HashMap::default();
5065 };
5066 let project = project.read(cx);
5067 let multi_buffer = self.buffer().read(cx);
5068 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5069 let multi_buffer_visible_start = self
5070 .scroll_manager
5071 .anchor()
5072 .anchor
5073 .to_point(&multi_buffer_snapshot);
5074 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5075 multi_buffer_visible_start
5076 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5077 Bias::Left,
5078 );
5079 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5080 multi_buffer_snapshot
5081 .range_to_buffer_ranges(multi_buffer_visible_range)
5082 .into_iter()
5083 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5084 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5085 let buffer_file = project::File::from_dyn(buffer.file())?;
5086 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5087 let worktree_entry = buffer_worktree
5088 .read(cx)
5089 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5090 if worktree_entry.is_ignored {
5091 return None;
5092 }
5093
5094 let language = buffer.language()?;
5095 if let Some(restrict_to_languages) = restrict_to_languages {
5096 if !restrict_to_languages.contains(language) {
5097 return None;
5098 }
5099 }
5100 Some((
5101 excerpt_id,
5102 (
5103 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5104 buffer.version().clone(),
5105 excerpt_visible_range,
5106 ),
5107 ))
5108 })
5109 .collect()
5110 }
5111
5112 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5113 TextLayoutDetails {
5114 text_system: window.text_system().clone(),
5115 editor_style: self.style.clone().unwrap(),
5116 rem_size: window.rem_size(),
5117 scroll_anchor: self.scroll_manager.anchor(),
5118 visible_rows: self.visible_line_count(),
5119 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5120 }
5121 }
5122
5123 pub fn splice_inlays(
5124 &self,
5125 to_remove: &[InlayId],
5126 to_insert: Vec<Inlay>,
5127 cx: &mut Context<Self>,
5128 ) {
5129 self.display_map.update(cx, |display_map, cx| {
5130 display_map.splice_inlays(to_remove, to_insert, cx)
5131 });
5132 cx.notify();
5133 }
5134
5135 fn trigger_on_type_formatting(
5136 &self,
5137 input: String,
5138 window: &mut Window,
5139 cx: &mut Context<Self>,
5140 ) -> Option<Task<Result<()>>> {
5141 if input.len() != 1 {
5142 return None;
5143 }
5144
5145 let project = self.project.as_ref()?;
5146 let position = self.selections.newest_anchor().head();
5147 let (buffer, buffer_position) = self
5148 .buffer
5149 .read(cx)
5150 .text_anchor_for_position(position, cx)?;
5151
5152 let settings = language_settings::language_settings(
5153 buffer
5154 .read(cx)
5155 .language_at(buffer_position)
5156 .map(|l| l.name()),
5157 buffer.read(cx).file(),
5158 cx,
5159 );
5160 if !settings.use_on_type_format {
5161 return None;
5162 }
5163
5164 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5165 // hence we do LSP request & edit on host side only — add formats to host's history.
5166 let push_to_lsp_host_history = true;
5167 // If this is not the host, append its history with new edits.
5168 let push_to_client_history = project.read(cx).is_via_collab();
5169
5170 let on_type_formatting = project.update(cx, |project, cx| {
5171 project.on_type_format(
5172 buffer.clone(),
5173 buffer_position,
5174 input,
5175 push_to_lsp_host_history,
5176 cx,
5177 )
5178 });
5179 Some(cx.spawn_in(window, async move |editor, cx| {
5180 if let Some(transaction) = on_type_formatting.await? {
5181 if push_to_client_history {
5182 buffer
5183 .update(cx, |buffer, _| {
5184 buffer.push_transaction(transaction, Instant::now());
5185 buffer.finalize_last_transaction();
5186 })
5187 .ok();
5188 }
5189 editor.update(cx, |editor, cx| {
5190 editor.refresh_document_highlights(cx);
5191 })?;
5192 }
5193 Ok(())
5194 }))
5195 }
5196
5197 pub fn show_word_completions(
5198 &mut self,
5199 _: &ShowWordCompletions,
5200 window: &mut Window,
5201 cx: &mut Context<Self>,
5202 ) {
5203 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5204 }
5205
5206 pub fn show_completions(
5207 &mut self,
5208 options: &ShowCompletions,
5209 window: &mut Window,
5210 cx: &mut Context<Self>,
5211 ) {
5212 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5213 }
5214
5215 fn open_or_update_completions_menu(
5216 &mut self,
5217 requested_source: Option<CompletionsMenuSource>,
5218 trigger: Option<&str>,
5219 window: &mut Window,
5220 cx: &mut Context<Self>,
5221 ) {
5222 if self.pending_rename.is_some() {
5223 return;
5224 }
5225
5226 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5227
5228 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5229 // inserted and selected. To handle that case, the start of the selection is used so that
5230 // the menu starts with all choices.
5231 let position = self
5232 .selections
5233 .newest_anchor()
5234 .start
5235 .bias_right(&multibuffer_snapshot);
5236 if position.diff_base_anchor.is_some() {
5237 return;
5238 }
5239 let (buffer, buffer_position) =
5240 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5241 output
5242 } else {
5243 return;
5244 };
5245 let buffer_snapshot = buffer.read(cx).snapshot();
5246
5247 let query: Option<Arc<String>> =
5248 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5249
5250 drop(multibuffer_snapshot);
5251
5252 let provider = match requested_source {
5253 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5254 Some(CompletionsMenuSource::Words) => None,
5255 Some(CompletionsMenuSource::SnippetChoices) => {
5256 log::error!("bug: SnippetChoices requested_source is not handled");
5257 None
5258 }
5259 };
5260
5261 let sort_completions = provider
5262 .as_ref()
5263 .map_or(false, |provider| provider.sort_completions());
5264
5265 let filter_completions = provider
5266 .as_ref()
5267 .map_or(true, |provider| provider.filter_completions());
5268
5269 let trigger_kind = match trigger {
5270 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5271 CompletionTriggerKind::TRIGGER_CHARACTER
5272 }
5273 _ => CompletionTriggerKind::INVOKED,
5274 };
5275 let completion_context = CompletionContext {
5276 trigger_character: trigger.and_then(|trigger| {
5277 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5278 Some(String::from(trigger))
5279 } else {
5280 None
5281 }
5282 }),
5283 trigger_kind,
5284 };
5285
5286 // Hide the current completions menu when a trigger char is typed. Without this, cached
5287 // completions from before the trigger char may be reused (#32774). Snippet choices could
5288 // involve trigger chars, so this is skipped in that case.
5289 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5290 {
5291 let menu_is_open = matches!(
5292 self.context_menu.borrow().as_ref(),
5293 Some(CodeContextMenu::Completions(_))
5294 );
5295 if menu_is_open {
5296 self.hide_context_menu(window, cx);
5297 }
5298 }
5299
5300 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5301 if filter_completions {
5302 menu.filter(query.clone(), provider.clone(), window, cx);
5303 }
5304 // When `is_incomplete` is false, no need to re-query completions when the current query
5305 // is a suffix of the initial query.
5306 if !menu.is_incomplete {
5307 // If the new query is a suffix of the old query (typing more characters) and
5308 // the previous result was complete, the existing completions can be filtered.
5309 //
5310 // Note that this is always true for snippet completions.
5311 let query_matches = match (&menu.initial_query, &query) {
5312 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5313 (None, _) => true,
5314 _ => false,
5315 };
5316 if query_matches {
5317 let position_matches = if menu.initial_position == position {
5318 true
5319 } else {
5320 let snapshot = self.buffer.read(cx).read(cx);
5321 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5322 };
5323 if position_matches {
5324 return;
5325 }
5326 }
5327 }
5328 };
5329
5330 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5331 buffer_snapshot.surrounding_word(buffer_position)
5332 {
5333 let word_to_exclude = buffer_snapshot
5334 .text_for_range(word_range.clone())
5335 .collect::<String>();
5336 (
5337 buffer_snapshot.anchor_before(word_range.start)
5338 ..buffer_snapshot.anchor_after(buffer_position),
5339 Some(word_to_exclude),
5340 )
5341 } else {
5342 (buffer_position..buffer_position, None)
5343 };
5344
5345 let language = buffer_snapshot
5346 .language_at(buffer_position)
5347 .map(|language| language.name());
5348
5349 let completion_settings =
5350 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5351
5352 let show_completion_documentation = buffer_snapshot
5353 .settings_at(buffer_position, cx)
5354 .show_completion_documentation;
5355
5356 // The document can be large, so stay in reasonable bounds when searching for words,
5357 // otherwise completion pop-up might be slow to appear.
5358 const WORD_LOOKUP_ROWS: u32 = 5_000;
5359 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5360 let min_word_search = buffer_snapshot.clip_point(
5361 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5362 Bias::Left,
5363 );
5364 let max_word_search = buffer_snapshot.clip_point(
5365 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5366 Bias::Right,
5367 );
5368 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5369 ..buffer_snapshot.point_to_offset(max_word_search);
5370
5371 let skip_digits = query
5372 .as_ref()
5373 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5374
5375 let (mut words, provider_responses) = match &provider {
5376 Some(provider) => {
5377 let provider_responses = provider.completions(
5378 position.excerpt_id,
5379 &buffer,
5380 buffer_position,
5381 completion_context,
5382 window,
5383 cx,
5384 );
5385
5386 let words = match completion_settings.words {
5387 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5388 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5389 .background_spawn(async move {
5390 buffer_snapshot.words_in_range(WordsQuery {
5391 fuzzy_contents: None,
5392 range: word_search_range,
5393 skip_digits,
5394 })
5395 }),
5396 };
5397
5398 (words, provider_responses)
5399 }
5400 None => (
5401 cx.background_spawn(async move {
5402 buffer_snapshot.words_in_range(WordsQuery {
5403 fuzzy_contents: None,
5404 range: word_search_range,
5405 skip_digits,
5406 })
5407 }),
5408 Task::ready(Ok(Vec::new())),
5409 ),
5410 };
5411
5412 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5413
5414 let id = post_inc(&mut self.next_completion_id);
5415 let task = cx.spawn_in(window, async move |editor, cx| {
5416 let Ok(()) = editor.update(cx, |this, _| {
5417 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5418 }) else {
5419 return;
5420 };
5421
5422 // TODO: Ideally completions from different sources would be selectively re-queried, so
5423 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5424 let mut completions = Vec::new();
5425 let mut is_incomplete = false;
5426 if let Some(provider_responses) = provider_responses.await.log_err() {
5427 if !provider_responses.is_empty() {
5428 for response in provider_responses {
5429 completions.extend(response.completions);
5430 is_incomplete = is_incomplete || response.is_incomplete;
5431 }
5432 if completion_settings.words == WordsCompletionMode::Fallback {
5433 words = Task::ready(BTreeMap::default());
5434 }
5435 }
5436 }
5437
5438 let mut words = words.await;
5439 if let Some(word_to_exclude) = &word_to_exclude {
5440 words.remove(word_to_exclude);
5441 }
5442 for lsp_completion in &completions {
5443 words.remove(&lsp_completion.new_text);
5444 }
5445 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5446 replace_range: word_replace_range.clone(),
5447 new_text: word.clone(),
5448 label: CodeLabel::plain(word, None),
5449 icon_path: None,
5450 documentation: None,
5451 source: CompletionSource::BufferWord {
5452 word_range,
5453 resolved: false,
5454 },
5455 insert_text_mode: Some(InsertTextMode::AS_IS),
5456 confirm: None,
5457 }));
5458
5459 let menu = if completions.is_empty() {
5460 None
5461 } else {
5462 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5463 let languages = editor
5464 .workspace
5465 .as_ref()
5466 .and_then(|(workspace, _)| workspace.upgrade())
5467 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5468 let menu = CompletionsMenu::new(
5469 id,
5470 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5471 sort_completions,
5472 show_completion_documentation,
5473 position,
5474 query.clone(),
5475 is_incomplete,
5476 buffer.clone(),
5477 completions.into(),
5478 snippet_sort_order,
5479 languages,
5480 language,
5481 cx,
5482 );
5483
5484 let query = if filter_completions { query } else { None };
5485 let matches_task = if let Some(query) = query {
5486 menu.do_async_filtering(query, cx)
5487 } else {
5488 Task::ready(menu.unfiltered_matches())
5489 };
5490 (menu, matches_task)
5491 }) else {
5492 return;
5493 };
5494
5495 let matches = matches_task.await;
5496
5497 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5498 // Newer menu already set, so exit.
5499 match editor.context_menu.borrow().as_ref() {
5500 Some(CodeContextMenu::Completions(prev_menu)) => {
5501 if prev_menu.id > id {
5502 return;
5503 }
5504 }
5505 _ => {}
5506 };
5507
5508 // Only valid to take prev_menu because it the new menu is immediately set
5509 // below, or the menu is hidden.
5510 match editor.context_menu.borrow_mut().take() {
5511 Some(CodeContextMenu::Completions(prev_menu)) => {
5512 let position_matches =
5513 if prev_menu.initial_position == menu.initial_position {
5514 true
5515 } else {
5516 let snapshot = editor.buffer.read(cx).read(cx);
5517 prev_menu.initial_position.to_offset(&snapshot)
5518 == menu.initial_position.to_offset(&snapshot)
5519 };
5520 if position_matches {
5521 // Preserve markdown cache before `set_filter_results` because it will
5522 // try to populate the documentation cache.
5523 menu.preserve_markdown_cache(prev_menu);
5524 }
5525 }
5526 _ => {}
5527 };
5528
5529 menu.set_filter_results(matches, provider, window, cx);
5530 }) else {
5531 return;
5532 };
5533
5534 menu.visible().then_some(menu)
5535 };
5536
5537 editor
5538 .update_in(cx, |editor, window, cx| {
5539 if editor.focus_handle.is_focused(window) {
5540 if let Some(menu) = menu {
5541 *editor.context_menu.borrow_mut() =
5542 Some(CodeContextMenu::Completions(menu));
5543
5544 crate::hover_popover::hide_hover(editor, cx);
5545 if editor.show_edit_predictions_in_menu() {
5546 editor.update_visible_inline_completion(window, cx);
5547 } else {
5548 editor.discard_inline_completion(false, cx);
5549 }
5550
5551 cx.notify();
5552 return;
5553 }
5554 }
5555
5556 if editor.completion_tasks.len() <= 1 {
5557 // If there are no more completion tasks and the last menu was empty, we should hide it.
5558 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5559 // If it was already hidden and we don't show inline completions in the menu, we should
5560 // also show the inline-completion when available.
5561 if was_hidden && editor.show_edit_predictions_in_menu() {
5562 editor.update_visible_inline_completion(window, cx);
5563 }
5564 }
5565 })
5566 .ok();
5567 });
5568
5569 self.completion_tasks.push((id, task));
5570 }
5571
5572 #[cfg(feature = "test-support")]
5573 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5574 let menu = self.context_menu.borrow();
5575 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5576 let completions = menu.completions.borrow();
5577 Some(completions.to_vec())
5578 } else {
5579 None
5580 }
5581 }
5582
5583 pub fn with_completions_menu_matching_id<R>(
5584 &self,
5585 id: CompletionId,
5586 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5587 ) -> R {
5588 let mut context_menu = self.context_menu.borrow_mut();
5589 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5590 return f(None);
5591 };
5592 if completions_menu.id != id {
5593 return f(None);
5594 }
5595 f(Some(completions_menu))
5596 }
5597
5598 pub fn confirm_completion(
5599 &mut self,
5600 action: &ConfirmCompletion,
5601 window: &mut Window,
5602 cx: &mut Context<Self>,
5603 ) -> Option<Task<Result<()>>> {
5604 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5605 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5606 }
5607
5608 pub fn confirm_completion_insert(
5609 &mut self,
5610 _: &ConfirmCompletionInsert,
5611 window: &mut Window,
5612 cx: &mut Context<Self>,
5613 ) -> Option<Task<Result<()>>> {
5614 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5615 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5616 }
5617
5618 pub fn confirm_completion_replace(
5619 &mut self,
5620 _: &ConfirmCompletionReplace,
5621 window: &mut Window,
5622 cx: &mut Context<Self>,
5623 ) -> Option<Task<Result<()>>> {
5624 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5625 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5626 }
5627
5628 pub fn compose_completion(
5629 &mut self,
5630 action: &ComposeCompletion,
5631 window: &mut Window,
5632 cx: &mut Context<Self>,
5633 ) -> Option<Task<Result<()>>> {
5634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5635 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5636 }
5637
5638 fn do_completion(
5639 &mut self,
5640 item_ix: Option<usize>,
5641 intent: CompletionIntent,
5642 window: &mut Window,
5643 cx: &mut Context<Editor>,
5644 ) -> Option<Task<Result<()>>> {
5645 use language::ToOffset as _;
5646
5647 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5648 else {
5649 return None;
5650 };
5651
5652 let candidate_id = {
5653 let entries = completions_menu.entries.borrow();
5654 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5655 if self.show_edit_predictions_in_menu() {
5656 self.discard_inline_completion(true, cx);
5657 }
5658 mat.candidate_id
5659 };
5660
5661 let completion = completions_menu
5662 .completions
5663 .borrow()
5664 .get(candidate_id)?
5665 .clone();
5666 cx.stop_propagation();
5667
5668 let buffer_handle = completions_menu.buffer.clone();
5669
5670 let CompletionEdit {
5671 new_text,
5672 snippet,
5673 replace_range,
5674 } = process_completion_for_edit(
5675 &completion,
5676 intent,
5677 &buffer_handle,
5678 &completions_menu.initial_position.text_anchor,
5679 cx,
5680 );
5681
5682 let buffer = buffer_handle.read(cx);
5683 let snapshot = self.buffer.read(cx).snapshot(cx);
5684 let newest_anchor = self.selections.newest_anchor();
5685 let replace_range_multibuffer = {
5686 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5687 let multibuffer_anchor = snapshot
5688 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5689 .unwrap()
5690 ..snapshot
5691 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5692 .unwrap();
5693 multibuffer_anchor.start.to_offset(&snapshot)
5694 ..multibuffer_anchor.end.to_offset(&snapshot)
5695 };
5696 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5697 return None;
5698 }
5699
5700 let old_text = buffer
5701 .text_for_range(replace_range.clone())
5702 .collect::<String>();
5703 let lookbehind = newest_anchor
5704 .start
5705 .text_anchor
5706 .to_offset(buffer)
5707 .saturating_sub(replace_range.start);
5708 let lookahead = replace_range
5709 .end
5710 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5711 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5712 let suffix = &old_text[lookbehind.min(old_text.len())..];
5713
5714 let selections = self.selections.all::<usize>(cx);
5715 let mut ranges = Vec::new();
5716 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5717
5718 for selection in &selections {
5719 let range = if selection.id == newest_anchor.id {
5720 replace_range_multibuffer.clone()
5721 } else {
5722 let mut range = selection.range();
5723
5724 // if prefix is present, don't duplicate it
5725 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5726 range.start = range.start.saturating_sub(lookbehind);
5727
5728 // if suffix is also present, mimic the newest cursor and replace it
5729 if selection.id != newest_anchor.id
5730 && snapshot.contains_str_at(range.end, suffix)
5731 {
5732 range.end += lookahead;
5733 }
5734 }
5735 range
5736 };
5737
5738 ranges.push(range.clone());
5739
5740 if !self.linked_edit_ranges.is_empty() {
5741 let start_anchor = snapshot.anchor_before(range.start);
5742 let end_anchor = snapshot.anchor_after(range.end);
5743 if let Some(ranges) = self
5744 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5745 {
5746 for (buffer, edits) in ranges {
5747 linked_edits
5748 .entry(buffer.clone())
5749 .or_default()
5750 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5751 }
5752 }
5753 }
5754 }
5755
5756 let common_prefix_len = old_text
5757 .chars()
5758 .zip(new_text.chars())
5759 .take_while(|(a, b)| a == b)
5760 .map(|(a, _)| a.len_utf8())
5761 .sum::<usize>();
5762
5763 cx.emit(EditorEvent::InputHandled {
5764 utf16_range_to_replace: None,
5765 text: new_text[common_prefix_len..].into(),
5766 });
5767
5768 self.transact(window, cx, |this, window, cx| {
5769 if let Some(mut snippet) = snippet {
5770 snippet.text = new_text.to_string();
5771 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5772 } else {
5773 this.buffer.update(cx, |buffer, cx| {
5774 let auto_indent = match completion.insert_text_mode {
5775 Some(InsertTextMode::AS_IS) => None,
5776 _ => this.autoindent_mode.clone(),
5777 };
5778 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5779 buffer.edit(edits, auto_indent, cx);
5780 });
5781 }
5782 for (buffer, edits) in linked_edits {
5783 buffer.update(cx, |buffer, cx| {
5784 let snapshot = buffer.snapshot();
5785 let edits = edits
5786 .into_iter()
5787 .map(|(range, text)| {
5788 use text::ToPoint as TP;
5789 let end_point = TP::to_point(&range.end, &snapshot);
5790 let start_point = TP::to_point(&range.start, &snapshot);
5791 (start_point..end_point, text)
5792 })
5793 .sorted_by_key(|(range, _)| range.start);
5794 buffer.edit(edits, None, cx);
5795 })
5796 }
5797
5798 this.refresh_inline_completion(true, false, window, cx);
5799 });
5800
5801 let show_new_completions_on_confirm = completion
5802 .confirm
5803 .as_ref()
5804 .map_or(false, |confirm| confirm(intent, window, cx));
5805 if show_new_completions_on_confirm {
5806 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5807 }
5808
5809 let provider = self.completion_provider.as_ref()?;
5810 drop(completion);
5811 let apply_edits = provider.apply_additional_edits_for_completion(
5812 buffer_handle,
5813 completions_menu.completions.clone(),
5814 candidate_id,
5815 true,
5816 cx,
5817 );
5818
5819 let editor_settings = EditorSettings::get_global(cx);
5820 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5821 // After the code completion is finished, users often want to know what signatures are needed.
5822 // so we should automatically call signature_help
5823 self.show_signature_help(&ShowSignatureHelp, window, cx);
5824 }
5825
5826 Some(cx.foreground_executor().spawn(async move {
5827 apply_edits.await?;
5828 Ok(())
5829 }))
5830 }
5831
5832 pub fn toggle_code_actions(
5833 &mut self,
5834 action: &ToggleCodeActions,
5835 window: &mut Window,
5836 cx: &mut Context<Self>,
5837 ) {
5838 let quick_launch = action.quick_launch;
5839 let mut context_menu = self.context_menu.borrow_mut();
5840 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5841 if code_actions.deployed_from == action.deployed_from {
5842 // Toggle if we're selecting the same one
5843 *context_menu = None;
5844 cx.notify();
5845 return;
5846 } else {
5847 // Otherwise, clear it and start a new one
5848 *context_menu = None;
5849 cx.notify();
5850 }
5851 }
5852 drop(context_menu);
5853 let snapshot = self.snapshot(window, cx);
5854 let deployed_from = action.deployed_from.clone();
5855 let action = action.clone();
5856 self.completion_tasks.clear();
5857 self.discard_inline_completion(false, cx);
5858
5859 let multibuffer_point = match &action.deployed_from {
5860 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5861 DisplayPoint::new(*row, 0).to_point(&snapshot)
5862 }
5863 _ => self.selections.newest::<Point>(cx).head(),
5864 };
5865 let Some((buffer, buffer_row)) = snapshot
5866 .buffer_snapshot
5867 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5868 .and_then(|(buffer_snapshot, range)| {
5869 self.buffer()
5870 .read(cx)
5871 .buffer(buffer_snapshot.remote_id())
5872 .map(|buffer| (buffer, range.start.row))
5873 })
5874 else {
5875 return;
5876 };
5877 let buffer_id = buffer.read(cx).remote_id();
5878 let tasks = self
5879 .tasks
5880 .get(&(buffer_id, buffer_row))
5881 .map(|t| Arc::new(t.to_owned()));
5882
5883 if !self.focus_handle.is_focused(window) {
5884 return;
5885 }
5886 let project = self.project.clone();
5887
5888 let code_actions_task = match deployed_from {
5889 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5890 _ => self.code_actions(buffer_row, window, cx),
5891 };
5892
5893 let runnable_task = match deployed_from {
5894 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5895 _ => {
5896 let mut task_context_task = Task::ready(None);
5897 if let Some(tasks) = &tasks {
5898 if let Some(project) = project {
5899 task_context_task =
5900 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5901 }
5902 }
5903
5904 cx.spawn_in(window, {
5905 let buffer = buffer.clone();
5906 async move |editor, cx| {
5907 let task_context = task_context_task.await;
5908
5909 let resolved_tasks =
5910 tasks
5911 .zip(task_context.clone())
5912 .map(|(tasks, task_context)| ResolvedTasks {
5913 templates: tasks.resolve(&task_context).collect(),
5914 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5915 multibuffer_point.row,
5916 tasks.column,
5917 )),
5918 });
5919 let debug_scenarios = editor
5920 .update(cx, |editor, cx| {
5921 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5922 })?
5923 .await;
5924 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5925 }
5926 })
5927 }
5928 };
5929
5930 cx.spawn_in(window, async move |editor, cx| {
5931 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5932 let code_actions = code_actions_task.await;
5933 let spawn_straight_away = quick_launch
5934 && resolved_tasks
5935 .as_ref()
5936 .map_or(false, |tasks| tasks.templates.len() == 1)
5937 && code_actions
5938 .as_ref()
5939 .map_or(true, |actions| actions.is_empty())
5940 && debug_scenarios.is_empty();
5941
5942 editor.update_in(cx, |editor, window, cx| {
5943 crate::hover_popover::hide_hover(editor, cx);
5944 let actions = CodeActionContents::new(
5945 resolved_tasks,
5946 code_actions,
5947 debug_scenarios,
5948 task_context.unwrap_or_default(),
5949 );
5950
5951 // Don't show the menu if there are no actions available
5952 if actions.is_empty() {
5953 cx.notify();
5954 return Task::ready(Ok(()));
5955 }
5956
5957 *editor.context_menu.borrow_mut() =
5958 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5959 buffer,
5960 actions,
5961 selected_item: Default::default(),
5962 scroll_handle: UniformListScrollHandle::default(),
5963 deployed_from,
5964 }));
5965 cx.notify();
5966 if spawn_straight_away {
5967 if let Some(task) = editor.confirm_code_action(
5968 &ConfirmCodeAction { item_ix: Some(0) },
5969 window,
5970 cx,
5971 ) {
5972 return task;
5973 }
5974 }
5975
5976 Task::ready(Ok(()))
5977 })
5978 })
5979 .detach_and_log_err(cx);
5980 }
5981
5982 fn debug_scenarios(
5983 &mut self,
5984 resolved_tasks: &Option<ResolvedTasks>,
5985 buffer: &Entity<Buffer>,
5986 cx: &mut App,
5987 ) -> Task<Vec<task::DebugScenario>> {
5988 maybe!({
5989 let project = self.project.as_ref()?;
5990 let dap_store = project.read(cx).dap_store();
5991 let mut scenarios = vec![];
5992 let resolved_tasks = resolved_tasks.as_ref()?;
5993 let buffer = buffer.read(cx);
5994 let language = buffer.language()?;
5995 let file = buffer.file();
5996 let debug_adapter = language_settings(language.name().into(), file, cx)
5997 .debuggers
5998 .first()
5999 .map(SharedString::from)
6000 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6001
6002 dap_store.update(cx, |dap_store, cx| {
6003 for (_, task) in &resolved_tasks.templates {
6004 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6005 task.original_task().clone(),
6006 debug_adapter.clone().into(),
6007 task.display_label().to_owned().into(),
6008 cx,
6009 );
6010 scenarios.push(maybe_scenario);
6011 }
6012 });
6013 Some(cx.background_spawn(async move {
6014 let scenarios = futures::future::join_all(scenarios)
6015 .await
6016 .into_iter()
6017 .flatten()
6018 .collect::<Vec<_>>();
6019 scenarios
6020 }))
6021 })
6022 .unwrap_or_else(|| Task::ready(vec![]))
6023 }
6024
6025 fn code_actions(
6026 &mut self,
6027 buffer_row: u32,
6028 window: &mut Window,
6029 cx: &mut Context<Self>,
6030 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6031 let mut task = self.code_actions_task.take();
6032 cx.spawn_in(window, async move |editor, cx| {
6033 while let Some(prev_task) = task {
6034 prev_task.await.log_err();
6035 task = editor
6036 .update(cx, |this, _| this.code_actions_task.take())
6037 .ok()?;
6038 }
6039
6040 editor
6041 .update(cx, |editor, cx| {
6042 editor
6043 .available_code_actions
6044 .clone()
6045 .and_then(|(location, code_actions)| {
6046 let snapshot = location.buffer.read(cx).snapshot();
6047 let point_range = location.range.to_point(&snapshot);
6048 let point_range = point_range.start.row..=point_range.end.row;
6049 if point_range.contains(&buffer_row) {
6050 Some(code_actions)
6051 } else {
6052 None
6053 }
6054 })
6055 })
6056 .ok()
6057 .flatten()
6058 })
6059 }
6060
6061 pub fn confirm_code_action(
6062 &mut self,
6063 action: &ConfirmCodeAction,
6064 window: &mut Window,
6065 cx: &mut Context<Self>,
6066 ) -> Option<Task<Result<()>>> {
6067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6068
6069 let actions_menu =
6070 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6071 menu
6072 } else {
6073 return None;
6074 };
6075
6076 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6077 let action = actions_menu.actions.get(action_ix)?;
6078 let title = action.label();
6079 let buffer = actions_menu.buffer;
6080 let workspace = self.workspace()?;
6081
6082 match action {
6083 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6084 workspace.update(cx, |workspace, cx| {
6085 workspace.schedule_resolved_task(
6086 task_source_kind,
6087 resolved_task,
6088 false,
6089 window,
6090 cx,
6091 );
6092
6093 Some(Task::ready(Ok(())))
6094 })
6095 }
6096 CodeActionsItem::CodeAction {
6097 excerpt_id,
6098 action,
6099 provider,
6100 } => {
6101 let apply_code_action =
6102 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6103 let workspace = workspace.downgrade();
6104 Some(cx.spawn_in(window, async move |editor, cx| {
6105 let project_transaction = apply_code_action.await?;
6106 Self::open_project_transaction(
6107 &editor,
6108 workspace,
6109 project_transaction,
6110 title,
6111 cx,
6112 )
6113 .await
6114 }))
6115 }
6116 CodeActionsItem::DebugScenario(scenario) => {
6117 let context = actions_menu.actions.context.clone();
6118
6119 workspace.update(cx, |workspace, cx| {
6120 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6121 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6122 });
6123 Some(Task::ready(Ok(())))
6124 }
6125 }
6126 }
6127
6128 pub async fn open_project_transaction(
6129 this: &WeakEntity<Editor>,
6130 workspace: WeakEntity<Workspace>,
6131 transaction: ProjectTransaction,
6132 title: String,
6133 cx: &mut AsyncWindowContext,
6134 ) -> Result<()> {
6135 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6136 cx.update(|_, cx| {
6137 entries.sort_unstable_by_key(|(buffer, _)| {
6138 buffer.read(cx).file().map(|f| f.path().clone())
6139 });
6140 })?;
6141
6142 // If the project transaction's edits are all contained within this editor, then
6143 // avoid opening a new editor to display them.
6144
6145 if let Some((buffer, transaction)) = entries.first() {
6146 if entries.len() == 1 {
6147 let excerpt = this.update(cx, |editor, cx| {
6148 editor
6149 .buffer()
6150 .read(cx)
6151 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6152 })?;
6153 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6154 if excerpted_buffer == *buffer {
6155 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6156 let excerpt_range = excerpt_range.to_offset(buffer);
6157 buffer
6158 .edited_ranges_for_transaction::<usize>(transaction)
6159 .all(|range| {
6160 excerpt_range.start <= range.start
6161 && excerpt_range.end >= range.end
6162 })
6163 })?;
6164
6165 if all_edits_within_excerpt {
6166 return Ok(());
6167 }
6168 }
6169 }
6170 }
6171 } else {
6172 return Ok(());
6173 }
6174
6175 let mut ranges_to_highlight = Vec::new();
6176 let excerpt_buffer = cx.new(|cx| {
6177 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6178 for (buffer_handle, transaction) in &entries {
6179 let edited_ranges = buffer_handle
6180 .read(cx)
6181 .edited_ranges_for_transaction::<Point>(transaction)
6182 .collect::<Vec<_>>();
6183 let (ranges, _) = multibuffer.set_excerpts_for_path(
6184 PathKey::for_buffer(buffer_handle, cx),
6185 buffer_handle.clone(),
6186 edited_ranges,
6187 DEFAULT_MULTIBUFFER_CONTEXT,
6188 cx,
6189 );
6190
6191 ranges_to_highlight.extend(ranges);
6192 }
6193 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6194 multibuffer
6195 })?;
6196
6197 workspace.update_in(cx, |workspace, window, cx| {
6198 let project = workspace.project().clone();
6199 let editor =
6200 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6201 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6202 editor.update(cx, |editor, cx| {
6203 editor.highlight_background::<Self>(
6204 &ranges_to_highlight,
6205 |theme| theme.colors().editor_highlighted_line_background,
6206 cx,
6207 );
6208 });
6209 })?;
6210
6211 Ok(())
6212 }
6213
6214 pub fn clear_code_action_providers(&mut self) {
6215 self.code_action_providers.clear();
6216 self.available_code_actions.take();
6217 }
6218
6219 pub fn add_code_action_provider(
6220 &mut self,
6221 provider: Rc<dyn CodeActionProvider>,
6222 window: &mut Window,
6223 cx: &mut Context<Self>,
6224 ) {
6225 if self
6226 .code_action_providers
6227 .iter()
6228 .any(|existing_provider| existing_provider.id() == provider.id())
6229 {
6230 return;
6231 }
6232
6233 self.code_action_providers.push(provider);
6234 self.refresh_code_actions(window, cx);
6235 }
6236
6237 pub fn remove_code_action_provider(
6238 &mut self,
6239 id: Arc<str>,
6240 window: &mut Window,
6241 cx: &mut Context<Self>,
6242 ) {
6243 self.code_action_providers
6244 .retain(|provider| provider.id() != id);
6245 self.refresh_code_actions(window, cx);
6246 }
6247
6248 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6249 !self.code_action_providers.is_empty()
6250 && EditorSettings::get_global(cx).toolbar.code_actions
6251 }
6252
6253 pub fn has_available_code_actions(&self) -> bool {
6254 self.available_code_actions
6255 .as_ref()
6256 .is_some_and(|(_, actions)| !actions.is_empty())
6257 }
6258
6259 fn render_inline_code_actions(
6260 &self,
6261 icon_size: ui::IconSize,
6262 display_row: DisplayRow,
6263 is_active: bool,
6264 cx: &mut Context<Self>,
6265 ) -> AnyElement {
6266 let show_tooltip = !self.context_menu_visible();
6267 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6268 .icon_size(icon_size)
6269 .shape(ui::IconButtonShape::Square)
6270 .style(ButtonStyle::Transparent)
6271 .icon_color(ui::Color::Hidden)
6272 .toggle_state(is_active)
6273 .when(show_tooltip, |this| {
6274 this.tooltip({
6275 let focus_handle = self.focus_handle.clone();
6276 move |window, cx| {
6277 Tooltip::for_action_in(
6278 "Toggle Code Actions",
6279 &ToggleCodeActions {
6280 deployed_from: None,
6281 quick_launch: false,
6282 },
6283 &focus_handle,
6284 window,
6285 cx,
6286 )
6287 }
6288 })
6289 })
6290 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6291 window.focus(&editor.focus_handle(cx));
6292 editor.toggle_code_actions(
6293 &crate::actions::ToggleCodeActions {
6294 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6295 display_row,
6296 )),
6297 quick_launch: false,
6298 },
6299 window,
6300 cx,
6301 );
6302 }))
6303 .into_any_element()
6304 }
6305
6306 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6307 &self.context_menu
6308 }
6309
6310 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6311 let newest_selection = self.selections.newest_anchor().clone();
6312 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6313 let buffer = self.buffer.read(cx);
6314 if newest_selection.head().diff_base_anchor.is_some() {
6315 return None;
6316 }
6317 let (start_buffer, start) =
6318 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6319 let (end_buffer, end) =
6320 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6321 if start_buffer != end_buffer {
6322 return None;
6323 }
6324
6325 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6326 cx.background_executor()
6327 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6328 .await;
6329
6330 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6331 let providers = this.code_action_providers.clone();
6332 let tasks = this
6333 .code_action_providers
6334 .iter()
6335 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6336 .collect::<Vec<_>>();
6337 (providers, tasks)
6338 })?;
6339
6340 let mut actions = Vec::new();
6341 for (provider, provider_actions) in
6342 providers.into_iter().zip(future::join_all(tasks).await)
6343 {
6344 if let Some(provider_actions) = provider_actions.log_err() {
6345 actions.extend(provider_actions.into_iter().map(|action| {
6346 AvailableCodeAction {
6347 excerpt_id: newest_selection.start.excerpt_id,
6348 action,
6349 provider: provider.clone(),
6350 }
6351 }));
6352 }
6353 }
6354
6355 this.update(cx, |this, cx| {
6356 this.available_code_actions = if actions.is_empty() {
6357 None
6358 } else {
6359 Some((
6360 Location {
6361 buffer: start_buffer,
6362 range: start..end,
6363 },
6364 actions.into(),
6365 ))
6366 };
6367 cx.notify();
6368 })
6369 }));
6370 None
6371 }
6372
6373 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6374 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6375 self.show_git_blame_inline = false;
6376
6377 self.show_git_blame_inline_delay_task =
6378 Some(cx.spawn_in(window, async move |this, cx| {
6379 cx.background_executor().timer(delay).await;
6380
6381 this.update(cx, |this, cx| {
6382 this.show_git_blame_inline = true;
6383 cx.notify();
6384 })
6385 .log_err();
6386 }));
6387 }
6388 }
6389
6390 fn show_blame_popover(
6391 &mut self,
6392 blame_entry: &BlameEntry,
6393 position: gpui::Point<Pixels>,
6394 cx: &mut Context<Self>,
6395 ) {
6396 if let Some(state) = &mut self.inline_blame_popover {
6397 state.hide_task.take();
6398 } else {
6399 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6400 let blame_entry = blame_entry.clone();
6401 let show_task = cx.spawn(async move |editor, cx| {
6402 cx.background_executor()
6403 .timer(std::time::Duration::from_millis(delay))
6404 .await;
6405 editor
6406 .update(cx, |editor, cx| {
6407 editor.inline_blame_popover_show_task.take();
6408 let Some(blame) = editor.blame.as_ref() else {
6409 return;
6410 };
6411 let blame = blame.read(cx);
6412 let details = blame.details_for_entry(&blame_entry);
6413 let markdown = cx.new(|cx| {
6414 Markdown::new(
6415 details
6416 .as_ref()
6417 .map(|message| message.message.clone())
6418 .unwrap_or_default(),
6419 None,
6420 None,
6421 cx,
6422 )
6423 });
6424 editor.inline_blame_popover = Some(InlineBlamePopover {
6425 position,
6426 hide_task: None,
6427 popover_bounds: None,
6428 popover_state: InlineBlamePopoverState {
6429 scroll_handle: ScrollHandle::new(),
6430 commit_message: details,
6431 markdown,
6432 },
6433 });
6434 cx.notify();
6435 })
6436 .ok();
6437 });
6438 self.inline_blame_popover_show_task = Some(show_task);
6439 }
6440 }
6441
6442 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6443 self.inline_blame_popover_show_task.take();
6444 if let Some(state) = &mut self.inline_blame_popover {
6445 let hide_task = cx.spawn(async move |editor, cx| {
6446 cx.background_executor()
6447 .timer(std::time::Duration::from_millis(100))
6448 .await;
6449 editor
6450 .update(cx, |editor, cx| {
6451 editor.inline_blame_popover.take();
6452 cx.notify();
6453 })
6454 .ok();
6455 });
6456 state.hide_task = Some(hide_task);
6457 }
6458 }
6459
6460 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6461 if self.pending_rename.is_some() {
6462 return None;
6463 }
6464
6465 let provider = self.semantics_provider.clone()?;
6466 let buffer = self.buffer.read(cx);
6467 let newest_selection = self.selections.newest_anchor().clone();
6468 let cursor_position = newest_selection.head();
6469 let (cursor_buffer, cursor_buffer_position) =
6470 buffer.text_anchor_for_position(cursor_position, cx)?;
6471 let (tail_buffer, tail_buffer_position) =
6472 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6473 if cursor_buffer != tail_buffer {
6474 return None;
6475 }
6476
6477 let snapshot = cursor_buffer.read(cx).snapshot();
6478 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6479 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6480 if start_word_range != end_word_range {
6481 self.document_highlights_task.take();
6482 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6483 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6484 return None;
6485 }
6486
6487 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6488 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6489 cx.background_executor()
6490 .timer(Duration::from_millis(debounce))
6491 .await;
6492
6493 let highlights = if let Some(highlights) = cx
6494 .update(|cx| {
6495 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6496 })
6497 .ok()
6498 .flatten()
6499 {
6500 highlights.await.log_err()
6501 } else {
6502 None
6503 };
6504
6505 if let Some(highlights) = highlights {
6506 this.update(cx, |this, cx| {
6507 if this.pending_rename.is_some() {
6508 return;
6509 }
6510
6511 let buffer_id = cursor_position.buffer_id;
6512 let buffer = this.buffer.read(cx);
6513 if !buffer
6514 .text_anchor_for_position(cursor_position, cx)
6515 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6516 {
6517 return;
6518 }
6519
6520 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6521 let mut write_ranges = Vec::new();
6522 let mut read_ranges = Vec::new();
6523 for highlight in highlights {
6524 for (excerpt_id, excerpt_range) in
6525 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6526 {
6527 let start = highlight
6528 .range
6529 .start
6530 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6531 let end = highlight
6532 .range
6533 .end
6534 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6535 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6536 continue;
6537 }
6538
6539 let range = Anchor {
6540 buffer_id,
6541 excerpt_id,
6542 text_anchor: start,
6543 diff_base_anchor: None,
6544 }..Anchor {
6545 buffer_id,
6546 excerpt_id,
6547 text_anchor: end,
6548 diff_base_anchor: None,
6549 };
6550 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6551 write_ranges.push(range);
6552 } else {
6553 read_ranges.push(range);
6554 }
6555 }
6556 }
6557
6558 this.highlight_background::<DocumentHighlightRead>(
6559 &read_ranges,
6560 |theme| theme.colors().editor_document_highlight_read_background,
6561 cx,
6562 );
6563 this.highlight_background::<DocumentHighlightWrite>(
6564 &write_ranges,
6565 |theme| theme.colors().editor_document_highlight_write_background,
6566 cx,
6567 );
6568 cx.notify();
6569 })
6570 .log_err();
6571 }
6572 }));
6573 None
6574 }
6575
6576 fn prepare_highlight_query_from_selection(
6577 &mut self,
6578 cx: &mut Context<Editor>,
6579 ) -> Option<(String, Range<Anchor>)> {
6580 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6581 return None;
6582 }
6583 if !EditorSettings::get_global(cx).selection_highlight {
6584 return None;
6585 }
6586 if self.selections.count() != 1 || self.selections.line_mode {
6587 return None;
6588 }
6589 let selection = self.selections.newest::<Point>(cx);
6590 if selection.is_empty() || selection.start.row != selection.end.row {
6591 return None;
6592 }
6593 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6594 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6595 let query = multi_buffer_snapshot
6596 .text_for_range(selection_anchor_range.clone())
6597 .collect::<String>();
6598 if query.trim().is_empty() {
6599 return None;
6600 }
6601 Some((query, selection_anchor_range))
6602 }
6603
6604 fn update_selection_occurrence_highlights(
6605 &mut self,
6606 query_text: String,
6607 query_range: Range<Anchor>,
6608 multi_buffer_range_to_query: Range<Point>,
6609 use_debounce: bool,
6610 window: &mut Window,
6611 cx: &mut Context<Editor>,
6612 ) -> Task<()> {
6613 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6614 cx.spawn_in(window, async move |editor, cx| {
6615 if use_debounce {
6616 cx.background_executor()
6617 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6618 .await;
6619 }
6620 let match_task = cx.background_spawn(async move {
6621 let buffer_ranges = multi_buffer_snapshot
6622 .range_to_buffer_ranges(multi_buffer_range_to_query)
6623 .into_iter()
6624 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6625 let mut match_ranges = Vec::new();
6626 let Ok(regex) = project::search::SearchQuery::text(
6627 query_text.clone(),
6628 false,
6629 false,
6630 false,
6631 Default::default(),
6632 Default::default(),
6633 false,
6634 None,
6635 ) else {
6636 return Vec::default();
6637 };
6638 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6639 match_ranges.extend(
6640 regex
6641 .search(&buffer_snapshot, Some(search_range.clone()))
6642 .await
6643 .into_iter()
6644 .filter_map(|match_range| {
6645 let match_start = buffer_snapshot
6646 .anchor_after(search_range.start + match_range.start);
6647 let match_end = buffer_snapshot
6648 .anchor_before(search_range.start + match_range.end);
6649 let match_anchor_range = Anchor::range_in_buffer(
6650 excerpt_id,
6651 buffer_snapshot.remote_id(),
6652 match_start..match_end,
6653 );
6654 (match_anchor_range != query_range).then_some(match_anchor_range)
6655 }),
6656 );
6657 }
6658 match_ranges
6659 });
6660 let match_ranges = match_task.await;
6661 editor
6662 .update_in(cx, |editor, _, cx| {
6663 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6664 if !match_ranges.is_empty() {
6665 editor.highlight_background::<SelectedTextHighlight>(
6666 &match_ranges,
6667 |theme| theme.colors().editor_document_highlight_bracket_background,
6668 cx,
6669 )
6670 }
6671 })
6672 .log_err();
6673 })
6674 }
6675
6676 fn refresh_selected_text_highlights(
6677 &mut self,
6678 on_buffer_edit: bool,
6679 window: &mut Window,
6680 cx: &mut Context<Editor>,
6681 ) {
6682 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6683 else {
6684 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6685 self.quick_selection_highlight_task.take();
6686 self.debounced_selection_highlight_task.take();
6687 return;
6688 };
6689 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6690 if on_buffer_edit
6691 || self
6692 .quick_selection_highlight_task
6693 .as_ref()
6694 .map_or(true, |(prev_anchor_range, _)| {
6695 prev_anchor_range != &query_range
6696 })
6697 {
6698 let multi_buffer_visible_start = self
6699 .scroll_manager
6700 .anchor()
6701 .anchor
6702 .to_point(&multi_buffer_snapshot);
6703 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6704 multi_buffer_visible_start
6705 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6706 Bias::Left,
6707 );
6708 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6709 self.quick_selection_highlight_task = Some((
6710 query_range.clone(),
6711 self.update_selection_occurrence_highlights(
6712 query_text.clone(),
6713 query_range.clone(),
6714 multi_buffer_visible_range,
6715 false,
6716 window,
6717 cx,
6718 ),
6719 ));
6720 }
6721 if on_buffer_edit
6722 || self
6723 .debounced_selection_highlight_task
6724 .as_ref()
6725 .map_or(true, |(prev_anchor_range, _)| {
6726 prev_anchor_range != &query_range
6727 })
6728 {
6729 let multi_buffer_start = multi_buffer_snapshot
6730 .anchor_before(0)
6731 .to_point(&multi_buffer_snapshot);
6732 let multi_buffer_end = multi_buffer_snapshot
6733 .anchor_after(multi_buffer_snapshot.len())
6734 .to_point(&multi_buffer_snapshot);
6735 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6736 self.debounced_selection_highlight_task = Some((
6737 query_range.clone(),
6738 self.update_selection_occurrence_highlights(
6739 query_text,
6740 query_range,
6741 multi_buffer_full_range,
6742 true,
6743 window,
6744 cx,
6745 ),
6746 ));
6747 }
6748 }
6749
6750 pub fn refresh_inline_completion(
6751 &mut self,
6752 debounce: bool,
6753 user_requested: bool,
6754 window: &mut Window,
6755 cx: &mut Context<Self>,
6756 ) -> Option<()> {
6757 let provider = self.edit_prediction_provider()?;
6758 let cursor = self.selections.newest_anchor().head();
6759 let (buffer, cursor_buffer_position) =
6760 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6761
6762 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6763 self.discard_inline_completion(false, cx);
6764 return None;
6765 }
6766
6767 if !user_requested
6768 && (!self.should_show_edit_predictions()
6769 || !self.is_focused(window)
6770 || buffer.read(cx).is_empty())
6771 {
6772 self.discard_inline_completion(false, cx);
6773 return None;
6774 }
6775
6776 self.update_visible_inline_completion(window, cx);
6777 provider.refresh(
6778 self.project.clone(),
6779 buffer,
6780 cursor_buffer_position,
6781 debounce,
6782 cx,
6783 );
6784 Some(())
6785 }
6786
6787 fn show_edit_predictions_in_menu(&self) -> bool {
6788 match self.edit_prediction_settings {
6789 EditPredictionSettings::Disabled => false,
6790 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6791 }
6792 }
6793
6794 pub fn edit_predictions_enabled(&self) -> bool {
6795 match self.edit_prediction_settings {
6796 EditPredictionSettings::Disabled => false,
6797 EditPredictionSettings::Enabled { .. } => true,
6798 }
6799 }
6800
6801 fn edit_prediction_requires_modifier(&self) -> bool {
6802 match self.edit_prediction_settings {
6803 EditPredictionSettings::Disabled => false,
6804 EditPredictionSettings::Enabled {
6805 preview_requires_modifier,
6806 ..
6807 } => preview_requires_modifier,
6808 }
6809 }
6810
6811 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6812 if self.edit_prediction_provider.is_none() {
6813 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6814 } else {
6815 let selection = self.selections.newest_anchor();
6816 let cursor = selection.head();
6817
6818 if let Some((buffer, cursor_buffer_position)) =
6819 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6820 {
6821 self.edit_prediction_settings =
6822 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6823 }
6824 }
6825 }
6826
6827 fn edit_prediction_settings_at_position(
6828 &self,
6829 buffer: &Entity<Buffer>,
6830 buffer_position: language::Anchor,
6831 cx: &App,
6832 ) -> EditPredictionSettings {
6833 if !self.mode.is_full()
6834 || !self.show_inline_completions_override.unwrap_or(true)
6835 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6836 {
6837 return EditPredictionSettings::Disabled;
6838 }
6839
6840 let buffer = buffer.read(cx);
6841
6842 let file = buffer.file();
6843
6844 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6845 return EditPredictionSettings::Disabled;
6846 };
6847
6848 let by_provider = matches!(
6849 self.menu_inline_completions_policy,
6850 MenuInlineCompletionsPolicy::ByProvider
6851 );
6852
6853 let show_in_menu = by_provider
6854 && self
6855 .edit_prediction_provider
6856 .as_ref()
6857 .map_or(false, |provider| {
6858 provider.provider.show_completions_in_menu()
6859 });
6860
6861 let preview_requires_modifier =
6862 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6863
6864 EditPredictionSettings::Enabled {
6865 show_in_menu,
6866 preview_requires_modifier,
6867 }
6868 }
6869
6870 fn should_show_edit_predictions(&self) -> bool {
6871 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6872 }
6873
6874 pub fn edit_prediction_preview_is_active(&self) -> bool {
6875 matches!(
6876 self.edit_prediction_preview,
6877 EditPredictionPreview::Active { .. }
6878 )
6879 }
6880
6881 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6882 let cursor = self.selections.newest_anchor().head();
6883 if let Some((buffer, cursor_position)) =
6884 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6885 {
6886 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6887 } else {
6888 false
6889 }
6890 }
6891
6892 pub fn supports_minimap(&self, cx: &App) -> bool {
6893 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6894 }
6895
6896 fn edit_predictions_enabled_in_buffer(
6897 &self,
6898 buffer: &Entity<Buffer>,
6899 buffer_position: language::Anchor,
6900 cx: &App,
6901 ) -> bool {
6902 maybe!({
6903 if self.read_only(cx) {
6904 return Some(false);
6905 }
6906 let provider = self.edit_prediction_provider()?;
6907 if !provider.is_enabled(&buffer, buffer_position, cx) {
6908 return Some(false);
6909 }
6910 let buffer = buffer.read(cx);
6911 let Some(file) = buffer.file() else {
6912 return Some(true);
6913 };
6914 let settings = all_language_settings(Some(file), cx);
6915 Some(settings.edit_predictions_enabled_for_file(file, cx))
6916 })
6917 .unwrap_or(false)
6918 }
6919
6920 fn cycle_inline_completion(
6921 &mut self,
6922 direction: Direction,
6923 window: &mut Window,
6924 cx: &mut Context<Self>,
6925 ) -> Option<()> {
6926 let provider = self.edit_prediction_provider()?;
6927 let cursor = self.selections.newest_anchor().head();
6928 let (buffer, cursor_buffer_position) =
6929 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6930 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6931 return None;
6932 }
6933
6934 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6935 self.update_visible_inline_completion(window, cx);
6936
6937 Some(())
6938 }
6939
6940 pub fn show_inline_completion(
6941 &mut self,
6942 _: &ShowEditPrediction,
6943 window: &mut Window,
6944 cx: &mut Context<Self>,
6945 ) {
6946 if !self.has_active_inline_completion() {
6947 self.refresh_inline_completion(false, true, window, cx);
6948 return;
6949 }
6950
6951 self.update_visible_inline_completion(window, cx);
6952 }
6953
6954 pub fn display_cursor_names(
6955 &mut self,
6956 _: &DisplayCursorNames,
6957 window: &mut Window,
6958 cx: &mut Context<Self>,
6959 ) {
6960 self.show_cursor_names(window, cx);
6961 }
6962
6963 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6964 self.show_cursor_names = true;
6965 cx.notify();
6966 cx.spawn_in(window, async move |this, cx| {
6967 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6968 this.update(cx, |this, cx| {
6969 this.show_cursor_names = false;
6970 cx.notify()
6971 })
6972 .ok()
6973 })
6974 .detach();
6975 }
6976
6977 pub fn next_edit_prediction(
6978 &mut self,
6979 _: &NextEditPrediction,
6980 window: &mut Window,
6981 cx: &mut Context<Self>,
6982 ) {
6983 if self.has_active_inline_completion() {
6984 self.cycle_inline_completion(Direction::Next, window, cx);
6985 } else {
6986 let is_copilot_disabled = self
6987 .refresh_inline_completion(false, true, window, cx)
6988 .is_none();
6989 if is_copilot_disabled {
6990 cx.propagate();
6991 }
6992 }
6993 }
6994
6995 pub fn previous_edit_prediction(
6996 &mut self,
6997 _: &PreviousEditPrediction,
6998 window: &mut Window,
6999 cx: &mut Context<Self>,
7000 ) {
7001 if self.has_active_inline_completion() {
7002 self.cycle_inline_completion(Direction::Prev, window, cx);
7003 } else {
7004 let is_copilot_disabled = self
7005 .refresh_inline_completion(false, true, window, cx)
7006 .is_none();
7007 if is_copilot_disabled {
7008 cx.propagate();
7009 }
7010 }
7011 }
7012
7013 pub fn accept_edit_prediction(
7014 &mut self,
7015 _: &AcceptEditPrediction,
7016 window: &mut Window,
7017 cx: &mut Context<Self>,
7018 ) {
7019 if self.show_edit_predictions_in_menu() {
7020 self.hide_context_menu(window, cx);
7021 }
7022
7023 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7024 return;
7025 };
7026
7027 self.report_inline_completion_event(
7028 active_inline_completion.completion_id.clone(),
7029 true,
7030 cx,
7031 );
7032
7033 match &active_inline_completion.completion {
7034 InlineCompletion::Move { target, .. } => {
7035 let target = *target;
7036
7037 if let Some(position_map) = &self.last_position_map {
7038 if position_map
7039 .visible_row_range
7040 .contains(&target.to_display_point(&position_map.snapshot).row())
7041 || !self.edit_prediction_requires_modifier()
7042 {
7043 self.unfold_ranges(&[target..target], true, false, cx);
7044 // Note that this is also done in vim's handler of the Tab action.
7045 self.change_selections(
7046 Some(Autoscroll::newest()),
7047 window,
7048 cx,
7049 |selections| {
7050 selections.select_anchor_ranges([target..target]);
7051 },
7052 );
7053 self.clear_row_highlights::<EditPredictionPreview>();
7054
7055 self.edit_prediction_preview
7056 .set_previous_scroll_position(None);
7057 } else {
7058 self.edit_prediction_preview
7059 .set_previous_scroll_position(Some(
7060 position_map.snapshot.scroll_anchor,
7061 ));
7062
7063 self.highlight_rows::<EditPredictionPreview>(
7064 target..target,
7065 cx.theme().colors().editor_highlighted_line_background,
7066 RowHighlightOptions {
7067 autoscroll: true,
7068 ..Default::default()
7069 },
7070 cx,
7071 );
7072 self.request_autoscroll(Autoscroll::fit(), cx);
7073 }
7074 }
7075 }
7076 InlineCompletion::Edit { edits, .. } => {
7077 if let Some(provider) = self.edit_prediction_provider() {
7078 provider.accept(cx);
7079 }
7080
7081 // Store the transaction ID and selections before applying the edit
7082 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7083
7084 let snapshot = self.buffer.read(cx).snapshot(cx);
7085 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7086
7087 self.buffer.update(cx, |buffer, cx| {
7088 buffer.edit(edits.iter().cloned(), None, cx)
7089 });
7090
7091 self.change_selections(None, window, cx, |s| {
7092 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7093 });
7094
7095 let selections = self.selections.disjoint_anchors();
7096 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7097 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7098 if has_new_transaction {
7099 self.selection_history
7100 .insert_transaction(transaction_id_now, selections);
7101 }
7102 }
7103
7104 self.update_visible_inline_completion(window, cx);
7105 if self.active_inline_completion.is_none() {
7106 self.refresh_inline_completion(true, true, window, cx);
7107 }
7108
7109 cx.notify();
7110 }
7111 }
7112
7113 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7114 }
7115
7116 pub fn accept_partial_inline_completion(
7117 &mut self,
7118 _: &AcceptPartialEditPrediction,
7119 window: &mut Window,
7120 cx: &mut Context<Self>,
7121 ) {
7122 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7123 return;
7124 };
7125 if self.selections.count() != 1 {
7126 return;
7127 }
7128
7129 self.report_inline_completion_event(
7130 active_inline_completion.completion_id.clone(),
7131 true,
7132 cx,
7133 );
7134
7135 match &active_inline_completion.completion {
7136 InlineCompletion::Move { target, .. } => {
7137 let target = *target;
7138 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7139 selections.select_anchor_ranges([target..target]);
7140 });
7141 }
7142 InlineCompletion::Edit { edits, .. } => {
7143 // Find an insertion that starts at the cursor position.
7144 let snapshot = self.buffer.read(cx).snapshot(cx);
7145 let cursor_offset = self.selections.newest::<usize>(cx).head();
7146 let insertion = edits.iter().find_map(|(range, text)| {
7147 let range = range.to_offset(&snapshot);
7148 if range.is_empty() && range.start == cursor_offset {
7149 Some(text)
7150 } else {
7151 None
7152 }
7153 });
7154
7155 if let Some(text) = insertion {
7156 let mut partial_completion = text
7157 .chars()
7158 .by_ref()
7159 .take_while(|c| c.is_alphabetic())
7160 .collect::<String>();
7161 if partial_completion.is_empty() {
7162 partial_completion = text
7163 .chars()
7164 .by_ref()
7165 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7166 .collect::<String>();
7167 }
7168
7169 cx.emit(EditorEvent::InputHandled {
7170 utf16_range_to_replace: None,
7171 text: partial_completion.clone().into(),
7172 });
7173
7174 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7175
7176 self.refresh_inline_completion(true, true, window, cx);
7177 cx.notify();
7178 } else {
7179 self.accept_edit_prediction(&Default::default(), window, cx);
7180 }
7181 }
7182 }
7183 }
7184
7185 fn discard_inline_completion(
7186 &mut self,
7187 should_report_inline_completion_event: bool,
7188 cx: &mut Context<Self>,
7189 ) -> bool {
7190 if should_report_inline_completion_event {
7191 let completion_id = self
7192 .active_inline_completion
7193 .as_ref()
7194 .and_then(|active_completion| active_completion.completion_id.clone());
7195
7196 self.report_inline_completion_event(completion_id, false, cx);
7197 }
7198
7199 if let Some(provider) = self.edit_prediction_provider() {
7200 provider.discard(cx);
7201 }
7202
7203 self.take_active_inline_completion(cx)
7204 }
7205
7206 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7207 let Some(provider) = self.edit_prediction_provider() else {
7208 return;
7209 };
7210
7211 let Some((_, buffer, _)) = self
7212 .buffer
7213 .read(cx)
7214 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7215 else {
7216 return;
7217 };
7218
7219 let extension = buffer
7220 .read(cx)
7221 .file()
7222 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7223
7224 let event_type = match accepted {
7225 true => "Edit Prediction Accepted",
7226 false => "Edit Prediction Discarded",
7227 };
7228 telemetry::event!(
7229 event_type,
7230 provider = provider.name(),
7231 prediction_id = id,
7232 suggestion_accepted = accepted,
7233 file_extension = extension,
7234 );
7235 }
7236
7237 pub fn has_active_inline_completion(&self) -> bool {
7238 self.active_inline_completion.is_some()
7239 }
7240
7241 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7242 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7243 return false;
7244 };
7245
7246 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7247 self.clear_highlights::<InlineCompletionHighlight>(cx);
7248 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7249 true
7250 }
7251
7252 /// Returns true when we're displaying the edit prediction popover below the cursor
7253 /// like we are not previewing and the LSP autocomplete menu is visible
7254 /// or we are in `when_holding_modifier` mode.
7255 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7256 if self.edit_prediction_preview_is_active()
7257 || !self.show_edit_predictions_in_menu()
7258 || !self.edit_predictions_enabled()
7259 {
7260 return false;
7261 }
7262
7263 if self.has_visible_completions_menu() {
7264 return true;
7265 }
7266
7267 has_completion && self.edit_prediction_requires_modifier()
7268 }
7269
7270 fn handle_modifiers_changed(
7271 &mut self,
7272 modifiers: Modifiers,
7273 position_map: &PositionMap,
7274 window: &mut Window,
7275 cx: &mut Context<Self>,
7276 ) {
7277 if self.show_edit_predictions_in_menu() {
7278 self.update_edit_prediction_preview(&modifiers, window, cx);
7279 }
7280
7281 self.update_selection_mode(&modifiers, position_map, window, cx);
7282
7283 let mouse_position = window.mouse_position();
7284 if !position_map.text_hitbox.is_hovered(window) {
7285 return;
7286 }
7287
7288 self.update_hovered_link(
7289 position_map.point_for_position(mouse_position),
7290 &position_map.snapshot,
7291 modifiers,
7292 window,
7293 cx,
7294 )
7295 }
7296
7297 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7298 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7299 if invert {
7300 match multi_cursor_setting {
7301 MultiCursorModifier::Alt => modifiers.alt,
7302 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7303 }
7304 } else {
7305 match multi_cursor_setting {
7306 MultiCursorModifier::Alt => modifiers.secondary(),
7307 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7308 }
7309 }
7310 }
7311
7312 fn columnar_selection_mode(
7313 modifiers: &Modifiers,
7314 cx: &mut Context<Self>,
7315 ) -> Option<ColumnarMode> {
7316 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7317 if Self::multi_cursor_modifier(false, modifiers, cx) {
7318 Some(ColumnarMode::FromMouse)
7319 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7320 Some(ColumnarMode::FromSelection)
7321 } else {
7322 None
7323 }
7324 } else {
7325 None
7326 }
7327 }
7328
7329 fn update_selection_mode(
7330 &mut self,
7331 modifiers: &Modifiers,
7332 position_map: &PositionMap,
7333 window: &mut Window,
7334 cx: &mut Context<Self>,
7335 ) {
7336 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7337 return;
7338 };
7339 if self.selections.pending.is_none() {
7340 return;
7341 }
7342
7343 let mouse_position = window.mouse_position();
7344 let point_for_position = position_map.point_for_position(mouse_position);
7345 let position = point_for_position.previous_valid;
7346
7347 self.select(
7348 SelectPhase::BeginColumnar {
7349 position,
7350 reset: false,
7351 mode,
7352 goal_column: point_for_position.exact_unclipped.column(),
7353 },
7354 window,
7355 cx,
7356 );
7357 }
7358
7359 fn update_edit_prediction_preview(
7360 &mut self,
7361 modifiers: &Modifiers,
7362 window: &mut Window,
7363 cx: &mut Context<Self>,
7364 ) {
7365 let mut modifiers_held = false;
7366 if let Some(accept_keystroke) = self
7367 .accept_edit_prediction_keybind(false, window, cx)
7368 .keystroke()
7369 {
7370 modifiers_held = modifiers_held
7371 || (&accept_keystroke.modifiers == modifiers
7372 && accept_keystroke.modifiers.modified());
7373 };
7374 if let Some(accept_partial_keystroke) = self
7375 .accept_edit_prediction_keybind(true, window, cx)
7376 .keystroke()
7377 {
7378 modifiers_held = modifiers_held
7379 || (&accept_partial_keystroke.modifiers == modifiers
7380 && accept_partial_keystroke.modifiers.modified());
7381 }
7382
7383 if modifiers_held {
7384 if matches!(
7385 self.edit_prediction_preview,
7386 EditPredictionPreview::Inactive { .. }
7387 ) {
7388 self.edit_prediction_preview = EditPredictionPreview::Active {
7389 previous_scroll_position: None,
7390 since: Instant::now(),
7391 };
7392
7393 self.update_visible_inline_completion(window, cx);
7394 cx.notify();
7395 }
7396 } else if let EditPredictionPreview::Active {
7397 previous_scroll_position,
7398 since,
7399 } = self.edit_prediction_preview
7400 {
7401 if let (Some(previous_scroll_position), Some(position_map)) =
7402 (previous_scroll_position, self.last_position_map.as_ref())
7403 {
7404 self.set_scroll_position(
7405 previous_scroll_position
7406 .scroll_position(&position_map.snapshot.display_snapshot),
7407 window,
7408 cx,
7409 );
7410 }
7411
7412 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7413 released_too_fast: since.elapsed() < Duration::from_millis(200),
7414 };
7415 self.clear_row_highlights::<EditPredictionPreview>();
7416 self.update_visible_inline_completion(window, cx);
7417 cx.notify();
7418 }
7419 }
7420
7421 fn update_visible_inline_completion(
7422 &mut self,
7423 _window: &mut Window,
7424 cx: &mut Context<Self>,
7425 ) -> Option<()> {
7426 let selection = self.selections.newest_anchor();
7427 let cursor = selection.head();
7428 let multibuffer = self.buffer.read(cx).snapshot(cx);
7429 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7430 let excerpt_id = cursor.excerpt_id;
7431
7432 let show_in_menu = self.show_edit_predictions_in_menu();
7433 let completions_menu_has_precedence = !show_in_menu
7434 && (self.context_menu.borrow().is_some()
7435 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7436
7437 if completions_menu_has_precedence
7438 || !offset_selection.is_empty()
7439 || self
7440 .active_inline_completion
7441 .as_ref()
7442 .map_or(false, |completion| {
7443 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7444 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7445 !invalidation_range.contains(&offset_selection.head())
7446 })
7447 {
7448 self.discard_inline_completion(false, cx);
7449 return None;
7450 }
7451
7452 self.take_active_inline_completion(cx);
7453 let Some(provider) = self.edit_prediction_provider() else {
7454 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7455 return None;
7456 };
7457
7458 let (buffer, cursor_buffer_position) =
7459 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7460
7461 self.edit_prediction_settings =
7462 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7463
7464 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7465
7466 if self.edit_prediction_indent_conflict {
7467 let cursor_point = cursor.to_point(&multibuffer);
7468
7469 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7470
7471 if let Some((_, indent)) = indents.iter().next() {
7472 if indent.len == cursor_point.column {
7473 self.edit_prediction_indent_conflict = false;
7474 }
7475 }
7476 }
7477
7478 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7479 let edits = inline_completion
7480 .edits
7481 .into_iter()
7482 .flat_map(|(range, new_text)| {
7483 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7484 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7485 Some((start..end, new_text))
7486 })
7487 .collect::<Vec<_>>();
7488 if edits.is_empty() {
7489 return None;
7490 }
7491
7492 let first_edit_start = edits.first().unwrap().0.start;
7493 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7494 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7495
7496 let last_edit_end = edits.last().unwrap().0.end;
7497 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7498 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7499
7500 let cursor_row = cursor.to_point(&multibuffer).row;
7501
7502 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7503
7504 let mut inlay_ids = Vec::new();
7505 let invalidation_row_range;
7506 let move_invalidation_row_range = if cursor_row < edit_start_row {
7507 Some(cursor_row..edit_end_row)
7508 } else if cursor_row > edit_end_row {
7509 Some(edit_start_row..cursor_row)
7510 } else {
7511 None
7512 };
7513 let is_move =
7514 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7515 let completion = if is_move {
7516 invalidation_row_range =
7517 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7518 let target = first_edit_start;
7519 InlineCompletion::Move { target, snapshot }
7520 } else {
7521 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7522 && !self.inline_completions_hidden_for_vim_mode;
7523
7524 if show_completions_in_buffer {
7525 if edits
7526 .iter()
7527 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7528 {
7529 let mut inlays = Vec::new();
7530 for (range, new_text) in &edits {
7531 let inlay = Inlay::inline_completion(
7532 post_inc(&mut self.next_inlay_id),
7533 range.start,
7534 new_text.as_str(),
7535 );
7536 inlay_ids.push(inlay.id);
7537 inlays.push(inlay);
7538 }
7539
7540 self.splice_inlays(&[], inlays, cx);
7541 } else {
7542 let background_color = cx.theme().status().deleted_background;
7543 self.highlight_text::<InlineCompletionHighlight>(
7544 edits.iter().map(|(range, _)| range.clone()).collect(),
7545 HighlightStyle {
7546 background_color: Some(background_color),
7547 ..Default::default()
7548 },
7549 cx,
7550 );
7551 }
7552 }
7553
7554 invalidation_row_range = edit_start_row..edit_end_row;
7555
7556 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7557 if provider.show_tab_accept_marker() {
7558 EditDisplayMode::TabAccept
7559 } else {
7560 EditDisplayMode::Inline
7561 }
7562 } else {
7563 EditDisplayMode::DiffPopover
7564 };
7565
7566 InlineCompletion::Edit {
7567 edits,
7568 edit_preview: inline_completion.edit_preview,
7569 display_mode,
7570 snapshot,
7571 }
7572 };
7573
7574 let invalidation_range = multibuffer
7575 .anchor_before(Point::new(invalidation_row_range.start, 0))
7576 ..multibuffer.anchor_after(Point::new(
7577 invalidation_row_range.end,
7578 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7579 ));
7580
7581 self.stale_inline_completion_in_menu = None;
7582 self.active_inline_completion = Some(InlineCompletionState {
7583 inlay_ids,
7584 completion,
7585 completion_id: inline_completion.id,
7586 invalidation_range,
7587 });
7588
7589 cx.notify();
7590
7591 Some(())
7592 }
7593
7594 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7595 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7596 }
7597
7598 fn clear_tasks(&mut self) {
7599 self.tasks.clear()
7600 }
7601
7602 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7603 if self.tasks.insert(key, value).is_some() {
7604 // This case should hopefully be rare, but just in case...
7605 log::error!(
7606 "multiple different run targets found on a single line, only the last target will be rendered"
7607 )
7608 }
7609 }
7610
7611 /// Get all display points of breakpoints that will be rendered within editor
7612 ///
7613 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7614 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7615 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7616 fn active_breakpoints(
7617 &self,
7618 range: Range<DisplayRow>,
7619 window: &mut Window,
7620 cx: &mut Context<Self>,
7621 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7622 let mut breakpoint_display_points = HashMap::default();
7623
7624 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7625 return breakpoint_display_points;
7626 };
7627
7628 let snapshot = self.snapshot(window, cx);
7629
7630 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7631 let Some(project) = self.project.as_ref() else {
7632 return breakpoint_display_points;
7633 };
7634
7635 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7636 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7637
7638 for (buffer_snapshot, range, excerpt_id) in
7639 multi_buffer_snapshot.range_to_buffer_ranges(range)
7640 {
7641 let Some(buffer) = project
7642 .read(cx)
7643 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7644 else {
7645 continue;
7646 };
7647 let breakpoints = breakpoint_store.read(cx).breakpoints(
7648 &buffer,
7649 Some(
7650 buffer_snapshot.anchor_before(range.start)
7651 ..buffer_snapshot.anchor_after(range.end),
7652 ),
7653 buffer_snapshot,
7654 cx,
7655 );
7656 for (breakpoint, state) in breakpoints {
7657 let multi_buffer_anchor =
7658 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7659 let position = multi_buffer_anchor
7660 .to_point(&multi_buffer_snapshot)
7661 .to_display_point(&snapshot);
7662
7663 breakpoint_display_points.insert(
7664 position.row(),
7665 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7666 );
7667 }
7668 }
7669
7670 breakpoint_display_points
7671 }
7672
7673 fn breakpoint_context_menu(
7674 &self,
7675 anchor: Anchor,
7676 window: &mut Window,
7677 cx: &mut Context<Self>,
7678 ) -> Entity<ui::ContextMenu> {
7679 let weak_editor = cx.weak_entity();
7680 let focus_handle = self.focus_handle(cx);
7681
7682 let row = self
7683 .buffer
7684 .read(cx)
7685 .snapshot(cx)
7686 .summary_for_anchor::<Point>(&anchor)
7687 .row;
7688
7689 let breakpoint = self
7690 .breakpoint_at_row(row, window, cx)
7691 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7692
7693 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7694 "Edit Log Breakpoint"
7695 } else {
7696 "Set Log Breakpoint"
7697 };
7698
7699 let condition_breakpoint_msg = if breakpoint
7700 .as_ref()
7701 .is_some_and(|bp| bp.1.condition.is_some())
7702 {
7703 "Edit Condition Breakpoint"
7704 } else {
7705 "Set Condition Breakpoint"
7706 };
7707
7708 let hit_condition_breakpoint_msg = if breakpoint
7709 .as_ref()
7710 .is_some_and(|bp| bp.1.hit_condition.is_some())
7711 {
7712 "Edit Hit Condition Breakpoint"
7713 } else {
7714 "Set Hit Condition Breakpoint"
7715 };
7716
7717 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7718 "Unset Breakpoint"
7719 } else {
7720 "Set Breakpoint"
7721 };
7722
7723 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7724
7725 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7726 BreakpointState::Enabled => Some("Disable"),
7727 BreakpointState::Disabled => Some("Enable"),
7728 });
7729
7730 let (anchor, breakpoint) =
7731 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7732
7733 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7734 menu.on_blur_subscription(Subscription::new(|| {}))
7735 .context(focus_handle)
7736 .when(run_to_cursor, |this| {
7737 let weak_editor = weak_editor.clone();
7738 this.entry("Run to cursor", None, move |window, cx| {
7739 weak_editor
7740 .update(cx, |editor, cx| {
7741 editor.change_selections(None, window, cx, |s| {
7742 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7743 });
7744 })
7745 .ok();
7746
7747 window.dispatch_action(Box::new(RunToCursor), cx);
7748 })
7749 .separator()
7750 })
7751 .when_some(toggle_state_msg, |this, msg| {
7752 this.entry(msg, None, {
7753 let weak_editor = weak_editor.clone();
7754 let breakpoint = breakpoint.clone();
7755 move |_window, cx| {
7756 weak_editor
7757 .update(cx, |this, cx| {
7758 this.edit_breakpoint_at_anchor(
7759 anchor,
7760 breakpoint.as_ref().clone(),
7761 BreakpointEditAction::InvertState,
7762 cx,
7763 );
7764 })
7765 .log_err();
7766 }
7767 })
7768 })
7769 .entry(set_breakpoint_msg, None, {
7770 let weak_editor = weak_editor.clone();
7771 let breakpoint = breakpoint.clone();
7772 move |_window, cx| {
7773 weak_editor
7774 .update(cx, |this, cx| {
7775 this.edit_breakpoint_at_anchor(
7776 anchor,
7777 breakpoint.as_ref().clone(),
7778 BreakpointEditAction::Toggle,
7779 cx,
7780 );
7781 })
7782 .log_err();
7783 }
7784 })
7785 .entry(log_breakpoint_msg, None, {
7786 let breakpoint = breakpoint.clone();
7787 let weak_editor = weak_editor.clone();
7788 move |window, cx| {
7789 weak_editor
7790 .update(cx, |this, cx| {
7791 this.add_edit_breakpoint_block(
7792 anchor,
7793 breakpoint.as_ref(),
7794 BreakpointPromptEditAction::Log,
7795 window,
7796 cx,
7797 );
7798 })
7799 .log_err();
7800 }
7801 })
7802 .entry(condition_breakpoint_msg, None, {
7803 let breakpoint = breakpoint.clone();
7804 let weak_editor = weak_editor.clone();
7805 move |window, cx| {
7806 weak_editor
7807 .update(cx, |this, cx| {
7808 this.add_edit_breakpoint_block(
7809 anchor,
7810 breakpoint.as_ref(),
7811 BreakpointPromptEditAction::Condition,
7812 window,
7813 cx,
7814 );
7815 })
7816 .log_err();
7817 }
7818 })
7819 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7820 weak_editor
7821 .update(cx, |this, cx| {
7822 this.add_edit_breakpoint_block(
7823 anchor,
7824 breakpoint.as_ref(),
7825 BreakpointPromptEditAction::HitCondition,
7826 window,
7827 cx,
7828 );
7829 })
7830 .log_err();
7831 })
7832 })
7833 }
7834
7835 fn render_breakpoint(
7836 &self,
7837 position: Anchor,
7838 row: DisplayRow,
7839 breakpoint: &Breakpoint,
7840 state: Option<BreakpointSessionState>,
7841 cx: &mut Context<Self>,
7842 ) -> IconButton {
7843 let is_rejected = state.is_some_and(|s| !s.verified);
7844 // Is it a breakpoint that shows up when hovering over gutter?
7845 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7846 (false, false),
7847 |PhantomBreakpointIndicator {
7848 is_active,
7849 display_row,
7850 collides_with_existing_breakpoint,
7851 }| {
7852 (
7853 is_active && display_row == row,
7854 collides_with_existing_breakpoint,
7855 )
7856 },
7857 );
7858
7859 let (color, icon) = {
7860 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7861 (false, false) => ui::IconName::DebugBreakpoint,
7862 (true, false) => ui::IconName::DebugLogBreakpoint,
7863 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7864 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7865 };
7866
7867 let color = if is_phantom {
7868 Color::Hint
7869 } else if is_rejected {
7870 Color::Disabled
7871 } else {
7872 Color::Debugger
7873 };
7874
7875 (color, icon)
7876 };
7877
7878 let breakpoint = Arc::from(breakpoint.clone());
7879
7880 let alt_as_text = gpui::Keystroke {
7881 modifiers: Modifiers::secondary_key(),
7882 ..Default::default()
7883 };
7884 let primary_action_text = if breakpoint.is_disabled() {
7885 "Enable breakpoint"
7886 } else if is_phantom && !collides_with_existing {
7887 "Set breakpoint"
7888 } else {
7889 "Unset breakpoint"
7890 };
7891 let focus_handle = self.focus_handle.clone();
7892
7893 let meta = if is_rejected {
7894 SharedString::from("No executable code is associated with this line.")
7895 } else if collides_with_existing && !breakpoint.is_disabled() {
7896 SharedString::from(format!(
7897 "{alt_as_text}-click to disable,\nright-click for more options."
7898 ))
7899 } else {
7900 SharedString::from("Right-click for more options.")
7901 };
7902 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7903 .icon_size(IconSize::XSmall)
7904 .size(ui::ButtonSize::None)
7905 .when(is_rejected, |this| {
7906 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7907 })
7908 .icon_color(color)
7909 .style(ButtonStyle::Transparent)
7910 .on_click(cx.listener({
7911 let breakpoint = breakpoint.clone();
7912
7913 move |editor, event: &ClickEvent, window, cx| {
7914 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7915 BreakpointEditAction::InvertState
7916 } else {
7917 BreakpointEditAction::Toggle
7918 };
7919
7920 window.focus(&editor.focus_handle(cx));
7921 editor.edit_breakpoint_at_anchor(
7922 position,
7923 breakpoint.as_ref().clone(),
7924 edit_action,
7925 cx,
7926 );
7927 }
7928 }))
7929 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7930 editor.set_breakpoint_context_menu(
7931 row,
7932 Some(position),
7933 event.down.position,
7934 window,
7935 cx,
7936 );
7937 }))
7938 .tooltip(move |window, cx| {
7939 Tooltip::with_meta_in(
7940 primary_action_text,
7941 Some(&ToggleBreakpoint),
7942 meta.clone(),
7943 &focus_handle,
7944 window,
7945 cx,
7946 )
7947 })
7948 }
7949
7950 fn build_tasks_context(
7951 project: &Entity<Project>,
7952 buffer: &Entity<Buffer>,
7953 buffer_row: u32,
7954 tasks: &Arc<RunnableTasks>,
7955 cx: &mut Context<Self>,
7956 ) -> Task<Option<task::TaskContext>> {
7957 let position = Point::new(buffer_row, tasks.column);
7958 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7959 let location = Location {
7960 buffer: buffer.clone(),
7961 range: range_start..range_start,
7962 };
7963 // Fill in the environmental variables from the tree-sitter captures
7964 let mut captured_task_variables = TaskVariables::default();
7965 for (capture_name, value) in tasks.extra_variables.clone() {
7966 captured_task_variables.insert(
7967 task::VariableName::Custom(capture_name.into()),
7968 value.clone(),
7969 );
7970 }
7971 project.update(cx, |project, cx| {
7972 project.task_store().update(cx, |task_store, cx| {
7973 task_store.task_context_for_location(captured_task_variables, location, cx)
7974 })
7975 })
7976 }
7977
7978 pub fn spawn_nearest_task(
7979 &mut self,
7980 action: &SpawnNearestTask,
7981 window: &mut Window,
7982 cx: &mut Context<Self>,
7983 ) {
7984 let Some((workspace, _)) = self.workspace.clone() else {
7985 return;
7986 };
7987 let Some(project) = self.project.clone() else {
7988 return;
7989 };
7990
7991 // Try to find a closest, enclosing node using tree-sitter that has a
7992 // task
7993 let Some((buffer, buffer_row, tasks)) = self
7994 .find_enclosing_node_task(cx)
7995 // Or find the task that's closest in row-distance.
7996 .or_else(|| self.find_closest_task(cx))
7997 else {
7998 return;
7999 };
8000
8001 let reveal_strategy = action.reveal;
8002 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8003 cx.spawn_in(window, async move |_, cx| {
8004 let context = task_context.await?;
8005 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8006
8007 let resolved = &mut resolved_task.resolved;
8008 resolved.reveal = reveal_strategy;
8009
8010 workspace
8011 .update_in(cx, |workspace, window, cx| {
8012 workspace.schedule_resolved_task(
8013 task_source_kind,
8014 resolved_task,
8015 false,
8016 window,
8017 cx,
8018 );
8019 })
8020 .ok()
8021 })
8022 .detach();
8023 }
8024
8025 fn find_closest_task(
8026 &mut self,
8027 cx: &mut Context<Self>,
8028 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8029 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8030
8031 let ((buffer_id, row), tasks) = self
8032 .tasks
8033 .iter()
8034 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8035
8036 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8037 let tasks = Arc::new(tasks.to_owned());
8038 Some((buffer, *row, tasks))
8039 }
8040
8041 fn find_enclosing_node_task(
8042 &mut self,
8043 cx: &mut Context<Self>,
8044 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8045 let snapshot = self.buffer.read(cx).snapshot(cx);
8046 let offset = self.selections.newest::<usize>(cx).head();
8047 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8048 let buffer_id = excerpt.buffer().remote_id();
8049
8050 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8051 let mut cursor = layer.node().walk();
8052
8053 while cursor.goto_first_child_for_byte(offset).is_some() {
8054 if cursor.node().end_byte() == offset {
8055 cursor.goto_next_sibling();
8056 }
8057 }
8058
8059 // Ascend to the smallest ancestor that contains the range and has a task.
8060 loop {
8061 let node = cursor.node();
8062 let node_range = node.byte_range();
8063 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8064
8065 // Check if this node contains our offset
8066 if node_range.start <= offset && node_range.end >= offset {
8067 // If it contains offset, check for task
8068 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8069 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8070 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8071 }
8072 }
8073
8074 if !cursor.goto_parent() {
8075 break;
8076 }
8077 }
8078 None
8079 }
8080
8081 fn render_run_indicator(
8082 &self,
8083 _style: &EditorStyle,
8084 is_active: bool,
8085 row: DisplayRow,
8086 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8087 cx: &mut Context<Self>,
8088 ) -> IconButton {
8089 let color = Color::Muted;
8090 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8091
8092 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8093 .shape(ui::IconButtonShape::Square)
8094 .icon_size(IconSize::XSmall)
8095 .icon_color(color)
8096 .toggle_state(is_active)
8097 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8098 let quick_launch = e.down.button == MouseButton::Left;
8099 window.focus(&editor.focus_handle(cx));
8100 editor.toggle_code_actions(
8101 &ToggleCodeActions {
8102 deployed_from: Some(CodeActionSource::RunMenu(row)),
8103 quick_launch,
8104 },
8105 window,
8106 cx,
8107 );
8108 }))
8109 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8110 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8111 }))
8112 }
8113
8114 pub fn context_menu_visible(&self) -> bool {
8115 !self.edit_prediction_preview_is_active()
8116 && self
8117 .context_menu
8118 .borrow()
8119 .as_ref()
8120 .map_or(false, |menu| menu.visible())
8121 }
8122
8123 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8124 self.context_menu
8125 .borrow()
8126 .as_ref()
8127 .map(|menu| menu.origin())
8128 }
8129
8130 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8131 self.context_menu_options = Some(options);
8132 }
8133
8134 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8135 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8136
8137 fn render_edit_prediction_popover(
8138 &mut self,
8139 text_bounds: &Bounds<Pixels>,
8140 content_origin: gpui::Point<Pixels>,
8141 right_margin: Pixels,
8142 editor_snapshot: &EditorSnapshot,
8143 visible_row_range: Range<DisplayRow>,
8144 scroll_top: f32,
8145 scroll_bottom: f32,
8146 line_layouts: &[LineWithInvisibles],
8147 line_height: Pixels,
8148 scroll_pixel_position: gpui::Point<Pixels>,
8149 newest_selection_head: Option<DisplayPoint>,
8150 editor_width: Pixels,
8151 style: &EditorStyle,
8152 window: &mut Window,
8153 cx: &mut App,
8154 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8155 if self.mode().is_minimap() {
8156 return None;
8157 }
8158 let active_inline_completion = self.active_inline_completion.as_ref()?;
8159
8160 if self.edit_prediction_visible_in_cursor_popover(true) {
8161 return None;
8162 }
8163
8164 match &active_inline_completion.completion {
8165 InlineCompletion::Move { target, .. } => {
8166 let target_display_point = target.to_display_point(editor_snapshot);
8167
8168 if self.edit_prediction_requires_modifier() {
8169 if !self.edit_prediction_preview_is_active() {
8170 return None;
8171 }
8172
8173 self.render_edit_prediction_modifier_jump_popover(
8174 text_bounds,
8175 content_origin,
8176 visible_row_range,
8177 line_layouts,
8178 line_height,
8179 scroll_pixel_position,
8180 newest_selection_head,
8181 target_display_point,
8182 window,
8183 cx,
8184 )
8185 } else {
8186 self.render_edit_prediction_eager_jump_popover(
8187 text_bounds,
8188 content_origin,
8189 editor_snapshot,
8190 visible_row_range,
8191 scroll_top,
8192 scroll_bottom,
8193 line_height,
8194 scroll_pixel_position,
8195 target_display_point,
8196 editor_width,
8197 window,
8198 cx,
8199 )
8200 }
8201 }
8202 InlineCompletion::Edit {
8203 display_mode: EditDisplayMode::Inline,
8204 ..
8205 } => None,
8206 InlineCompletion::Edit {
8207 display_mode: EditDisplayMode::TabAccept,
8208 edits,
8209 ..
8210 } => {
8211 let range = &edits.first()?.0;
8212 let target_display_point = range.end.to_display_point(editor_snapshot);
8213
8214 self.render_edit_prediction_end_of_line_popover(
8215 "Accept",
8216 editor_snapshot,
8217 visible_row_range,
8218 target_display_point,
8219 line_height,
8220 scroll_pixel_position,
8221 content_origin,
8222 editor_width,
8223 window,
8224 cx,
8225 )
8226 }
8227 InlineCompletion::Edit {
8228 edits,
8229 edit_preview,
8230 display_mode: EditDisplayMode::DiffPopover,
8231 snapshot,
8232 } => self.render_edit_prediction_diff_popover(
8233 text_bounds,
8234 content_origin,
8235 right_margin,
8236 editor_snapshot,
8237 visible_row_range,
8238 line_layouts,
8239 line_height,
8240 scroll_pixel_position,
8241 newest_selection_head,
8242 editor_width,
8243 style,
8244 edits,
8245 edit_preview,
8246 snapshot,
8247 window,
8248 cx,
8249 ),
8250 }
8251 }
8252
8253 fn render_edit_prediction_modifier_jump_popover(
8254 &mut self,
8255 text_bounds: &Bounds<Pixels>,
8256 content_origin: gpui::Point<Pixels>,
8257 visible_row_range: Range<DisplayRow>,
8258 line_layouts: &[LineWithInvisibles],
8259 line_height: Pixels,
8260 scroll_pixel_position: gpui::Point<Pixels>,
8261 newest_selection_head: Option<DisplayPoint>,
8262 target_display_point: DisplayPoint,
8263 window: &mut Window,
8264 cx: &mut App,
8265 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8266 let scrolled_content_origin =
8267 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8268
8269 const SCROLL_PADDING_Y: Pixels = px(12.);
8270
8271 if target_display_point.row() < visible_row_range.start {
8272 return self.render_edit_prediction_scroll_popover(
8273 |_| SCROLL_PADDING_Y,
8274 IconName::ArrowUp,
8275 visible_row_range,
8276 line_layouts,
8277 newest_selection_head,
8278 scrolled_content_origin,
8279 window,
8280 cx,
8281 );
8282 } else if target_display_point.row() >= visible_row_range.end {
8283 return self.render_edit_prediction_scroll_popover(
8284 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8285 IconName::ArrowDown,
8286 visible_row_range,
8287 line_layouts,
8288 newest_selection_head,
8289 scrolled_content_origin,
8290 window,
8291 cx,
8292 );
8293 }
8294
8295 const POLE_WIDTH: Pixels = px(2.);
8296
8297 let line_layout =
8298 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8299 let target_column = target_display_point.column() as usize;
8300
8301 let target_x = line_layout.x_for_index(target_column);
8302 let target_y =
8303 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8304
8305 let flag_on_right = target_x < text_bounds.size.width / 2.;
8306
8307 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8308 border_color.l += 0.001;
8309
8310 let mut element = v_flex()
8311 .items_end()
8312 .when(flag_on_right, |el| el.items_start())
8313 .child(if flag_on_right {
8314 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8315 .rounded_bl(px(0.))
8316 .rounded_tl(px(0.))
8317 .border_l_2()
8318 .border_color(border_color)
8319 } else {
8320 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8321 .rounded_br(px(0.))
8322 .rounded_tr(px(0.))
8323 .border_r_2()
8324 .border_color(border_color)
8325 })
8326 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8327 .into_any();
8328
8329 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8330
8331 let mut origin = scrolled_content_origin + point(target_x, target_y)
8332 - point(
8333 if flag_on_right {
8334 POLE_WIDTH
8335 } else {
8336 size.width - POLE_WIDTH
8337 },
8338 size.height - line_height,
8339 );
8340
8341 origin.x = origin.x.max(content_origin.x);
8342
8343 element.prepaint_at(origin, window, cx);
8344
8345 Some((element, origin))
8346 }
8347
8348 fn render_edit_prediction_scroll_popover(
8349 &mut self,
8350 to_y: impl Fn(Size<Pixels>) -> Pixels,
8351 scroll_icon: IconName,
8352 visible_row_range: Range<DisplayRow>,
8353 line_layouts: &[LineWithInvisibles],
8354 newest_selection_head: Option<DisplayPoint>,
8355 scrolled_content_origin: gpui::Point<Pixels>,
8356 window: &mut Window,
8357 cx: &mut App,
8358 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8359 let mut element = self
8360 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8361 .into_any();
8362
8363 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8364
8365 let cursor = newest_selection_head?;
8366 let cursor_row_layout =
8367 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8368 let cursor_column = cursor.column() as usize;
8369
8370 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8371
8372 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8373
8374 element.prepaint_at(origin, window, cx);
8375 Some((element, origin))
8376 }
8377
8378 fn render_edit_prediction_eager_jump_popover(
8379 &mut self,
8380 text_bounds: &Bounds<Pixels>,
8381 content_origin: gpui::Point<Pixels>,
8382 editor_snapshot: &EditorSnapshot,
8383 visible_row_range: Range<DisplayRow>,
8384 scroll_top: f32,
8385 scroll_bottom: f32,
8386 line_height: Pixels,
8387 scroll_pixel_position: gpui::Point<Pixels>,
8388 target_display_point: DisplayPoint,
8389 editor_width: Pixels,
8390 window: &mut Window,
8391 cx: &mut App,
8392 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8393 if target_display_point.row().as_f32() < scroll_top {
8394 let mut element = self
8395 .render_edit_prediction_line_popover(
8396 "Jump to Edit",
8397 Some(IconName::ArrowUp),
8398 window,
8399 cx,
8400 )?
8401 .into_any();
8402
8403 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8404 let offset = point(
8405 (text_bounds.size.width - size.width) / 2.,
8406 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8407 );
8408
8409 let origin = text_bounds.origin + offset;
8410 element.prepaint_at(origin, window, cx);
8411 Some((element, origin))
8412 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8413 let mut element = self
8414 .render_edit_prediction_line_popover(
8415 "Jump to Edit",
8416 Some(IconName::ArrowDown),
8417 window,
8418 cx,
8419 )?
8420 .into_any();
8421
8422 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8423 let offset = point(
8424 (text_bounds.size.width - size.width) / 2.,
8425 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8426 );
8427
8428 let origin = text_bounds.origin + offset;
8429 element.prepaint_at(origin, window, cx);
8430 Some((element, origin))
8431 } else {
8432 self.render_edit_prediction_end_of_line_popover(
8433 "Jump to Edit",
8434 editor_snapshot,
8435 visible_row_range,
8436 target_display_point,
8437 line_height,
8438 scroll_pixel_position,
8439 content_origin,
8440 editor_width,
8441 window,
8442 cx,
8443 )
8444 }
8445 }
8446
8447 fn render_edit_prediction_end_of_line_popover(
8448 self: &mut Editor,
8449 label: &'static str,
8450 editor_snapshot: &EditorSnapshot,
8451 visible_row_range: Range<DisplayRow>,
8452 target_display_point: DisplayPoint,
8453 line_height: Pixels,
8454 scroll_pixel_position: gpui::Point<Pixels>,
8455 content_origin: gpui::Point<Pixels>,
8456 editor_width: Pixels,
8457 window: &mut Window,
8458 cx: &mut App,
8459 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8460 let target_line_end = DisplayPoint::new(
8461 target_display_point.row(),
8462 editor_snapshot.line_len(target_display_point.row()),
8463 );
8464
8465 let mut element = self
8466 .render_edit_prediction_line_popover(label, None, window, cx)?
8467 .into_any();
8468
8469 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8470
8471 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8472
8473 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8474 let mut origin = start_point
8475 + line_origin
8476 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8477 origin.x = origin.x.max(content_origin.x);
8478
8479 let max_x = content_origin.x + editor_width - size.width;
8480
8481 if origin.x > max_x {
8482 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8483
8484 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8485 origin.y += offset;
8486 IconName::ArrowUp
8487 } else {
8488 origin.y -= offset;
8489 IconName::ArrowDown
8490 };
8491
8492 element = self
8493 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8494 .into_any();
8495
8496 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8497
8498 origin.x = content_origin.x + editor_width - size.width - px(2.);
8499 }
8500
8501 element.prepaint_at(origin, window, cx);
8502 Some((element, origin))
8503 }
8504
8505 fn render_edit_prediction_diff_popover(
8506 self: &Editor,
8507 text_bounds: &Bounds<Pixels>,
8508 content_origin: gpui::Point<Pixels>,
8509 right_margin: Pixels,
8510 editor_snapshot: &EditorSnapshot,
8511 visible_row_range: Range<DisplayRow>,
8512 line_layouts: &[LineWithInvisibles],
8513 line_height: Pixels,
8514 scroll_pixel_position: gpui::Point<Pixels>,
8515 newest_selection_head: Option<DisplayPoint>,
8516 editor_width: Pixels,
8517 style: &EditorStyle,
8518 edits: &Vec<(Range<Anchor>, String)>,
8519 edit_preview: &Option<language::EditPreview>,
8520 snapshot: &language::BufferSnapshot,
8521 window: &mut Window,
8522 cx: &mut App,
8523 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8524 let edit_start = edits
8525 .first()
8526 .unwrap()
8527 .0
8528 .start
8529 .to_display_point(editor_snapshot);
8530 let edit_end = edits
8531 .last()
8532 .unwrap()
8533 .0
8534 .end
8535 .to_display_point(editor_snapshot);
8536
8537 let is_visible = visible_row_range.contains(&edit_start.row())
8538 || visible_row_range.contains(&edit_end.row());
8539 if !is_visible {
8540 return None;
8541 }
8542
8543 let highlighted_edits =
8544 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8545
8546 let styled_text = highlighted_edits.to_styled_text(&style.text);
8547 let line_count = highlighted_edits.text.lines().count();
8548
8549 const BORDER_WIDTH: Pixels = px(1.);
8550
8551 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8552 let has_keybind = keybind.is_some();
8553
8554 let mut element = h_flex()
8555 .items_start()
8556 .child(
8557 h_flex()
8558 .bg(cx.theme().colors().editor_background)
8559 .border(BORDER_WIDTH)
8560 .shadow_sm()
8561 .border_color(cx.theme().colors().border)
8562 .rounded_l_lg()
8563 .when(line_count > 1, |el| el.rounded_br_lg())
8564 .pr_1()
8565 .child(styled_text),
8566 )
8567 .child(
8568 h_flex()
8569 .h(line_height + BORDER_WIDTH * 2.)
8570 .px_1p5()
8571 .gap_1()
8572 // Workaround: For some reason, there's a gap if we don't do this
8573 .ml(-BORDER_WIDTH)
8574 .shadow(vec![gpui::BoxShadow {
8575 color: gpui::black().opacity(0.05),
8576 offset: point(px(1.), px(1.)),
8577 blur_radius: px(2.),
8578 spread_radius: px(0.),
8579 }])
8580 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8581 .border(BORDER_WIDTH)
8582 .border_color(cx.theme().colors().border)
8583 .rounded_r_lg()
8584 .id("edit_prediction_diff_popover_keybind")
8585 .when(!has_keybind, |el| {
8586 let status_colors = cx.theme().status();
8587
8588 el.bg(status_colors.error_background)
8589 .border_color(status_colors.error.opacity(0.6))
8590 .child(Icon::new(IconName::Info).color(Color::Error))
8591 .cursor_default()
8592 .hoverable_tooltip(move |_window, cx| {
8593 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8594 })
8595 })
8596 .children(keybind),
8597 )
8598 .into_any();
8599
8600 let longest_row =
8601 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8602 let longest_line_width = if visible_row_range.contains(&longest_row) {
8603 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8604 } else {
8605 layout_line(
8606 longest_row,
8607 editor_snapshot,
8608 style,
8609 editor_width,
8610 |_| false,
8611 window,
8612 cx,
8613 )
8614 .width
8615 };
8616
8617 let viewport_bounds =
8618 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8619 right: -right_margin,
8620 ..Default::default()
8621 });
8622
8623 let x_after_longest =
8624 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8625 - scroll_pixel_position.x;
8626
8627 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8628
8629 // Fully visible if it can be displayed within the window (allow overlapping other
8630 // panes). However, this is only allowed if the popover starts within text_bounds.
8631 let can_position_to_the_right = x_after_longest < text_bounds.right()
8632 && x_after_longest + element_bounds.width < viewport_bounds.right();
8633
8634 let mut origin = if can_position_to_the_right {
8635 point(
8636 x_after_longest,
8637 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8638 - scroll_pixel_position.y,
8639 )
8640 } else {
8641 let cursor_row = newest_selection_head.map(|head| head.row());
8642 let above_edit = edit_start
8643 .row()
8644 .0
8645 .checked_sub(line_count as u32)
8646 .map(DisplayRow);
8647 let below_edit = Some(edit_end.row() + 1);
8648 let above_cursor =
8649 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8650 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8651
8652 // Place the edit popover adjacent to the edit if there is a location
8653 // available that is onscreen and does not obscure the cursor. Otherwise,
8654 // place it adjacent to the cursor.
8655 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8656 .into_iter()
8657 .flatten()
8658 .find(|&start_row| {
8659 let end_row = start_row + line_count as u32;
8660 visible_row_range.contains(&start_row)
8661 && visible_row_range.contains(&end_row)
8662 && cursor_row.map_or(true, |cursor_row| {
8663 !((start_row..end_row).contains(&cursor_row))
8664 })
8665 })?;
8666
8667 content_origin
8668 + point(
8669 -scroll_pixel_position.x,
8670 row_target.as_f32() * line_height - scroll_pixel_position.y,
8671 )
8672 };
8673
8674 origin.x -= BORDER_WIDTH;
8675
8676 window.defer_draw(element, origin, 1);
8677
8678 // Do not return an element, since it will already be drawn due to defer_draw.
8679 None
8680 }
8681
8682 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8683 px(30.)
8684 }
8685
8686 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8687 if self.read_only(cx) {
8688 cx.theme().players().read_only()
8689 } else {
8690 self.style.as_ref().unwrap().local_player
8691 }
8692 }
8693
8694 fn render_edit_prediction_accept_keybind(
8695 &self,
8696 window: &mut Window,
8697 cx: &App,
8698 ) -> Option<AnyElement> {
8699 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8700 let accept_keystroke = accept_binding.keystroke()?;
8701
8702 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8703
8704 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8705 Color::Accent
8706 } else {
8707 Color::Muted
8708 };
8709
8710 h_flex()
8711 .px_0p5()
8712 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8713 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8714 .text_size(TextSize::XSmall.rems(cx))
8715 .child(h_flex().children(ui::render_modifiers(
8716 &accept_keystroke.modifiers,
8717 PlatformStyle::platform(),
8718 Some(modifiers_color),
8719 Some(IconSize::XSmall.rems().into()),
8720 true,
8721 )))
8722 .when(is_platform_style_mac, |parent| {
8723 parent.child(accept_keystroke.key.clone())
8724 })
8725 .when(!is_platform_style_mac, |parent| {
8726 parent.child(
8727 Key::new(
8728 util::capitalize(&accept_keystroke.key),
8729 Some(Color::Default),
8730 )
8731 .size(Some(IconSize::XSmall.rems().into())),
8732 )
8733 })
8734 .into_any()
8735 .into()
8736 }
8737
8738 fn render_edit_prediction_line_popover(
8739 &self,
8740 label: impl Into<SharedString>,
8741 icon: Option<IconName>,
8742 window: &mut Window,
8743 cx: &App,
8744 ) -> Option<Stateful<Div>> {
8745 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8746
8747 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8748 let has_keybind = keybind.is_some();
8749
8750 let result = h_flex()
8751 .id("ep-line-popover")
8752 .py_0p5()
8753 .pl_1()
8754 .pr(padding_right)
8755 .gap_1()
8756 .rounded_md()
8757 .border_1()
8758 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8759 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8760 .shadow_sm()
8761 .when(!has_keybind, |el| {
8762 let status_colors = cx.theme().status();
8763
8764 el.bg(status_colors.error_background)
8765 .border_color(status_colors.error.opacity(0.6))
8766 .pl_2()
8767 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8768 .cursor_default()
8769 .hoverable_tooltip(move |_window, cx| {
8770 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8771 })
8772 })
8773 .children(keybind)
8774 .child(
8775 Label::new(label)
8776 .size(LabelSize::Small)
8777 .when(!has_keybind, |el| {
8778 el.color(cx.theme().status().error.into()).strikethrough()
8779 }),
8780 )
8781 .when(!has_keybind, |el| {
8782 el.child(
8783 h_flex().ml_1().child(
8784 Icon::new(IconName::Info)
8785 .size(IconSize::Small)
8786 .color(cx.theme().status().error.into()),
8787 ),
8788 )
8789 })
8790 .when_some(icon, |element, icon| {
8791 element.child(
8792 div()
8793 .mt(px(1.5))
8794 .child(Icon::new(icon).size(IconSize::Small)),
8795 )
8796 });
8797
8798 Some(result)
8799 }
8800
8801 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8802 let accent_color = cx.theme().colors().text_accent;
8803 let editor_bg_color = cx.theme().colors().editor_background;
8804 editor_bg_color.blend(accent_color.opacity(0.1))
8805 }
8806
8807 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8808 let accent_color = cx.theme().colors().text_accent;
8809 let editor_bg_color = cx.theme().colors().editor_background;
8810 editor_bg_color.blend(accent_color.opacity(0.6))
8811 }
8812
8813 fn render_edit_prediction_cursor_popover(
8814 &self,
8815 min_width: Pixels,
8816 max_width: Pixels,
8817 cursor_point: Point,
8818 style: &EditorStyle,
8819 accept_keystroke: Option<&gpui::Keystroke>,
8820 _window: &Window,
8821 cx: &mut Context<Editor>,
8822 ) -> Option<AnyElement> {
8823 let provider = self.edit_prediction_provider.as_ref()?;
8824
8825 if provider.provider.needs_terms_acceptance(cx) {
8826 return Some(
8827 h_flex()
8828 .min_w(min_width)
8829 .flex_1()
8830 .px_2()
8831 .py_1()
8832 .gap_3()
8833 .elevation_2(cx)
8834 .hover(|style| style.bg(cx.theme().colors().element_hover))
8835 .id("accept-terms")
8836 .cursor_pointer()
8837 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8838 .on_click(cx.listener(|this, _event, window, cx| {
8839 cx.stop_propagation();
8840 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8841 window.dispatch_action(
8842 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8843 cx,
8844 );
8845 }))
8846 .child(
8847 h_flex()
8848 .flex_1()
8849 .gap_2()
8850 .child(Icon::new(IconName::ZedPredict))
8851 .child(Label::new("Accept Terms of Service"))
8852 .child(div().w_full())
8853 .child(
8854 Icon::new(IconName::ArrowUpRight)
8855 .color(Color::Muted)
8856 .size(IconSize::Small),
8857 )
8858 .into_any_element(),
8859 )
8860 .into_any(),
8861 );
8862 }
8863
8864 let is_refreshing = provider.provider.is_refreshing(cx);
8865
8866 fn pending_completion_container() -> Div {
8867 h_flex()
8868 .h_full()
8869 .flex_1()
8870 .gap_2()
8871 .child(Icon::new(IconName::ZedPredict))
8872 }
8873
8874 let completion = match &self.active_inline_completion {
8875 Some(prediction) => {
8876 if !self.has_visible_completions_menu() {
8877 const RADIUS: Pixels = px(6.);
8878 const BORDER_WIDTH: Pixels = px(1.);
8879
8880 return Some(
8881 h_flex()
8882 .elevation_2(cx)
8883 .border(BORDER_WIDTH)
8884 .border_color(cx.theme().colors().border)
8885 .when(accept_keystroke.is_none(), |el| {
8886 el.border_color(cx.theme().status().error)
8887 })
8888 .rounded(RADIUS)
8889 .rounded_tl(px(0.))
8890 .overflow_hidden()
8891 .child(div().px_1p5().child(match &prediction.completion {
8892 InlineCompletion::Move { target, snapshot } => {
8893 use text::ToPoint as _;
8894 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8895 {
8896 Icon::new(IconName::ZedPredictDown)
8897 } else {
8898 Icon::new(IconName::ZedPredictUp)
8899 }
8900 }
8901 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8902 }))
8903 .child(
8904 h_flex()
8905 .gap_1()
8906 .py_1()
8907 .px_2()
8908 .rounded_r(RADIUS - BORDER_WIDTH)
8909 .border_l_1()
8910 .border_color(cx.theme().colors().border)
8911 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8912 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8913 el.child(
8914 Label::new("Hold")
8915 .size(LabelSize::Small)
8916 .when(accept_keystroke.is_none(), |el| {
8917 el.strikethrough()
8918 })
8919 .line_height_style(LineHeightStyle::UiLabel),
8920 )
8921 })
8922 .id("edit_prediction_cursor_popover_keybind")
8923 .when(accept_keystroke.is_none(), |el| {
8924 let status_colors = cx.theme().status();
8925
8926 el.bg(status_colors.error_background)
8927 .border_color(status_colors.error.opacity(0.6))
8928 .child(Icon::new(IconName::Info).color(Color::Error))
8929 .cursor_default()
8930 .hoverable_tooltip(move |_window, cx| {
8931 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8932 .into()
8933 })
8934 })
8935 .when_some(
8936 accept_keystroke.as_ref(),
8937 |el, accept_keystroke| {
8938 el.child(h_flex().children(ui::render_modifiers(
8939 &accept_keystroke.modifiers,
8940 PlatformStyle::platform(),
8941 Some(Color::Default),
8942 Some(IconSize::XSmall.rems().into()),
8943 false,
8944 )))
8945 },
8946 ),
8947 )
8948 .into_any(),
8949 );
8950 }
8951
8952 self.render_edit_prediction_cursor_popover_preview(
8953 prediction,
8954 cursor_point,
8955 style,
8956 cx,
8957 )?
8958 }
8959
8960 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8961 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8962 stale_completion,
8963 cursor_point,
8964 style,
8965 cx,
8966 )?,
8967
8968 None => {
8969 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8970 }
8971 },
8972
8973 None => pending_completion_container().child(Label::new("No Prediction")),
8974 };
8975
8976 let completion = if is_refreshing {
8977 completion
8978 .with_animation(
8979 "loading-completion",
8980 Animation::new(Duration::from_secs(2))
8981 .repeat()
8982 .with_easing(pulsating_between(0.4, 0.8)),
8983 |label, delta| label.opacity(delta),
8984 )
8985 .into_any_element()
8986 } else {
8987 completion.into_any_element()
8988 };
8989
8990 let has_completion = self.active_inline_completion.is_some();
8991
8992 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8993 Some(
8994 h_flex()
8995 .min_w(min_width)
8996 .max_w(max_width)
8997 .flex_1()
8998 .elevation_2(cx)
8999 .border_color(cx.theme().colors().border)
9000 .child(
9001 div()
9002 .flex_1()
9003 .py_1()
9004 .px_2()
9005 .overflow_hidden()
9006 .child(completion),
9007 )
9008 .when_some(accept_keystroke, |el, accept_keystroke| {
9009 if !accept_keystroke.modifiers.modified() {
9010 return el;
9011 }
9012
9013 el.child(
9014 h_flex()
9015 .h_full()
9016 .border_l_1()
9017 .rounded_r_lg()
9018 .border_color(cx.theme().colors().border)
9019 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9020 .gap_1()
9021 .py_1()
9022 .px_2()
9023 .child(
9024 h_flex()
9025 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9026 .when(is_platform_style_mac, |parent| parent.gap_1())
9027 .child(h_flex().children(ui::render_modifiers(
9028 &accept_keystroke.modifiers,
9029 PlatformStyle::platform(),
9030 Some(if !has_completion {
9031 Color::Muted
9032 } else {
9033 Color::Default
9034 }),
9035 None,
9036 false,
9037 ))),
9038 )
9039 .child(Label::new("Preview").into_any_element())
9040 .opacity(if has_completion { 1.0 } else { 0.4 }),
9041 )
9042 })
9043 .into_any(),
9044 )
9045 }
9046
9047 fn render_edit_prediction_cursor_popover_preview(
9048 &self,
9049 completion: &InlineCompletionState,
9050 cursor_point: Point,
9051 style: &EditorStyle,
9052 cx: &mut Context<Editor>,
9053 ) -> Option<Div> {
9054 use text::ToPoint as _;
9055
9056 fn render_relative_row_jump(
9057 prefix: impl Into<String>,
9058 current_row: u32,
9059 target_row: u32,
9060 ) -> Div {
9061 let (row_diff, arrow) = if target_row < current_row {
9062 (current_row - target_row, IconName::ArrowUp)
9063 } else {
9064 (target_row - current_row, IconName::ArrowDown)
9065 };
9066
9067 h_flex()
9068 .child(
9069 Label::new(format!("{}{}", prefix.into(), row_diff))
9070 .color(Color::Muted)
9071 .size(LabelSize::Small),
9072 )
9073 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9074 }
9075
9076 match &completion.completion {
9077 InlineCompletion::Move {
9078 target, snapshot, ..
9079 } => Some(
9080 h_flex()
9081 .px_2()
9082 .gap_2()
9083 .flex_1()
9084 .child(
9085 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9086 Icon::new(IconName::ZedPredictDown)
9087 } else {
9088 Icon::new(IconName::ZedPredictUp)
9089 },
9090 )
9091 .child(Label::new("Jump to Edit")),
9092 ),
9093
9094 InlineCompletion::Edit {
9095 edits,
9096 edit_preview,
9097 snapshot,
9098 display_mode: _,
9099 } => {
9100 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9101
9102 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9103 &snapshot,
9104 &edits,
9105 edit_preview.as_ref()?,
9106 true,
9107 cx,
9108 )
9109 .first_line_preview();
9110
9111 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9112 .with_default_highlights(&style.text, highlighted_edits.highlights);
9113
9114 let preview = h_flex()
9115 .gap_1()
9116 .min_w_16()
9117 .child(styled_text)
9118 .when(has_more_lines, |parent| parent.child("…"));
9119
9120 let left = if first_edit_row != cursor_point.row {
9121 render_relative_row_jump("", cursor_point.row, first_edit_row)
9122 .into_any_element()
9123 } else {
9124 Icon::new(IconName::ZedPredict).into_any_element()
9125 };
9126
9127 Some(
9128 h_flex()
9129 .h_full()
9130 .flex_1()
9131 .gap_2()
9132 .pr_1()
9133 .overflow_x_hidden()
9134 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9135 .child(left)
9136 .child(preview),
9137 )
9138 }
9139 }
9140 }
9141
9142 pub fn render_context_menu(
9143 &self,
9144 style: &EditorStyle,
9145 max_height_in_lines: u32,
9146 window: &mut Window,
9147 cx: &mut Context<Editor>,
9148 ) -> Option<AnyElement> {
9149 let menu = self.context_menu.borrow();
9150 let menu = menu.as_ref()?;
9151 if !menu.visible() {
9152 return None;
9153 };
9154 Some(menu.render(style, max_height_in_lines, window, cx))
9155 }
9156
9157 fn render_context_menu_aside(
9158 &mut self,
9159 max_size: Size<Pixels>,
9160 window: &mut Window,
9161 cx: &mut Context<Editor>,
9162 ) -> Option<AnyElement> {
9163 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9164 if menu.visible() {
9165 menu.render_aside(max_size, window, cx)
9166 } else {
9167 None
9168 }
9169 })
9170 }
9171
9172 fn hide_context_menu(
9173 &mut self,
9174 window: &mut Window,
9175 cx: &mut Context<Self>,
9176 ) -> Option<CodeContextMenu> {
9177 cx.notify();
9178 self.completion_tasks.clear();
9179 let context_menu = self.context_menu.borrow_mut().take();
9180 self.stale_inline_completion_in_menu.take();
9181 self.update_visible_inline_completion(window, cx);
9182 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9183 if let Some(completion_provider) = &self.completion_provider {
9184 completion_provider.selection_changed(None, window, cx);
9185 }
9186 }
9187 context_menu
9188 }
9189
9190 fn show_snippet_choices(
9191 &mut self,
9192 choices: &Vec<String>,
9193 selection: Range<Anchor>,
9194 cx: &mut Context<Self>,
9195 ) {
9196 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9197 (Some(a), Some(b)) if a == b => a,
9198 _ => {
9199 log::error!("expected anchor range to have matching buffer IDs");
9200 return;
9201 }
9202 };
9203 let multi_buffer = self.buffer().read(cx);
9204 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9205 return;
9206 };
9207
9208 let id = post_inc(&mut self.next_completion_id);
9209 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9210 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9211 CompletionsMenu::new_snippet_choices(
9212 id,
9213 true,
9214 choices,
9215 selection,
9216 buffer,
9217 snippet_sort_order,
9218 ),
9219 ));
9220 }
9221
9222 pub fn insert_snippet(
9223 &mut self,
9224 insertion_ranges: &[Range<usize>],
9225 snippet: Snippet,
9226 window: &mut Window,
9227 cx: &mut Context<Self>,
9228 ) -> Result<()> {
9229 struct Tabstop<T> {
9230 is_end_tabstop: bool,
9231 ranges: Vec<Range<T>>,
9232 choices: Option<Vec<String>>,
9233 }
9234
9235 let tabstops = self.buffer.update(cx, |buffer, cx| {
9236 let snippet_text: Arc<str> = snippet.text.clone().into();
9237 let edits = insertion_ranges
9238 .iter()
9239 .cloned()
9240 .map(|range| (range, snippet_text.clone()));
9241 let autoindent_mode = AutoindentMode::Block {
9242 original_indent_columns: Vec::new(),
9243 };
9244 buffer.edit(edits, Some(autoindent_mode), cx);
9245
9246 let snapshot = &*buffer.read(cx);
9247 let snippet = &snippet;
9248 snippet
9249 .tabstops
9250 .iter()
9251 .map(|tabstop| {
9252 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9253 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9254 });
9255 let mut tabstop_ranges = tabstop
9256 .ranges
9257 .iter()
9258 .flat_map(|tabstop_range| {
9259 let mut delta = 0_isize;
9260 insertion_ranges.iter().map(move |insertion_range| {
9261 let insertion_start = insertion_range.start as isize + delta;
9262 delta +=
9263 snippet.text.len() as isize - insertion_range.len() as isize;
9264
9265 let start = ((insertion_start + tabstop_range.start) as usize)
9266 .min(snapshot.len());
9267 let end = ((insertion_start + tabstop_range.end) as usize)
9268 .min(snapshot.len());
9269 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9270 })
9271 })
9272 .collect::<Vec<_>>();
9273 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9274
9275 Tabstop {
9276 is_end_tabstop,
9277 ranges: tabstop_ranges,
9278 choices: tabstop.choices.clone(),
9279 }
9280 })
9281 .collect::<Vec<_>>()
9282 });
9283 if let Some(tabstop) = tabstops.first() {
9284 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9285 // Reverse order so that the first range is the newest created selection.
9286 // Completions will use it and autoscroll will prioritize it.
9287 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9288 });
9289
9290 if let Some(choices) = &tabstop.choices {
9291 if let Some(selection) = tabstop.ranges.first() {
9292 self.show_snippet_choices(choices, selection.clone(), cx)
9293 }
9294 }
9295
9296 // If we're already at the last tabstop and it's at the end of the snippet,
9297 // we're done, we don't need to keep the state around.
9298 if !tabstop.is_end_tabstop {
9299 let choices = tabstops
9300 .iter()
9301 .map(|tabstop| tabstop.choices.clone())
9302 .collect();
9303
9304 let ranges = tabstops
9305 .into_iter()
9306 .map(|tabstop| tabstop.ranges)
9307 .collect::<Vec<_>>();
9308
9309 self.snippet_stack.push(SnippetState {
9310 active_index: 0,
9311 ranges,
9312 choices,
9313 });
9314 }
9315
9316 // Check whether the just-entered snippet ends with an auto-closable bracket.
9317 if self.autoclose_regions.is_empty() {
9318 let snapshot = self.buffer.read(cx).snapshot(cx);
9319 for selection in &mut self.selections.all::<Point>(cx) {
9320 let selection_head = selection.head();
9321 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9322 continue;
9323 };
9324
9325 let mut bracket_pair = None;
9326 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9327 let prev_chars = snapshot
9328 .reversed_chars_at(selection_head)
9329 .collect::<String>();
9330 for (pair, enabled) in scope.brackets() {
9331 if enabled
9332 && pair.close
9333 && prev_chars.starts_with(pair.start.as_str())
9334 && next_chars.starts_with(pair.end.as_str())
9335 {
9336 bracket_pair = Some(pair.clone());
9337 break;
9338 }
9339 }
9340 if let Some(pair) = bracket_pair {
9341 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9342 let autoclose_enabled =
9343 self.use_autoclose && snapshot_settings.use_autoclose;
9344 if autoclose_enabled {
9345 let start = snapshot.anchor_after(selection_head);
9346 let end = snapshot.anchor_after(selection_head);
9347 self.autoclose_regions.push(AutocloseRegion {
9348 selection_id: selection.id,
9349 range: start..end,
9350 pair,
9351 });
9352 }
9353 }
9354 }
9355 }
9356 }
9357 Ok(())
9358 }
9359
9360 pub fn move_to_next_snippet_tabstop(
9361 &mut self,
9362 window: &mut Window,
9363 cx: &mut Context<Self>,
9364 ) -> bool {
9365 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9366 }
9367
9368 pub fn move_to_prev_snippet_tabstop(
9369 &mut self,
9370 window: &mut Window,
9371 cx: &mut Context<Self>,
9372 ) -> bool {
9373 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9374 }
9375
9376 pub fn move_to_snippet_tabstop(
9377 &mut self,
9378 bias: Bias,
9379 window: &mut Window,
9380 cx: &mut Context<Self>,
9381 ) -> bool {
9382 if let Some(mut snippet) = self.snippet_stack.pop() {
9383 match bias {
9384 Bias::Left => {
9385 if snippet.active_index > 0 {
9386 snippet.active_index -= 1;
9387 } else {
9388 self.snippet_stack.push(snippet);
9389 return false;
9390 }
9391 }
9392 Bias::Right => {
9393 if snippet.active_index + 1 < snippet.ranges.len() {
9394 snippet.active_index += 1;
9395 } else {
9396 self.snippet_stack.push(snippet);
9397 return false;
9398 }
9399 }
9400 }
9401 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9402 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9403 // Reverse order so that the first range is the newest created selection.
9404 // Completions will use it and autoscroll will prioritize it.
9405 s.select_ranges(current_ranges.iter().rev().cloned())
9406 });
9407
9408 if let Some(choices) = &snippet.choices[snippet.active_index] {
9409 if let Some(selection) = current_ranges.first() {
9410 self.show_snippet_choices(&choices, selection.clone(), cx);
9411 }
9412 }
9413
9414 // If snippet state is not at the last tabstop, push it back on the stack
9415 if snippet.active_index + 1 < snippet.ranges.len() {
9416 self.snippet_stack.push(snippet);
9417 }
9418 return true;
9419 }
9420 }
9421
9422 false
9423 }
9424
9425 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9426 self.transact(window, cx, |this, window, cx| {
9427 this.select_all(&SelectAll, window, cx);
9428 this.insert("", window, cx);
9429 });
9430 }
9431
9432 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9434 self.transact(window, cx, |this, window, cx| {
9435 this.select_autoclose_pair(window, cx);
9436 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9437 if !this.linked_edit_ranges.is_empty() {
9438 let selections = this.selections.all::<MultiBufferPoint>(cx);
9439 let snapshot = this.buffer.read(cx).snapshot(cx);
9440
9441 for selection in selections.iter() {
9442 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9443 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9444 if selection_start.buffer_id != selection_end.buffer_id {
9445 continue;
9446 }
9447 if let Some(ranges) =
9448 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9449 {
9450 for (buffer, entries) in ranges {
9451 linked_ranges.entry(buffer).or_default().extend(entries);
9452 }
9453 }
9454 }
9455 }
9456
9457 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9458 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9459 for selection in &mut selections {
9460 if selection.is_empty() {
9461 let old_head = selection.head();
9462 let mut new_head =
9463 movement::left(&display_map, old_head.to_display_point(&display_map))
9464 .to_point(&display_map);
9465 if let Some((buffer, line_buffer_range)) = display_map
9466 .buffer_snapshot
9467 .buffer_line_for_row(MultiBufferRow(old_head.row))
9468 {
9469 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9470 let indent_len = match indent_size.kind {
9471 IndentKind::Space => {
9472 buffer.settings_at(line_buffer_range.start, cx).tab_size
9473 }
9474 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9475 };
9476 if old_head.column <= indent_size.len && old_head.column > 0 {
9477 let indent_len = indent_len.get();
9478 new_head = cmp::min(
9479 new_head,
9480 MultiBufferPoint::new(
9481 old_head.row,
9482 ((old_head.column - 1) / indent_len) * indent_len,
9483 ),
9484 );
9485 }
9486 }
9487
9488 selection.set_head(new_head, SelectionGoal::None);
9489 }
9490 }
9491
9492 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9493 s.select(selections)
9494 });
9495 this.insert("", window, cx);
9496 let empty_str: Arc<str> = Arc::from("");
9497 for (buffer, edits) in linked_ranges {
9498 let snapshot = buffer.read(cx).snapshot();
9499 use text::ToPoint as TP;
9500
9501 let edits = edits
9502 .into_iter()
9503 .map(|range| {
9504 let end_point = TP::to_point(&range.end, &snapshot);
9505 let mut start_point = TP::to_point(&range.start, &snapshot);
9506
9507 if end_point == start_point {
9508 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9509 .saturating_sub(1);
9510 start_point =
9511 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9512 };
9513
9514 (start_point..end_point, empty_str.clone())
9515 })
9516 .sorted_by_key(|(range, _)| range.start)
9517 .collect::<Vec<_>>();
9518 buffer.update(cx, |this, cx| {
9519 this.edit(edits, None, cx);
9520 })
9521 }
9522 this.refresh_inline_completion(true, false, window, cx);
9523 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9524 });
9525 }
9526
9527 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9529 self.transact(window, cx, |this, window, cx| {
9530 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9531 s.move_with(|map, selection| {
9532 if selection.is_empty() {
9533 let cursor = movement::right(map, selection.head());
9534 selection.end = cursor;
9535 selection.reversed = true;
9536 selection.goal = SelectionGoal::None;
9537 }
9538 })
9539 });
9540 this.insert("", window, cx);
9541 this.refresh_inline_completion(true, false, window, cx);
9542 });
9543 }
9544
9545 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9547 if self.move_to_prev_snippet_tabstop(window, cx) {
9548 return;
9549 }
9550 self.outdent(&Outdent, window, cx);
9551 }
9552
9553 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9554 if self.move_to_next_snippet_tabstop(window, cx) {
9555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9556 return;
9557 }
9558 if self.read_only(cx) {
9559 return;
9560 }
9561 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9562 let mut selections = self.selections.all_adjusted(cx);
9563 let buffer = self.buffer.read(cx);
9564 let snapshot = buffer.snapshot(cx);
9565 let rows_iter = selections.iter().map(|s| s.head().row);
9566 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9567
9568 let has_some_cursor_in_whitespace = selections
9569 .iter()
9570 .filter(|selection| selection.is_empty())
9571 .any(|selection| {
9572 let cursor = selection.head();
9573 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9574 cursor.column < current_indent.len
9575 });
9576
9577 let mut edits = Vec::new();
9578 let mut prev_edited_row = 0;
9579 let mut row_delta = 0;
9580 for selection in &mut selections {
9581 if selection.start.row != prev_edited_row {
9582 row_delta = 0;
9583 }
9584 prev_edited_row = selection.end.row;
9585
9586 // If the selection is non-empty, then increase the indentation of the selected lines.
9587 if !selection.is_empty() {
9588 row_delta =
9589 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9590 continue;
9591 }
9592
9593 let cursor = selection.head();
9594 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9595 if let Some(suggested_indent) =
9596 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9597 {
9598 // Don't do anything if already at suggested indent
9599 // and there is any other cursor which is not
9600 if has_some_cursor_in_whitespace
9601 && cursor.column == current_indent.len
9602 && current_indent.len == suggested_indent.len
9603 {
9604 continue;
9605 }
9606
9607 // Adjust line and move cursor to suggested indent
9608 // if cursor is not at suggested indent
9609 if cursor.column < suggested_indent.len
9610 && cursor.column <= current_indent.len
9611 && current_indent.len <= suggested_indent.len
9612 {
9613 selection.start = Point::new(cursor.row, suggested_indent.len);
9614 selection.end = selection.start;
9615 if row_delta == 0 {
9616 edits.extend(Buffer::edit_for_indent_size_adjustment(
9617 cursor.row,
9618 current_indent,
9619 suggested_indent,
9620 ));
9621 row_delta = suggested_indent.len - current_indent.len;
9622 }
9623 continue;
9624 }
9625
9626 // If current indent is more than suggested indent
9627 // only move cursor to current indent and skip indent
9628 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9629 selection.start = Point::new(cursor.row, current_indent.len);
9630 selection.end = selection.start;
9631 continue;
9632 }
9633 }
9634
9635 // Otherwise, insert a hard or soft tab.
9636 let settings = buffer.language_settings_at(cursor, cx);
9637 let tab_size = if settings.hard_tabs {
9638 IndentSize::tab()
9639 } else {
9640 let tab_size = settings.tab_size.get();
9641 let indent_remainder = snapshot
9642 .text_for_range(Point::new(cursor.row, 0)..cursor)
9643 .flat_map(str::chars)
9644 .fold(row_delta % tab_size, |counter: u32, c| {
9645 if c == '\t' {
9646 0
9647 } else {
9648 (counter + 1) % tab_size
9649 }
9650 });
9651
9652 let chars_to_next_tab_stop = tab_size - indent_remainder;
9653 IndentSize::spaces(chars_to_next_tab_stop)
9654 };
9655 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9656 selection.end = selection.start;
9657 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9658 row_delta += tab_size.len;
9659 }
9660
9661 self.transact(window, cx, |this, window, cx| {
9662 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9663 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9664 s.select(selections)
9665 });
9666 this.refresh_inline_completion(true, false, window, cx);
9667 });
9668 }
9669
9670 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9671 if self.read_only(cx) {
9672 return;
9673 }
9674 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9675 let mut selections = self.selections.all::<Point>(cx);
9676 let mut prev_edited_row = 0;
9677 let mut row_delta = 0;
9678 let mut edits = Vec::new();
9679 let buffer = self.buffer.read(cx);
9680 let snapshot = buffer.snapshot(cx);
9681 for selection in &mut selections {
9682 if selection.start.row != prev_edited_row {
9683 row_delta = 0;
9684 }
9685 prev_edited_row = selection.end.row;
9686
9687 row_delta =
9688 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9689 }
9690
9691 self.transact(window, cx, |this, window, cx| {
9692 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9693 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9694 s.select(selections)
9695 });
9696 });
9697 }
9698
9699 fn indent_selection(
9700 buffer: &MultiBuffer,
9701 snapshot: &MultiBufferSnapshot,
9702 selection: &mut Selection<Point>,
9703 edits: &mut Vec<(Range<Point>, String)>,
9704 delta_for_start_row: u32,
9705 cx: &App,
9706 ) -> u32 {
9707 let settings = buffer.language_settings_at(selection.start, cx);
9708 let tab_size = settings.tab_size.get();
9709 let indent_kind = if settings.hard_tabs {
9710 IndentKind::Tab
9711 } else {
9712 IndentKind::Space
9713 };
9714 let mut start_row = selection.start.row;
9715 let mut end_row = selection.end.row + 1;
9716
9717 // If a selection ends at the beginning of a line, don't indent
9718 // that last line.
9719 if selection.end.column == 0 && selection.end.row > selection.start.row {
9720 end_row -= 1;
9721 }
9722
9723 // Avoid re-indenting a row that has already been indented by a
9724 // previous selection, but still update this selection's column
9725 // to reflect that indentation.
9726 if delta_for_start_row > 0 {
9727 start_row += 1;
9728 selection.start.column += delta_for_start_row;
9729 if selection.end.row == selection.start.row {
9730 selection.end.column += delta_for_start_row;
9731 }
9732 }
9733
9734 let mut delta_for_end_row = 0;
9735 let has_multiple_rows = start_row + 1 != end_row;
9736 for row in start_row..end_row {
9737 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9738 let indent_delta = match (current_indent.kind, indent_kind) {
9739 (IndentKind::Space, IndentKind::Space) => {
9740 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9741 IndentSize::spaces(columns_to_next_tab_stop)
9742 }
9743 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9744 (_, IndentKind::Tab) => IndentSize::tab(),
9745 };
9746
9747 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9748 0
9749 } else {
9750 selection.start.column
9751 };
9752 let row_start = Point::new(row, start);
9753 edits.push((
9754 row_start..row_start,
9755 indent_delta.chars().collect::<String>(),
9756 ));
9757
9758 // Update this selection's endpoints to reflect the indentation.
9759 if row == selection.start.row {
9760 selection.start.column += indent_delta.len;
9761 }
9762 if row == selection.end.row {
9763 selection.end.column += indent_delta.len;
9764 delta_for_end_row = indent_delta.len;
9765 }
9766 }
9767
9768 if selection.start.row == selection.end.row {
9769 delta_for_start_row + delta_for_end_row
9770 } else {
9771 delta_for_end_row
9772 }
9773 }
9774
9775 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9776 if self.read_only(cx) {
9777 return;
9778 }
9779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9780 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9781 let selections = self.selections.all::<Point>(cx);
9782 let mut deletion_ranges = Vec::new();
9783 let mut last_outdent = None;
9784 {
9785 let buffer = self.buffer.read(cx);
9786 let snapshot = buffer.snapshot(cx);
9787 for selection in &selections {
9788 let settings = buffer.language_settings_at(selection.start, cx);
9789 let tab_size = settings.tab_size.get();
9790 let mut rows = selection.spanned_rows(false, &display_map);
9791
9792 // Avoid re-outdenting a row that has already been outdented by a
9793 // previous selection.
9794 if let Some(last_row) = last_outdent {
9795 if last_row == rows.start {
9796 rows.start = rows.start.next_row();
9797 }
9798 }
9799 let has_multiple_rows = rows.len() > 1;
9800 for row in rows.iter_rows() {
9801 let indent_size = snapshot.indent_size_for_line(row);
9802 if indent_size.len > 0 {
9803 let deletion_len = match indent_size.kind {
9804 IndentKind::Space => {
9805 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9806 if columns_to_prev_tab_stop == 0 {
9807 tab_size
9808 } else {
9809 columns_to_prev_tab_stop
9810 }
9811 }
9812 IndentKind::Tab => 1,
9813 };
9814 let start = if has_multiple_rows
9815 || deletion_len > selection.start.column
9816 || indent_size.len < selection.start.column
9817 {
9818 0
9819 } else {
9820 selection.start.column - deletion_len
9821 };
9822 deletion_ranges.push(
9823 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9824 );
9825 last_outdent = Some(row);
9826 }
9827 }
9828 }
9829 }
9830
9831 self.transact(window, cx, |this, window, cx| {
9832 this.buffer.update(cx, |buffer, cx| {
9833 let empty_str: Arc<str> = Arc::default();
9834 buffer.edit(
9835 deletion_ranges
9836 .into_iter()
9837 .map(|range| (range, empty_str.clone())),
9838 None,
9839 cx,
9840 );
9841 });
9842 let selections = this.selections.all::<usize>(cx);
9843 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9844 s.select(selections)
9845 });
9846 });
9847 }
9848
9849 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9850 if self.read_only(cx) {
9851 return;
9852 }
9853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9854 let selections = self
9855 .selections
9856 .all::<usize>(cx)
9857 .into_iter()
9858 .map(|s| s.range());
9859
9860 self.transact(window, cx, |this, window, cx| {
9861 this.buffer.update(cx, |buffer, cx| {
9862 buffer.autoindent_ranges(selections, cx);
9863 });
9864 let selections = this.selections.all::<usize>(cx);
9865 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9866 s.select(selections)
9867 });
9868 });
9869 }
9870
9871 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9872 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9873 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9874 let selections = self.selections.all::<Point>(cx);
9875
9876 let mut new_cursors = Vec::new();
9877 let mut edit_ranges = Vec::new();
9878 let mut selections = selections.iter().peekable();
9879 while let Some(selection) = selections.next() {
9880 let mut rows = selection.spanned_rows(false, &display_map);
9881 let goal_display_column = selection.head().to_display_point(&display_map).column();
9882
9883 // Accumulate contiguous regions of rows that we want to delete.
9884 while let Some(next_selection) = selections.peek() {
9885 let next_rows = next_selection.spanned_rows(false, &display_map);
9886 if next_rows.start <= rows.end {
9887 rows.end = next_rows.end;
9888 selections.next().unwrap();
9889 } else {
9890 break;
9891 }
9892 }
9893
9894 let buffer = &display_map.buffer_snapshot;
9895 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9896 let edit_end;
9897 let cursor_buffer_row;
9898 if buffer.max_point().row >= rows.end.0 {
9899 // If there's a line after the range, delete the \n from the end of the row range
9900 // and position the cursor on the next line.
9901 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9902 cursor_buffer_row = rows.end;
9903 } else {
9904 // If there isn't a line after the range, delete the \n from the line before the
9905 // start of the row range and position the cursor there.
9906 edit_start = edit_start.saturating_sub(1);
9907 edit_end = buffer.len();
9908 cursor_buffer_row = rows.start.previous_row();
9909 }
9910
9911 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9912 *cursor.column_mut() =
9913 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9914
9915 new_cursors.push((
9916 selection.id,
9917 buffer.anchor_after(cursor.to_point(&display_map)),
9918 ));
9919 edit_ranges.push(edit_start..edit_end);
9920 }
9921
9922 self.transact(window, cx, |this, window, cx| {
9923 let buffer = this.buffer.update(cx, |buffer, cx| {
9924 let empty_str: Arc<str> = Arc::default();
9925 buffer.edit(
9926 edit_ranges
9927 .into_iter()
9928 .map(|range| (range, empty_str.clone())),
9929 None,
9930 cx,
9931 );
9932 buffer.snapshot(cx)
9933 });
9934 let new_selections = new_cursors
9935 .into_iter()
9936 .map(|(id, cursor)| {
9937 let cursor = cursor.to_point(&buffer);
9938 Selection {
9939 id,
9940 start: cursor,
9941 end: cursor,
9942 reversed: false,
9943 goal: SelectionGoal::None,
9944 }
9945 })
9946 .collect();
9947
9948 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9949 s.select(new_selections);
9950 });
9951 });
9952 }
9953
9954 pub fn join_lines_impl(
9955 &mut self,
9956 insert_whitespace: bool,
9957 window: &mut Window,
9958 cx: &mut Context<Self>,
9959 ) {
9960 if self.read_only(cx) {
9961 return;
9962 }
9963 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9964 for selection in self.selections.all::<Point>(cx) {
9965 let start = MultiBufferRow(selection.start.row);
9966 // Treat single line selections as if they include the next line. Otherwise this action
9967 // would do nothing for single line selections individual cursors.
9968 let end = if selection.start.row == selection.end.row {
9969 MultiBufferRow(selection.start.row + 1)
9970 } else {
9971 MultiBufferRow(selection.end.row)
9972 };
9973
9974 if let Some(last_row_range) = row_ranges.last_mut() {
9975 if start <= last_row_range.end {
9976 last_row_range.end = end;
9977 continue;
9978 }
9979 }
9980 row_ranges.push(start..end);
9981 }
9982
9983 let snapshot = self.buffer.read(cx).snapshot(cx);
9984 let mut cursor_positions = Vec::new();
9985 for row_range in &row_ranges {
9986 let anchor = snapshot.anchor_before(Point::new(
9987 row_range.end.previous_row().0,
9988 snapshot.line_len(row_range.end.previous_row()),
9989 ));
9990 cursor_positions.push(anchor..anchor);
9991 }
9992
9993 self.transact(window, cx, |this, window, cx| {
9994 for row_range in row_ranges.into_iter().rev() {
9995 for row in row_range.iter_rows().rev() {
9996 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9997 let next_line_row = row.next_row();
9998 let indent = snapshot.indent_size_for_line(next_line_row);
9999 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10000
10001 let replace =
10002 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10003 " "
10004 } else {
10005 ""
10006 };
10007
10008 this.buffer.update(cx, |buffer, cx| {
10009 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10010 });
10011 }
10012 }
10013
10014 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10015 s.select_anchor_ranges(cursor_positions)
10016 });
10017 });
10018 }
10019
10020 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10022 self.join_lines_impl(true, window, cx);
10023 }
10024
10025 pub fn sort_lines_case_sensitive(
10026 &mut self,
10027 _: &SortLinesCaseSensitive,
10028 window: &mut Window,
10029 cx: &mut Context<Self>,
10030 ) {
10031 self.manipulate_lines(window, cx, |lines| lines.sort())
10032 }
10033
10034 pub fn sort_lines_case_insensitive(
10035 &mut self,
10036 _: &SortLinesCaseInsensitive,
10037 window: &mut Window,
10038 cx: &mut Context<Self>,
10039 ) {
10040 self.manipulate_lines(window, cx, |lines| {
10041 lines.sort_by_key(|line| line.to_lowercase())
10042 })
10043 }
10044
10045 pub fn unique_lines_case_insensitive(
10046 &mut self,
10047 _: &UniqueLinesCaseInsensitive,
10048 window: &mut Window,
10049 cx: &mut Context<Self>,
10050 ) {
10051 self.manipulate_lines(window, cx, |lines| {
10052 let mut seen = HashSet::default();
10053 lines.retain(|line| seen.insert(line.to_lowercase()));
10054 })
10055 }
10056
10057 pub fn unique_lines_case_sensitive(
10058 &mut self,
10059 _: &UniqueLinesCaseSensitive,
10060 window: &mut Window,
10061 cx: &mut Context<Self>,
10062 ) {
10063 self.manipulate_lines(window, cx, |lines| {
10064 let mut seen = HashSet::default();
10065 lines.retain(|line| seen.insert(*line));
10066 })
10067 }
10068
10069 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10070 let Some(project) = self.project.clone() else {
10071 return;
10072 };
10073 self.reload(project, window, cx)
10074 .detach_and_notify_err(window, cx);
10075 }
10076
10077 pub fn restore_file(
10078 &mut self,
10079 _: &::git::RestoreFile,
10080 window: &mut Window,
10081 cx: &mut Context<Self>,
10082 ) {
10083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10084 let mut buffer_ids = HashSet::default();
10085 let snapshot = self.buffer().read(cx).snapshot(cx);
10086 for selection in self.selections.all::<usize>(cx) {
10087 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10088 }
10089
10090 let buffer = self.buffer().read(cx);
10091 let ranges = buffer_ids
10092 .into_iter()
10093 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10094 .collect::<Vec<_>>();
10095
10096 self.restore_hunks_in_ranges(ranges, window, cx);
10097 }
10098
10099 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10100 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10101 let selections = self
10102 .selections
10103 .all(cx)
10104 .into_iter()
10105 .map(|s| s.range())
10106 .collect();
10107 self.restore_hunks_in_ranges(selections, window, cx);
10108 }
10109
10110 pub fn restore_hunks_in_ranges(
10111 &mut self,
10112 ranges: Vec<Range<Point>>,
10113 window: &mut Window,
10114 cx: &mut Context<Editor>,
10115 ) {
10116 let mut revert_changes = HashMap::default();
10117 let chunk_by = self
10118 .snapshot(window, cx)
10119 .hunks_for_ranges(ranges)
10120 .into_iter()
10121 .chunk_by(|hunk| hunk.buffer_id);
10122 for (buffer_id, hunks) in &chunk_by {
10123 let hunks = hunks.collect::<Vec<_>>();
10124 for hunk in &hunks {
10125 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10126 }
10127 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10128 }
10129 drop(chunk_by);
10130 if !revert_changes.is_empty() {
10131 self.transact(window, cx, |editor, window, cx| {
10132 editor.restore(revert_changes, window, cx);
10133 });
10134 }
10135 }
10136
10137 pub fn open_active_item_in_terminal(
10138 &mut self,
10139 _: &OpenInTerminal,
10140 window: &mut Window,
10141 cx: &mut Context<Self>,
10142 ) {
10143 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10144 let project_path = buffer.read(cx).project_path(cx)?;
10145 let project = self.project.as_ref()?.read(cx);
10146 let entry = project.entry_for_path(&project_path, cx)?;
10147 let parent = match &entry.canonical_path {
10148 Some(canonical_path) => canonical_path.to_path_buf(),
10149 None => project.absolute_path(&project_path, cx)?,
10150 }
10151 .parent()?
10152 .to_path_buf();
10153 Some(parent)
10154 }) {
10155 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10156 }
10157 }
10158
10159 fn set_breakpoint_context_menu(
10160 &mut self,
10161 display_row: DisplayRow,
10162 position: Option<Anchor>,
10163 clicked_point: gpui::Point<Pixels>,
10164 window: &mut Window,
10165 cx: &mut Context<Self>,
10166 ) {
10167 let source = self
10168 .buffer
10169 .read(cx)
10170 .snapshot(cx)
10171 .anchor_before(Point::new(display_row.0, 0u32));
10172
10173 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10174
10175 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10176 self,
10177 source,
10178 clicked_point,
10179 context_menu,
10180 window,
10181 cx,
10182 );
10183 }
10184
10185 fn add_edit_breakpoint_block(
10186 &mut self,
10187 anchor: Anchor,
10188 breakpoint: &Breakpoint,
10189 edit_action: BreakpointPromptEditAction,
10190 window: &mut Window,
10191 cx: &mut Context<Self>,
10192 ) {
10193 let weak_editor = cx.weak_entity();
10194 let bp_prompt = cx.new(|cx| {
10195 BreakpointPromptEditor::new(
10196 weak_editor,
10197 anchor,
10198 breakpoint.clone(),
10199 edit_action,
10200 window,
10201 cx,
10202 )
10203 });
10204
10205 let height = bp_prompt.update(cx, |this, cx| {
10206 this.prompt
10207 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10208 });
10209 let cloned_prompt = bp_prompt.clone();
10210 let blocks = vec![BlockProperties {
10211 style: BlockStyle::Sticky,
10212 placement: BlockPlacement::Above(anchor),
10213 height: Some(height),
10214 render: Arc::new(move |cx| {
10215 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10216 cloned_prompt.clone().into_any_element()
10217 }),
10218 priority: 0,
10219 render_in_minimap: true,
10220 }];
10221
10222 let focus_handle = bp_prompt.focus_handle(cx);
10223 window.focus(&focus_handle);
10224
10225 let block_ids = self.insert_blocks(blocks, None, cx);
10226 bp_prompt.update(cx, |prompt, _| {
10227 prompt.add_block_ids(block_ids);
10228 });
10229 }
10230
10231 pub(crate) fn breakpoint_at_row(
10232 &self,
10233 row: u32,
10234 window: &mut Window,
10235 cx: &mut Context<Self>,
10236 ) -> Option<(Anchor, Breakpoint)> {
10237 let snapshot = self.snapshot(window, cx);
10238 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10239
10240 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10241 }
10242
10243 pub(crate) fn breakpoint_at_anchor(
10244 &self,
10245 breakpoint_position: Anchor,
10246 snapshot: &EditorSnapshot,
10247 cx: &mut Context<Self>,
10248 ) -> Option<(Anchor, Breakpoint)> {
10249 let project = self.project.clone()?;
10250
10251 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10252 snapshot
10253 .buffer_snapshot
10254 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10255 })?;
10256
10257 let enclosing_excerpt = breakpoint_position.excerpt_id;
10258 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10259 let buffer_snapshot = buffer.read(cx).snapshot();
10260
10261 let row = buffer_snapshot
10262 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10263 .row;
10264
10265 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10266 let anchor_end = snapshot
10267 .buffer_snapshot
10268 .anchor_after(Point::new(row, line_len));
10269
10270 let bp = self
10271 .breakpoint_store
10272 .as_ref()?
10273 .read_with(cx, |breakpoint_store, cx| {
10274 breakpoint_store
10275 .breakpoints(
10276 &buffer,
10277 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10278 &buffer_snapshot,
10279 cx,
10280 )
10281 .next()
10282 .and_then(|(bp, _)| {
10283 let breakpoint_row = buffer_snapshot
10284 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10285 .row;
10286
10287 if breakpoint_row == row {
10288 snapshot
10289 .buffer_snapshot
10290 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10291 .map(|position| (position, bp.bp.clone()))
10292 } else {
10293 None
10294 }
10295 })
10296 });
10297 bp
10298 }
10299
10300 pub fn edit_log_breakpoint(
10301 &mut self,
10302 _: &EditLogBreakpoint,
10303 window: &mut Window,
10304 cx: &mut Context<Self>,
10305 ) {
10306 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10307 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10308 message: None,
10309 state: BreakpointState::Enabled,
10310 condition: None,
10311 hit_condition: None,
10312 });
10313
10314 self.add_edit_breakpoint_block(
10315 anchor,
10316 &breakpoint,
10317 BreakpointPromptEditAction::Log,
10318 window,
10319 cx,
10320 );
10321 }
10322 }
10323
10324 fn breakpoints_at_cursors(
10325 &self,
10326 window: &mut Window,
10327 cx: &mut Context<Self>,
10328 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10329 let snapshot = self.snapshot(window, cx);
10330 let cursors = self
10331 .selections
10332 .disjoint_anchors()
10333 .into_iter()
10334 .map(|selection| {
10335 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10336
10337 let breakpoint_position = self
10338 .breakpoint_at_row(cursor_position.row, window, cx)
10339 .map(|bp| bp.0)
10340 .unwrap_or_else(|| {
10341 snapshot
10342 .display_snapshot
10343 .buffer_snapshot
10344 .anchor_after(Point::new(cursor_position.row, 0))
10345 });
10346
10347 let breakpoint = self
10348 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10349 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10350
10351 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10352 })
10353 // 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.
10354 .collect::<HashMap<Anchor, _>>();
10355
10356 cursors.into_iter().collect()
10357 }
10358
10359 pub fn enable_breakpoint(
10360 &mut self,
10361 _: &crate::actions::EnableBreakpoint,
10362 window: &mut Window,
10363 cx: &mut Context<Self>,
10364 ) {
10365 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10366 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10367 continue;
10368 };
10369 self.edit_breakpoint_at_anchor(
10370 anchor,
10371 breakpoint,
10372 BreakpointEditAction::InvertState,
10373 cx,
10374 );
10375 }
10376 }
10377
10378 pub fn disable_breakpoint(
10379 &mut self,
10380 _: &crate::actions::DisableBreakpoint,
10381 window: &mut Window,
10382 cx: &mut Context<Self>,
10383 ) {
10384 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10385 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10386 continue;
10387 };
10388 self.edit_breakpoint_at_anchor(
10389 anchor,
10390 breakpoint,
10391 BreakpointEditAction::InvertState,
10392 cx,
10393 );
10394 }
10395 }
10396
10397 pub fn toggle_breakpoint(
10398 &mut self,
10399 _: &crate::actions::ToggleBreakpoint,
10400 window: &mut Window,
10401 cx: &mut Context<Self>,
10402 ) {
10403 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10404 if let Some(breakpoint) = breakpoint {
10405 self.edit_breakpoint_at_anchor(
10406 anchor,
10407 breakpoint,
10408 BreakpointEditAction::Toggle,
10409 cx,
10410 );
10411 } else {
10412 self.edit_breakpoint_at_anchor(
10413 anchor,
10414 Breakpoint::new_standard(),
10415 BreakpointEditAction::Toggle,
10416 cx,
10417 );
10418 }
10419 }
10420 }
10421
10422 pub fn edit_breakpoint_at_anchor(
10423 &mut self,
10424 breakpoint_position: Anchor,
10425 breakpoint: Breakpoint,
10426 edit_action: BreakpointEditAction,
10427 cx: &mut Context<Self>,
10428 ) {
10429 let Some(breakpoint_store) = &self.breakpoint_store else {
10430 return;
10431 };
10432
10433 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10434 if breakpoint_position == Anchor::min() {
10435 self.buffer()
10436 .read(cx)
10437 .excerpt_buffer_ids()
10438 .into_iter()
10439 .next()
10440 } else {
10441 None
10442 }
10443 }) else {
10444 return;
10445 };
10446
10447 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10448 return;
10449 };
10450
10451 breakpoint_store.update(cx, |breakpoint_store, cx| {
10452 breakpoint_store.toggle_breakpoint(
10453 buffer,
10454 BreakpointWithPosition {
10455 position: breakpoint_position.text_anchor,
10456 bp: breakpoint,
10457 },
10458 edit_action,
10459 cx,
10460 );
10461 });
10462
10463 cx.notify();
10464 }
10465
10466 #[cfg(any(test, feature = "test-support"))]
10467 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10468 self.breakpoint_store.clone()
10469 }
10470
10471 pub fn prepare_restore_change(
10472 &self,
10473 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10474 hunk: &MultiBufferDiffHunk,
10475 cx: &mut App,
10476 ) -> Option<()> {
10477 if hunk.is_created_file() {
10478 return None;
10479 }
10480 let buffer = self.buffer.read(cx);
10481 let diff = buffer.diff_for(hunk.buffer_id)?;
10482 let buffer = buffer.buffer(hunk.buffer_id)?;
10483 let buffer = buffer.read(cx);
10484 let original_text = diff
10485 .read(cx)
10486 .base_text()
10487 .as_rope()
10488 .slice(hunk.diff_base_byte_range.clone());
10489 let buffer_snapshot = buffer.snapshot();
10490 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10491 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10492 probe
10493 .0
10494 .start
10495 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10496 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10497 }) {
10498 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10499 Some(())
10500 } else {
10501 None
10502 }
10503 }
10504
10505 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10506 self.manipulate_lines(window, cx, |lines| lines.reverse())
10507 }
10508
10509 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10510 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10511 }
10512
10513 fn manipulate_lines<Fn>(
10514 &mut self,
10515 window: &mut Window,
10516 cx: &mut Context<Self>,
10517 mut callback: Fn,
10518 ) where
10519 Fn: FnMut(&mut Vec<&str>),
10520 {
10521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10522
10523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10524 let buffer = self.buffer.read(cx).snapshot(cx);
10525
10526 let mut edits = Vec::new();
10527
10528 let selections = self.selections.all::<Point>(cx);
10529 let mut selections = selections.iter().peekable();
10530 let mut contiguous_row_selections = Vec::new();
10531 let mut new_selections = Vec::new();
10532 let mut added_lines = 0;
10533 let mut removed_lines = 0;
10534
10535 while let Some(selection) = selections.next() {
10536 let (start_row, end_row) = consume_contiguous_rows(
10537 &mut contiguous_row_selections,
10538 selection,
10539 &display_map,
10540 &mut selections,
10541 );
10542
10543 let start_point = Point::new(start_row.0, 0);
10544 let end_point = Point::new(
10545 end_row.previous_row().0,
10546 buffer.line_len(end_row.previous_row()),
10547 );
10548 let text = buffer
10549 .text_for_range(start_point..end_point)
10550 .collect::<String>();
10551
10552 let mut lines = text.split('\n').collect_vec();
10553
10554 let lines_before = lines.len();
10555 callback(&mut lines);
10556 let lines_after = lines.len();
10557
10558 edits.push((start_point..end_point, lines.join("\n")));
10559
10560 // Selections must change based on added and removed line count
10561 let start_row =
10562 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10563 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10564 new_selections.push(Selection {
10565 id: selection.id,
10566 start: start_row,
10567 end: end_row,
10568 goal: SelectionGoal::None,
10569 reversed: selection.reversed,
10570 });
10571
10572 if lines_after > lines_before {
10573 added_lines += lines_after - lines_before;
10574 } else if lines_before > lines_after {
10575 removed_lines += lines_before - lines_after;
10576 }
10577 }
10578
10579 self.transact(window, cx, |this, window, cx| {
10580 let buffer = this.buffer.update(cx, |buffer, cx| {
10581 buffer.edit(edits, None, cx);
10582 buffer.snapshot(cx)
10583 });
10584
10585 // Recalculate offsets on newly edited buffer
10586 let new_selections = new_selections
10587 .iter()
10588 .map(|s| {
10589 let start_point = Point::new(s.start.0, 0);
10590 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10591 Selection {
10592 id: s.id,
10593 start: buffer.point_to_offset(start_point),
10594 end: buffer.point_to_offset(end_point),
10595 goal: s.goal,
10596 reversed: s.reversed,
10597 }
10598 })
10599 .collect();
10600
10601 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10602 s.select(new_selections);
10603 });
10604
10605 this.request_autoscroll(Autoscroll::fit(), cx);
10606 });
10607 }
10608
10609 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10610 self.manipulate_text(window, cx, |text| {
10611 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10612 if has_upper_case_characters {
10613 text.to_lowercase()
10614 } else {
10615 text.to_uppercase()
10616 }
10617 })
10618 }
10619
10620 pub fn convert_to_upper_case(
10621 &mut self,
10622 _: &ConvertToUpperCase,
10623 window: &mut Window,
10624 cx: &mut Context<Self>,
10625 ) {
10626 self.manipulate_text(window, cx, |text| text.to_uppercase())
10627 }
10628
10629 pub fn convert_to_lower_case(
10630 &mut self,
10631 _: &ConvertToLowerCase,
10632 window: &mut Window,
10633 cx: &mut Context<Self>,
10634 ) {
10635 self.manipulate_text(window, cx, |text| text.to_lowercase())
10636 }
10637
10638 pub fn convert_to_title_case(
10639 &mut self,
10640 _: &ConvertToTitleCase,
10641 window: &mut Window,
10642 cx: &mut Context<Self>,
10643 ) {
10644 self.manipulate_text(window, cx, |text| {
10645 text.split('\n')
10646 .map(|line| line.to_case(Case::Title))
10647 .join("\n")
10648 })
10649 }
10650
10651 pub fn convert_to_snake_case(
10652 &mut self,
10653 _: &ConvertToSnakeCase,
10654 window: &mut Window,
10655 cx: &mut Context<Self>,
10656 ) {
10657 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10658 }
10659
10660 pub fn convert_to_kebab_case(
10661 &mut self,
10662 _: &ConvertToKebabCase,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10667 }
10668
10669 pub fn convert_to_upper_camel_case(
10670 &mut self,
10671 _: &ConvertToUpperCamelCase,
10672 window: &mut Window,
10673 cx: &mut Context<Self>,
10674 ) {
10675 self.manipulate_text(window, cx, |text| {
10676 text.split('\n')
10677 .map(|line| line.to_case(Case::UpperCamel))
10678 .join("\n")
10679 })
10680 }
10681
10682 pub fn convert_to_lower_camel_case(
10683 &mut self,
10684 _: &ConvertToLowerCamelCase,
10685 window: &mut Window,
10686 cx: &mut Context<Self>,
10687 ) {
10688 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10689 }
10690
10691 pub fn convert_to_opposite_case(
10692 &mut self,
10693 _: &ConvertToOppositeCase,
10694 window: &mut Window,
10695 cx: &mut Context<Self>,
10696 ) {
10697 self.manipulate_text(window, cx, |text| {
10698 text.chars()
10699 .fold(String::with_capacity(text.len()), |mut t, c| {
10700 if c.is_uppercase() {
10701 t.extend(c.to_lowercase());
10702 } else {
10703 t.extend(c.to_uppercase());
10704 }
10705 t
10706 })
10707 })
10708 }
10709
10710 pub fn convert_to_rot13(
10711 &mut self,
10712 _: &ConvertToRot13,
10713 window: &mut Window,
10714 cx: &mut Context<Self>,
10715 ) {
10716 self.manipulate_text(window, cx, |text| {
10717 text.chars()
10718 .map(|c| match c {
10719 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10720 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10721 _ => c,
10722 })
10723 .collect()
10724 })
10725 }
10726
10727 pub fn convert_to_rot47(
10728 &mut self,
10729 _: &ConvertToRot47,
10730 window: &mut Window,
10731 cx: &mut Context<Self>,
10732 ) {
10733 self.manipulate_text(window, cx, |text| {
10734 text.chars()
10735 .map(|c| {
10736 let code_point = c as u32;
10737 if code_point >= 33 && code_point <= 126 {
10738 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10739 }
10740 c
10741 })
10742 .collect()
10743 })
10744 }
10745
10746 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10747 where
10748 Fn: FnMut(&str) -> String,
10749 {
10750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10751 let buffer = self.buffer.read(cx).snapshot(cx);
10752
10753 let mut new_selections = Vec::new();
10754 let mut edits = Vec::new();
10755 let mut selection_adjustment = 0i32;
10756
10757 for selection in self.selections.all::<usize>(cx) {
10758 let selection_is_empty = selection.is_empty();
10759
10760 let (start, end) = if selection_is_empty {
10761 let word_range = movement::surrounding_word(
10762 &display_map,
10763 selection.start.to_display_point(&display_map),
10764 );
10765 let start = word_range.start.to_offset(&display_map, Bias::Left);
10766 let end = word_range.end.to_offset(&display_map, Bias::Left);
10767 (start, end)
10768 } else {
10769 (selection.start, selection.end)
10770 };
10771
10772 let text = buffer.text_for_range(start..end).collect::<String>();
10773 let old_length = text.len() as i32;
10774 let text = callback(&text);
10775
10776 new_selections.push(Selection {
10777 start: (start as i32 - selection_adjustment) as usize,
10778 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10779 goal: SelectionGoal::None,
10780 ..selection
10781 });
10782
10783 selection_adjustment += old_length - text.len() as i32;
10784
10785 edits.push((start..end, text));
10786 }
10787
10788 self.transact(window, cx, |this, window, cx| {
10789 this.buffer.update(cx, |buffer, cx| {
10790 buffer.edit(edits, None, cx);
10791 });
10792
10793 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10794 s.select(new_selections);
10795 });
10796
10797 this.request_autoscroll(Autoscroll::fit(), cx);
10798 });
10799 }
10800
10801 pub fn move_selection_on_drop(
10802 &mut self,
10803 selection: &Selection<Anchor>,
10804 target: DisplayPoint,
10805 is_cut: bool,
10806 window: &mut Window,
10807 cx: &mut Context<Self>,
10808 ) {
10809 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10810 let buffer = &display_map.buffer_snapshot;
10811 let mut edits = Vec::new();
10812 let insert_point = display_map
10813 .clip_point(target, Bias::Left)
10814 .to_point(&display_map);
10815 let text = buffer
10816 .text_for_range(selection.start..selection.end)
10817 .collect::<String>();
10818 if is_cut {
10819 edits.push(((selection.start..selection.end), String::new()));
10820 }
10821 let insert_anchor = buffer.anchor_before(insert_point);
10822 edits.push(((insert_anchor..insert_anchor), text));
10823 let last_edit_start = insert_anchor.bias_left(buffer);
10824 let last_edit_end = insert_anchor.bias_right(buffer);
10825 self.transact(window, cx, |this, window, cx| {
10826 this.buffer.update(cx, |buffer, cx| {
10827 buffer.edit(edits, None, cx);
10828 });
10829 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10830 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10831 });
10832 });
10833 }
10834
10835 pub fn clear_selection_drag_state(&mut self) {
10836 self.selection_drag_state = SelectionDragState::None;
10837 }
10838
10839 pub fn duplicate(
10840 &mut self,
10841 upwards: bool,
10842 whole_lines: bool,
10843 window: &mut Window,
10844 cx: &mut Context<Self>,
10845 ) {
10846 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10847
10848 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10849 let buffer = &display_map.buffer_snapshot;
10850 let selections = self.selections.all::<Point>(cx);
10851
10852 let mut edits = Vec::new();
10853 let mut selections_iter = selections.iter().peekable();
10854 while let Some(selection) = selections_iter.next() {
10855 let mut rows = selection.spanned_rows(false, &display_map);
10856 // duplicate line-wise
10857 if whole_lines || selection.start == selection.end {
10858 // Avoid duplicating the same lines twice.
10859 while let Some(next_selection) = selections_iter.peek() {
10860 let next_rows = next_selection.spanned_rows(false, &display_map);
10861 if next_rows.start < rows.end {
10862 rows.end = next_rows.end;
10863 selections_iter.next().unwrap();
10864 } else {
10865 break;
10866 }
10867 }
10868
10869 // Copy the text from the selected row region and splice it either at the start
10870 // or end of the region.
10871 let start = Point::new(rows.start.0, 0);
10872 let end = Point::new(
10873 rows.end.previous_row().0,
10874 buffer.line_len(rows.end.previous_row()),
10875 );
10876 let text = buffer
10877 .text_for_range(start..end)
10878 .chain(Some("\n"))
10879 .collect::<String>();
10880 let insert_location = if upwards {
10881 Point::new(rows.end.0, 0)
10882 } else {
10883 start
10884 };
10885 edits.push((insert_location..insert_location, text));
10886 } else {
10887 // duplicate character-wise
10888 let start = selection.start;
10889 let end = selection.end;
10890 let text = buffer.text_for_range(start..end).collect::<String>();
10891 edits.push((selection.end..selection.end, text));
10892 }
10893 }
10894
10895 self.transact(window, cx, |this, _, cx| {
10896 this.buffer.update(cx, |buffer, cx| {
10897 buffer.edit(edits, None, cx);
10898 });
10899
10900 this.request_autoscroll(Autoscroll::fit(), cx);
10901 });
10902 }
10903
10904 pub fn duplicate_line_up(
10905 &mut self,
10906 _: &DuplicateLineUp,
10907 window: &mut Window,
10908 cx: &mut Context<Self>,
10909 ) {
10910 self.duplicate(true, true, window, cx);
10911 }
10912
10913 pub fn duplicate_line_down(
10914 &mut self,
10915 _: &DuplicateLineDown,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 ) {
10919 self.duplicate(false, true, window, cx);
10920 }
10921
10922 pub fn duplicate_selection(
10923 &mut self,
10924 _: &DuplicateSelection,
10925 window: &mut Window,
10926 cx: &mut Context<Self>,
10927 ) {
10928 self.duplicate(false, false, window, cx);
10929 }
10930
10931 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10933
10934 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10935 let buffer = self.buffer.read(cx).snapshot(cx);
10936
10937 let mut edits = Vec::new();
10938 let mut unfold_ranges = Vec::new();
10939 let mut refold_creases = Vec::new();
10940
10941 let selections = self.selections.all::<Point>(cx);
10942 let mut selections = selections.iter().peekable();
10943 let mut contiguous_row_selections = Vec::new();
10944 let mut new_selections = Vec::new();
10945
10946 while let Some(selection) = selections.next() {
10947 // Find all the selections that span a contiguous row range
10948 let (start_row, end_row) = consume_contiguous_rows(
10949 &mut contiguous_row_selections,
10950 selection,
10951 &display_map,
10952 &mut selections,
10953 );
10954
10955 // Move the text spanned by the row range to be before the line preceding the row range
10956 if start_row.0 > 0 {
10957 let range_to_move = Point::new(
10958 start_row.previous_row().0,
10959 buffer.line_len(start_row.previous_row()),
10960 )
10961 ..Point::new(
10962 end_row.previous_row().0,
10963 buffer.line_len(end_row.previous_row()),
10964 );
10965 let insertion_point = display_map
10966 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10967 .0;
10968
10969 // Don't move lines across excerpts
10970 if buffer
10971 .excerpt_containing(insertion_point..range_to_move.end)
10972 .is_some()
10973 {
10974 let text = buffer
10975 .text_for_range(range_to_move.clone())
10976 .flat_map(|s| s.chars())
10977 .skip(1)
10978 .chain(['\n'])
10979 .collect::<String>();
10980
10981 edits.push((
10982 buffer.anchor_after(range_to_move.start)
10983 ..buffer.anchor_before(range_to_move.end),
10984 String::new(),
10985 ));
10986 let insertion_anchor = buffer.anchor_after(insertion_point);
10987 edits.push((insertion_anchor..insertion_anchor, text));
10988
10989 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10990
10991 // Move selections up
10992 new_selections.extend(contiguous_row_selections.drain(..).map(
10993 |mut selection| {
10994 selection.start.row -= row_delta;
10995 selection.end.row -= row_delta;
10996 selection
10997 },
10998 ));
10999
11000 // Move folds up
11001 unfold_ranges.push(range_to_move.clone());
11002 for fold in display_map.folds_in_range(
11003 buffer.anchor_before(range_to_move.start)
11004 ..buffer.anchor_after(range_to_move.end),
11005 ) {
11006 let mut start = fold.range.start.to_point(&buffer);
11007 let mut end = fold.range.end.to_point(&buffer);
11008 start.row -= row_delta;
11009 end.row -= row_delta;
11010 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11011 }
11012 }
11013 }
11014
11015 // If we didn't move line(s), preserve the existing selections
11016 new_selections.append(&mut contiguous_row_selections);
11017 }
11018
11019 self.transact(window, cx, |this, window, cx| {
11020 this.unfold_ranges(&unfold_ranges, true, true, cx);
11021 this.buffer.update(cx, |buffer, cx| {
11022 for (range, text) in edits {
11023 buffer.edit([(range, text)], None, cx);
11024 }
11025 });
11026 this.fold_creases(refold_creases, true, window, cx);
11027 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11028 s.select(new_selections);
11029 })
11030 });
11031 }
11032
11033 pub fn move_line_down(
11034 &mut self,
11035 _: &MoveLineDown,
11036 window: &mut Window,
11037 cx: &mut Context<Self>,
11038 ) {
11039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11040
11041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11042 let buffer = self.buffer.read(cx).snapshot(cx);
11043
11044 let mut edits = Vec::new();
11045 let mut unfold_ranges = Vec::new();
11046 let mut refold_creases = Vec::new();
11047
11048 let selections = self.selections.all::<Point>(cx);
11049 let mut selections = selections.iter().peekable();
11050 let mut contiguous_row_selections = Vec::new();
11051 let mut new_selections = Vec::new();
11052
11053 while let Some(selection) = selections.next() {
11054 // Find all the selections that span a contiguous row range
11055 let (start_row, end_row) = consume_contiguous_rows(
11056 &mut contiguous_row_selections,
11057 selection,
11058 &display_map,
11059 &mut selections,
11060 );
11061
11062 // Move the text spanned by the row range to be after the last line of the row range
11063 if end_row.0 <= buffer.max_point().row {
11064 let range_to_move =
11065 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11066 let insertion_point = display_map
11067 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11068 .0;
11069
11070 // Don't move lines across excerpt boundaries
11071 if buffer
11072 .excerpt_containing(range_to_move.start..insertion_point)
11073 .is_some()
11074 {
11075 let mut text = String::from("\n");
11076 text.extend(buffer.text_for_range(range_to_move.clone()));
11077 text.pop(); // Drop trailing newline
11078 edits.push((
11079 buffer.anchor_after(range_to_move.start)
11080 ..buffer.anchor_before(range_to_move.end),
11081 String::new(),
11082 ));
11083 let insertion_anchor = buffer.anchor_after(insertion_point);
11084 edits.push((insertion_anchor..insertion_anchor, text));
11085
11086 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11087
11088 // Move selections down
11089 new_selections.extend(contiguous_row_selections.drain(..).map(
11090 |mut selection| {
11091 selection.start.row += row_delta;
11092 selection.end.row += row_delta;
11093 selection
11094 },
11095 ));
11096
11097 // Move folds down
11098 unfold_ranges.push(range_to_move.clone());
11099 for fold in display_map.folds_in_range(
11100 buffer.anchor_before(range_to_move.start)
11101 ..buffer.anchor_after(range_to_move.end),
11102 ) {
11103 let mut start = fold.range.start.to_point(&buffer);
11104 let mut end = fold.range.end.to_point(&buffer);
11105 start.row += row_delta;
11106 end.row += row_delta;
11107 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11108 }
11109 }
11110 }
11111
11112 // If we didn't move line(s), preserve the existing selections
11113 new_selections.append(&mut contiguous_row_selections);
11114 }
11115
11116 self.transact(window, cx, |this, window, cx| {
11117 this.unfold_ranges(&unfold_ranges, true, true, cx);
11118 this.buffer.update(cx, |buffer, cx| {
11119 for (range, text) in edits {
11120 buffer.edit([(range, text)], None, cx);
11121 }
11122 });
11123 this.fold_creases(refold_creases, true, window, cx);
11124 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11125 s.select(new_selections)
11126 });
11127 });
11128 }
11129
11130 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11131 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11132 let text_layout_details = &self.text_layout_details(window);
11133 self.transact(window, cx, |this, window, cx| {
11134 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11135 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11136 s.move_with(|display_map, selection| {
11137 if !selection.is_empty() {
11138 return;
11139 }
11140
11141 let mut head = selection.head();
11142 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11143 if head.column() == display_map.line_len(head.row()) {
11144 transpose_offset = display_map
11145 .buffer_snapshot
11146 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11147 }
11148
11149 if transpose_offset == 0 {
11150 return;
11151 }
11152
11153 *head.column_mut() += 1;
11154 head = display_map.clip_point(head, Bias::Right);
11155 let goal = SelectionGoal::HorizontalPosition(
11156 display_map
11157 .x_for_display_point(head, text_layout_details)
11158 .into(),
11159 );
11160 selection.collapse_to(head, goal);
11161
11162 let transpose_start = display_map
11163 .buffer_snapshot
11164 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11165 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11166 let transpose_end = display_map
11167 .buffer_snapshot
11168 .clip_offset(transpose_offset + 1, Bias::Right);
11169 if let Some(ch) =
11170 display_map.buffer_snapshot.chars_at(transpose_start).next()
11171 {
11172 edits.push((transpose_start..transpose_offset, String::new()));
11173 edits.push((transpose_end..transpose_end, ch.to_string()));
11174 }
11175 }
11176 });
11177 edits
11178 });
11179 this.buffer
11180 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11181 let selections = this.selections.all::<usize>(cx);
11182 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11183 s.select(selections);
11184 });
11185 });
11186 }
11187
11188 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11189 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11190 self.rewrap_impl(RewrapOptions::default(), cx)
11191 }
11192
11193 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11194 let buffer = self.buffer.read(cx).snapshot(cx);
11195 let selections = self.selections.all::<Point>(cx);
11196
11197 // Shrink and split selections to respect paragraph boundaries.
11198 let ranges = selections.into_iter().flat_map(|selection| {
11199 let language_settings = buffer.language_settings_at(selection.head(), cx);
11200 let language_scope = buffer.language_scope_at(selection.head());
11201
11202 let Some(start_row) = (selection.start.row..=selection.end.row)
11203 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11204 else {
11205 return vec![];
11206 };
11207 let Some(end_row) = (selection.start.row..=selection.end.row)
11208 .rev()
11209 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11210 else {
11211 return vec![];
11212 };
11213
11214 let mut row = start_row;
11215 let mut ranges = Vec::new();
11216 while let Some(blank_row) =
11217 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11218 {
11219 let next_paragraph_start = (blank_row + 1..=end_row)
11220 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11221 .unwrap();
11222 ranges.push((
11223 language_settings.clone(),
11224 language_scope.clone(),
11225 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11226 ));
11227 row = next_paragraph_start;
11228 }
11229 ranges.push((
11230 language_settings.clone(),
11231 language_scope.clone(),
11232 Point::new(row, 0)..Point::new(end_row, 0),
11233 ));
11234
11235 ranges
11236 });
11237
11238 let mut edits = Vec::new();
11239 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11240
11241 for (language_settings, language_scope, range) in ranges {
11242 let mut start_row = range.start.row;
11243 let mut end_row = range.end.row;
11244
11245 // Skip selections that overlap with a range that has already been rewrapped.
11246 let selection_range = start_row..end_row;
11247 if rewrapped_row_ranges
11248 .iter()
11249 .any(|range| range.overlaps(&selection_range))
11250 {
11251 continue;
11252 }
11253
11254 let tab_size = language_settings.tab_size;
11255
11256 // Since not all lines in the selection may be at the same indent
11257 // level, choose the indent size that is the most common between all
11258 // of the lines.
11259 //
11260 // If there is a tie, we use the deepest indent.
11261 let (indent_size, indent_end) = {
11262 let mut indent_size_occurrences = HashMap::default();
11263 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11264
11265 for row in start_row..=end_row {
11266 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11267 rows_by_indent_size.entry(indent).or_default().push(row);
11268 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11269 }
11270
11271 let indent_size = indent_size_occurrences
11272 .into_iter()
11273 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11274 .map(|(indent, _)| indent)
11275 .unwrap_or_default();
11276 let row = rows_by_indent_size[&indent_size][0];
11277 let indent_end = Point::new(row, indent_size.len);
11278
11279 (indent_size, indent_end)
11280 };
11281
11282 let mut line_prefix = indent_size.chars().collect::<String>();
11283
11284 let mut inside_comment = false;
11285 if let Some(comment_prefix) = language_scope.and_then(|language| {
11286 language
11287 .line_comment_prefixes()
11288 .iter()
11289 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11290 .cloned()
11291 }) {
11292 line_prefix.push_str(&comment_prefix);
11293 inside_comment = true;
11294 }
11295
11296 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11297 RewrapBehavior::InComments => inside_comment,
11298 RewrapBehavior::InSelections => !range.is_empty(),
11299 RewrapBehavior::Anywhere => true,
11300 };
11301
11302 let should_rewrap = options.override_language_settings
11303 || allow_rewrap_based_on_language
11304 || self.hard_wrap.is_some();
11305 if !should_rewrap {
11306 continue;
11307 }
11308
11309 if range.is_empty() {
11310 'expand_upwards: while start_row > 0 {
11311 let prev_row = start_row - 1;
11312 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11313 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11314 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11315 {
11316 start_row = prev_row;
11317 } else {
11318 break 'expand_upwards;
11319 }
11320 }
11321
11322 'expand_downwards: while end_row < buffer.max_point().row {
11323 let next_row = end_row + 1;
11324 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11325 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11326 && !buffer.is_line_blank(MultiBufferRow(next_row))
11327 {
11328 end_row = next_row;
11329 } else {
11330 break 'expand_downwards;
11331 }
11332 }
11333 }
11334
11335 let start = Point::new(start_row, 0);
11336 let start_offset = start.to_offset(&buffer);
11337 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11338 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11339 let Some(lines_without_prefixes) = selection_text
11340 .lines()
11341 .map(|line| {
11342 line.strip_prefix(&line_prefix)
11343 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11344 .with_context(|| {
11345 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11346 })
11347 })
11348 .collect::<Result<Vec<_>, _>>()
11349 .log_err()
11350 else {
11351 continue;
11352 };
11353
11354 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11355 buffer
11356 .language_settings_at(Point::new(start_row, 0), cx)
11357 .preferred_line_length as usize
11358 });
11359 let wrapped_text = wrap_with_prefix(
11360 line_prefix,
11361 lines_without_prefixes.join("\n"),
11362 wrap_column,
11363 tab_size,
11364 options.preserve_existing_whitespace,
11365 );
11366
11367 // TODO: should always use char-based diff while still supporting cursor behavior that
11368 // matches vim.
11369 let mut diff_options = DiffOptions::default();
11370 if options.override_language_settings {
11371 diff_options.max_word_diff_len = 0;
11372 diff_options.max_word_diff_line_count = 0;
11373 } else {
11374 diff_options.max_word_diff_len = usize::MAX;
11375 diff_options.max_word_diff_line_count = usize::MAX;
11376 }
11377
11378 for (old_range, new_text) in
11379 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11380 {
11381 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11382 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11383 edits.push((edit_start..edit_end, new_text));
11384 }
11385
11386 rewrapped_row_ranges.push(start_row..=end_row);
11387 }
11388
11389 self.buffer
11390 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11391 }
11392
11393 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11394 let mut text = String::new();
11395 let buffer = self.buffer.read(cx).snapshot(cx);
11396 let mut selections = self.selections.all::<Point>(cx);
11397 let mut clipboard_selections = Vec::with_capacity(selections.len());
11398 {
11399 let max_point = buffer.max_point();
11400 let mut is_first = true;
11401 for selection in &mut selections {
11402 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11403 if is_entire_line {
11404 selection.start = Point::new(selection.start.row, 0);
11405 if !selection.is_empty() && selection.end.column == 0 {
11406 selection.end = cmp::min(max_point, selection.end);
11407 } else {
11408 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11409 }
11410 selection.goal = SelectionGoal::None;
11411 }
11412 if is_first {
11413 is_first = false;
11414 } else {
11415 text += "\n";
11416 }
11417 let mut len = 0;
11418 for chunk in buffer.text_for_range(selection.start..selection.end) {
11419 text.push_str(chunk);
11420 len += chunk.len();
11421 }
11422 clipboard_selections.push(ClipboardSelection {
11423 len,
11424 is_entire_line,
11425 first_line_indent: buffer
11426 .indent_size_for_line(MultiBufferRow(selection.start.row))
11427 .len,
11428 });
11429 }
11430 }
11431
11432 self.transact(window, cx, |this, window, cx| {
11433 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11434 s.select(selections);
11435 });
11436 this.insert("", window, cx);
11437 });
11438 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11439 }
11440
11441 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11443 let item = self.cut_common(window, cx);
11444 cx.write_to_clipboard(item);
11445 }
11446
11447 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11448 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11449 self.change_selections(None, window, cx, |s| {
11450 s.move_with(|snapshot, sel| {
11451 if sel.is_empty() {
11452 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11453 }
11454 });
11455 });
11456 let item = self.cut_common(window, cx);
11457 cx.set_global(KillRing(item))
11458 }
11459
11460 pub fn kill_ring_yank(
11461 &mut self,
11462 _: &KillRingYank,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11467 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11468 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11469 (kill_ring.text().to_string(), kill_ring.metadata_json())
11470 } else {
11471 return;
11472 }
11473 } else {
11474 return;
11475 };
11476 self.do_paste(&text, metadata, false, window, cx);
11477 }
11478
11479 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11480 self.do_copy(true, cx);
11481 }
11482
11483 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11484 self.do_copy(false, cx);
11485 }
11486
11487 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11488 let selections = self.selections.all::<Point>(cx);
11489 let buffer = self.buffer.read(cx).read(cx);
11490 let mut text = String::new();
11491
11492 let mut clipboard_selections = Vec::with_capacity(selections.len());
11493 {
11494 let max_point = buffer.max_point();
11495 let mut is_first = true;
11496 for selection in &selections {
11497 let mut start = selection.start;
11498 let mut end = selection.end;
11499 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11500 if is_entire_line {
11501 start = Point::new(start.row, 0);
11502 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11503 }
11504
11505 let mut trimmed_selections = Vec::new();
11506 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11507 let row = MultiBufferRow(start.row);
11508 let first_indent = buffer.indent_size_for_line(row);
11509 if first_indent.len == 0 || start.column > first_indent.len {
11510 trimmed_selections.push(start..end);
11511 } else {
11512 trimmed_selections.push(
11513 Point::new(row.0, first_indent.len)
11514 ..Point::new(row.0, buffer.line_len(row)),
11515 );
11516 for row in start.row + 1..=end.row {
11517 let mut line_len = buffer.line_len(MultiBufferRow(row));
11518 if row == end.row {
11519 line_len = end.column;
11520 }
11521 if line_len == 0 {
11522 trimmed_selections
11523 .push(Point::new(row, 0)..Point::new(row, line_len));
11524 continue;
11525 }
11526 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11527 if row_indent_size.len >= first_indent.len {
11528 trimmed_selections.push(
11529 Point::new(row, first_indent.len)..Point::new(row, line_len),
11530 );
11531 } else {
11532 trimmed_selections.clear();
11533 trimmed_selections.push(start..end);
11534 break;
11535 }
11536 }
11537 }
11538 } else {
11539 trimmed_selections.push(start..end);
11540 }
11541
11542 for trimmed_range in trimmed_selections {
11543 if is_first {
11544 is_first = false;
11545 } else {
11546 text += "\n";
11547 }
11548 let mut len = 0;
11549 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11550 text.push_str(chunk);
11551 len += chunk.len();
11552 }
11553 clipboard_selections.push(ClipboardSelection {
11554 len,
11555 is_entire_line,
11556 first_line_indent: buffer
11557 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11558 .len,
11559 });
11560 }
11561 }
11562 }
11563
11564 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11565 text,
11566 clipboard_selections,
11567 ));
11568 }
11569
11570 pub fn do_paste(
11571 &mut self,
11572 text: &String,
11573 clipboard_selections: Option<Vec<ClipboardSelection>>,
11574 handle_entire_lines: bool,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 if self.read_only(cx) {
11579 return;
11580 }
11581
11582 let clipboard_text = Cow::Borrowed(text);
11583
11584 self.transact(window, cx, |this, window, cx| {
11585 if let Some(mut clipboard_selections) = clipboard_selections {
11586 let old_selections = this.selections.all::<usize>(cx);
11587 let all_selections_were_entire_line =
11588 clipboard_selections.iter().all(|s| s.is_entire_line);
11589 let first_selection_indent_column =
11590 clipboard_selections.first().map(|s| s.first_line_indent);
11591 if clipboard_selections.len() != old_selections.len() {
11592 clipboard_selections.drain(..);
11593 }
11594 let cursor_offset = this.selections.last::<usize>(cx).head();
11595 let mut auto_indent_on_paste = true;
11596
11597 this.buffer.update(cx, |buffer, cx| {
11598 let snapshot = buffer.read(cx);
11599 auto_indent_on_paste = snapshot
11600 .language_settings_at(cursor_offset, cx)
11601 .auto_indent_on_paste;
11602
11603 let mut start_offset = 0;
11604 let mut edits = Vec::new();
11605 let mut original_indent_columns = Vec::new();
11606 for (ix, selection) in old_selections.iter().enumerate() {
11607 let to_insert;
11608 let entire_line;
11609 let original_indent_column;
11610 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11611 let end_offset = start_offset + clipboard_selection.len;
11612 to_insert = &clipboard_text[start_offset..end_offset];
11613 entire_line = clipboard_selection.is_entire_line;
11614 start_offset = end_offset + 1;
11615 original_indent_column = Some(clipboard_selection.first_line_indent);
11616 } else {
11617 to_insert = clipboard_text.as_str();
11618 entire_line = all_selections_were_entire_line;
11619 original_indent_column = first_selection_indent_column
11620 }
11621
11622 // If the corresponding selection was empty when this slice of the
11623 // clipboard text was written, then the entire line containing the
11624 // selection was copied. If this selection is also currently empty,
11625 // then paste the line before the current line of the buffer.
11626 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11627 let column = selection.start.to_point(&snapshot).column as usize;
11628 let line_start = selection.start - column;
11629 line_start..line_start
11630 } else {
11631 selection.range()
11632 };
11633
11634 edits.push((range, to_insert));
11635 original_indent_columns.push(original_indent_column);
11636 }
11637 drop(snapshot);
11638
11639 buffer.edit(
11640 edits,
11641 if auto_indent_on_paste {
11642 Some(AutoindentMode::Block {
11643 original_indent_columns,
11644 })
11645 } else {
11646 None
11647 },
11648 cx,
11649 );
11650 });
11651
11652 let selections = this.selections.all::<usize>(cx);
11653 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11654 s.select(selections)
11655 });
11656 } else {
11657 this.insert(&clipboard_text, window, cx);
11658 }
11659 });
11660 }
11661
11662 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11664 if let Some(item) = cx.read_from_clipboard() {
11665 let entries = item.entries();
11666
11667 match entries.first() {
11668 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11669 // of all the pasted entries.
11670 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11671 .do_paste(
11672 clipboard_string.text(),
11673 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11674 true,
11675 window,
11676 cx,
11677 ),
11678 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11679 }
11680 }
11681 }
11682
11683 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11684 if self.read_only(cx) {
11685 return;
11686 }
11687
11688 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11689
11690 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11691 if let Some((selections, _)) =
11692 self.selection_history.transaction(transaction_id).cloned()
11693 {
11694 self.change_selections(None, window, cx, |s| {
11695 s.select_anchors(selections.to_vec());
11696 });
11697 } else {
11698 log::error!(
11699 "No entry in selection_history found for undo. \
11700 This may correspond to a bug where undo does not update the selection. \
11701 If this is occurring, please add details to \
11702 https://github.com/zed-industries/zed/issues/22692"
11703 );
11704 }
11705 self.request_autoscroll(Autoscroll::fit(), cx);
11706 self.unmark_text(window, cx);
11707 self.refresh_inline_completion(true, false, window, cx);
11708 cx.emit(EditorEvent::Edited { transaction_id });
11709 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11710 }
11711 }
11712
11713 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11714 if self.read_only(cx) {
11715 return;
11716 }
11717
11718 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11719
11720 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11721 if let Some((_, Some(selections))) =
11722 self.selection_history.transaction(transaction_id).cloned()
11723 {
11724 self.change_selections(None, window, cx, |s| {
11725 s.select_anchors(selections.to_vec());
11726 });
11727 } else {
11728 log::error!(
11729 "No entry in selection_history found for redo. \
11730 This may correspond to a bug where undo does not update the selection. \
11731 If this is occurring, please add details to \
11732 https://github.com/zed-industries/zed/issues/22692"
11733 );
11734 }
11735 self.request_autoscroll(Autoscroll::fit(), cx);
11736 self.unmark_text(window, cx);
11737 self.refresh_inline_completion(true, false, window, cx);
11738 cx.emit(EditorEvent::Edited { transaction_id });
11739 }
11740 }
11741
11742 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11743 self.buffer
11744 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11745 }
11746
11747 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11748 self.buffer
11749 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11750 }
11751
11752 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11754 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11755 s.move_with(|map, selection| {
11756 let cursor = if selection.is_empty() {
11757 movement::left(map, selection.start)
11758 } else {
11759 selection.start
11760 };
11761 selection.collapse_to(cursor, SelectionGoal::None);
11762 });
11763 })
11764 }
11765
11766 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11769 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11770 })
11771 }
11772
11773 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11774 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11775 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11776 s.move_with(|map, selection| {
11777 let cursor = if selection.is_empty() {
11778 movement::right(map, selection.end)
11779 } else {
11780 selection.end
11781 };
11782 selection.collapse_to(cursor, SelectionGoal::None)
11783 });
11784 })
11785 }
11786
11787 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11789 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11790 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11791 })
11792 }
11793
11794 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11795 if self.take_rename(true, window, cx).is_some() {
11796 return;
11797 }
11798
11799 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11800 cx.propagate();
11801 return;
11802 }
11803
11804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11805
11806 let text_layout_details = &self.text_layout_details(window);
11807 let selection_count = self.selections.count();
11808 let first_selection = self.selections.first_anchor();
11809
11810 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11811 s.move_with(|map, selection| {
11812 if !selection.is_empty() {
11813 selection.goal = SelectionGoal::None;
11814 }
11815 let (cursor, goal) = movement::up(
11816 map,
11817 selection.start,
11818 selection.goal,
11819 false,
11820 text_layout_details,
11821 );
11822 selection.collapse_to(cursor, goal);
11823 });
11824 });
11825
11826 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11827 {
11828 cx.propagate();
11829 }
11830 }
11831
11832 pub fn move_up_by_lines(
11833 &mut self,
11834 action: &MoveUpByLines,
11835 window: &mut Window,
11836 cx: &mut Context<Self>,
11837 ) {
11838 if self.take_rename(true, window, cx).is_some() {
11839 return;
11840 }
11841
11842 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11843 cx.propagate();
11844 return;
11845 }
11846
11847 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11848
11849 let text_layout_details = &self.text_layout_details(window);
11850
11851 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11852 s.move_with(|map, selection| {
11853 if !selection.is_empty() {
11854 selection.goal = SelectionGoal::None;
11855 }
11856 let (cursor, goal) = movement::up_by_rows(
11857 map,
11858 selection.start,
11859 action.lines,
11860 selection.goal,
11861 false,
11862 text_layout_details,
11863 );
11864 selection.collapse_to(cursor, goal);
11865 });
11866 })
11867 }
11868
11869 pub fn move_down_by_lines(
11870 &mut self,
11871 action: &MoveDownByLines,
11872 window: &mut Window,
11873 cx: &mut Context<Self>,
11874 ) {
11875 if self.take_rename(true, window, cx).is_some() {
11876 return;
11877 }
11878
11879 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11880 cx.propagate();
11881 return;
11882 }
11883
11884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11885
11886 let text_layout_details = &self.text_layout_details(window);
11887
11888 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11889 s.move_with(|map, selection| {
11890 if !selection.is_empty() {
11891 selection.goal = SelectionGoal::None;
11892 }
11893 let (cursor, goal) = movement::down_by_rows(
11894 map,
11895 selection.start,
11896 action.lines,
11897 selection.goal,
11898 false,
11899 text_layout_details,
11900 );
11901 selection.collapse_to(cursor, goal);
11902 });
11903 })
11904 }
11905
11906 pub fn select_down_by_lines(
11907 &mut self,
11908 action: &SelectDownByLines,
11909 window: &mut Window,
11910 cx: &mut Context<Self>,
11911 ) {
11912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11913 let text_layout_details = &self.text_layout_details(window);
11914 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11915 s.move_heads_with(|map, head, goal| {
11916 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11917 })
11918 })
11919 }
11920
11921 pub fn select_up_by_lines(
11922 &mut self,
11923 action: &SelectUpByLines,
11924 window: &mut Window,
11925 cx: &mut Context<Self>,
11926 ) {
11927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11928 let text_layout_details = &self.text_layout_details(window);
11929 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11930 s.move_heads_with(|map, head, goal| {
11931 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11932 })
11933 })
11934 }
11935
11936 pub fn select_page_up(
11937 &mut self,
11938 _: &SelectPageUp,
11939 window: &mut Window,
11940 cx: &mut Context<Self>,
11941 ) {
11942 let Some(row_count) = self.visible_row_count() else {
11943 return;
11944 };
11945
11946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11947
11948 let text_layout_details = &self.text_layout_details(window);
11949
11950 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11951 s.move_heads_with(|map, head, goal| {
11952 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11953 })
11954 })
11955 }
11956
11957 pub fn move_page_up(
11958 &mut self,
11959 action: &MovePageUp,
11960 window: &mut Window,
11961 cx: &mut Context<Self>,
11962 ) {
11963 if self.take_rename(true, window, cx).is_some() {
11964 return;
11965 }
11966
11967 if self
11968 .context_menu
11969 .borrow_mut()
11970 .as_mut()
11971 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11972 .unwrap_or(false)
11973 {
11974 return;
11975 }
11976
11977 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11978 cx.propagate();
11979 return;
11980 }
11981
11982 let Some(row_count) = self.visible_row_count() else {
11983 return;
11984 };
11985
11986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11987
11988 let autoscroll = if action.center_cursor {
11989 Autoscroll::center()
11990 } else {
11991 Autoscroll::fit()
11992 };
11993
11994 let text_layout_details = &self.text_layout_details(window);
11995
11996 self.change_selections(Some(autoscroll), window, cx, |s| {
11997 s.move_with(|map, selection| {
11998 if !selection.is_empty() {
11999 selection.goal = SelectionGoal::None;
12000 }
12001 let (cursor, goal) = movement::up_by_rows(
12002 map,
12003 selection.end,
12004 row_count,
12005 selection.goal,
12006 false,
12007 text_layout_details,
12008 );
12009 selection.collapse_to(cursor, goal);
12010 });
12011 });
12012 }
12013
12014 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12016 let text_layout_details = &self.text_layout_details(window);
12017 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12018 s.move_heads_with(|map, head, goal| {
12019 movement::up(map, head, goal, false, text_layout_details)
12020 })
12021 })
12022 }
12023
12024 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12025 self.take_rename(true, window, cx);
12026
12027 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12028 cx.propagate();
12029 return;
12030 }
12031
12032 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12033
12034 let text_layout_details = &self.text_layout_details(window);
12035 let selection_count = self.selections.count();
12036 let first_selection = self.selections.first_anchor();
12037
12038 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12039 s.move_with(|map, selection| {
12040 if !selection.is_empty() {
12041 selection.goal = SelectionGoal::None;
12042 }
12043 let (cursor, goal) = movement::down(
12044 map,
12045 selection.end,
12046 selection.goal,
12047 false,
12048 text_layout_details,
12049 );
12050 selection.collapse_to(cursor, goal);
12051 });
12052 });
12053
12054 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12055 {
12056 cx.propagate();
12057 }
12058 }
12059
12060 pub fn select_page_down(
12061 &mut self,
12062 _: &SelectPageDown,
12063 window: &mut Window,
12064 cx: &mut Context<Self>,
12065 ) {
12066 let Some(row_count) = self.visible_row_count() else {
12067 return;
12068 };
12069
12070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12071
12072 let text_layout_details = &self.text_layout_details(window);
12073
12074 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12075 s.move_heads_with(|map, head, goal| {
12076 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12077 })
12078 })
12079 }
12080
12081 pub fn move_page_down(
12082 &mut self,
12083 action: &MovePageDown,
12084 window: &mut Window,
12085 cx: &mut Context<Self>,
12086 ) {
12087 if self.take_rename(true, window, cx).is_some() {
12088 return;
12089 }
12090
12091 if self
12092 .context_menu
12093 .borrow_mut()
12094 .as_mut()
12095 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12096 .unwrap_or(false)
12097 {
12098 return;
12099 }
12100
12101 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12102 cx.propagate();
12103 return;
12104 }
12105
12106 let Some(row_count) = self.visible_row_count() else {
12107 return;
12108 };
12109
12110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12111
12112 let autoscroll = if action.center_cursor {
12113 Autoscroll::center()
12114 } else {
12115 Autoscroll::fit()
12116 };
12117
12118 let text_layout_details = &self.text_layout_details(window);
12119 self.change_selections(Some(autoscroll), window, cx, |s| {
12120 s.move_with(|map, selection| {
12121 if !selection.is_empty() {
12122 selection.goal = SelectionGoal::None;
12123 }
12124 let (cursor, goal) = movement::down_by_rows(
12125 map,
12126 selection.end,
12127 row_count,
12128 selection.goal,
12129 false,
12130 text_layout_details,
12131 );
12132 selection.collapse_to(cursor, goal);
12133 });
12134 });
12135 }
12136
12137 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12138 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12139 let text_layout_details = &self.text_layout_details(window);
12140 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12141 s.move_heads_with(|map, head, goal| {
12142 movement::down(map, head, goal, false, text_layout_details)
12143 })
12144 });
12145 }
12146
12147 pub fn context_menu_first(
12148 &mut self,
12149 _: &ContextMenuFirst,
12150 window: &mut Window,
12151 cx: &mut Context<Self>,
12152 ) {
12153 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12154 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12155 }
12156 }
12157
12158 pub fn context_menu_prev(
12159 &mut self,
12160 _: &ContextMenuPrevious,
12161 window: &mut Window,
12162 cx: &mut Context<Self>,
12163 ) {
12164 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12165 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12166 }
12167 }
12168
12169 pub fn context_menu_next(
12170 &mut self,
12171 _: &ContextMenuNext,
12172 window: &mut Window,
12173 cx: &mut Context<Self>,
12174 ) {
12175 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12176 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12177 }
12178 }
12179
12180 pub fn context_menu_last(
12181 &mut self,
12182 _: &ContextMenuLast,
12183 window: &mut Window,
12184 cx: &mut Context<Self>,
12185 ) {
12186 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12187 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12188 }
12189 }
12190
12191 pub fn move_to_previous_word_start(
12192 &mut self,
12193 _: &MoveToPreviousWordStart,
12194 window: &mut Window,
12195 cx: &mut Context<Self>,
12196 ) {
12197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12198 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12199 s.move_cursors_with(|map, head, _| {
12200 (
12201 movement::previous_word_start(map, head),
12202 SelectionGoal::None,
12203 )
12204 });
12205 })
12206 }
12207
12208 pub fn move_to_previous_subword_start(
12209 &mut self,
12210 _: &MoveToPreviousSubwordStart,
12211 window: &mut Window,
12212 cx: &mut Context<Self>,
12213 ) {
12214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12215 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12216 s.move_cursors_with(|map, head, _| {
12217 (
12218 movement::previous_subword_start(map, head),
12219 SelectionGoal::None,
12220 )
12221 });
12222 })
12223 }
12224
12225 pub fn select_to_previous_word_start(
12226 &mut self,
12227 _: &SelectToPreviousWordStart,
12228 window: &mut Window,
12229 cx: &mut Context<Self>,
12230 ) {
12231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12232 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12233 s.move_heads_with(|map, head, _| {
12234 (
12235 movement::previous_word_start(map, head),
12236 SelectionGoal::None,
12237 )
12238 });
12239 })
12240 }
12241
12242 pub fn select_to_previous_subword_start(
12243 &mut self,
12244 _: &SelectToPreviousSubwordStart,
12245 window: &mut Window,
12246 cx: &mut Context<Self>,
12247 ) {
12248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12250 s.move_heads_with(|map, head, _| {
12251 (
12252 movement::previous_subword_start(map, head),
12253 SelectionGoal::None,
12254 )
12255 });
12256 })
12257 }
12258
12259 pub fn delete_to_previous_word_start(
12260 &mut self,
12261 action: &DeleteToPreviousWordStart,
12262 window: &mut Window,
12263 cx: &mut Context<Self>,
12264 ) {
12265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12266 self.transact(window, cx, |this, window, cx| {
12267 this.select_autoclose_pair(window, cx);
12268 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12269 s.move_with(|map, selection| {
12270 if selection.is_empty() {
12271 let cursor = if action.ignore_newlines {
12272 movement::previous_word_start(map, selection.head())
12273 } else {
12274 movement::previous_word_start_or_newline(map, selection.head())
12275 };
12276 selection.set_head(cursor, SelectionGoal::None);
12277 }
12278 });
12279 });
12280 this.insert("", window, cx);
12281 });
12282 }
12283
12284 pub fn delete_to_previous_subword_start(
12285 &mut self,
12286 _: &DeleteToPreviousSubwordStart,
12287 window: &mut Window,
12288 cx: &mut Context<Self>,
12289 ) {
12290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12291 self.transact(window, cx, |this, window, cx| {
12292 this.select_autoclose_pair(window, cx);
12293 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12294 s.move_with(|map, selection| {
12295 if selection.is_empty() {
12296 let cursor = movement::previous_subword_start(map, selection.head());
12297 selection.set_head(cursor, SelectionGoal::None);
12298 }
12299 });
12300 });
12301 this.insert("", window, cx);
12302 });
12303 }
12304
12305 pub fn move_to_next_word_end(
12306 &mut self,
12307 _: &MoveToNextWordEnd,
12308 window: &mut Window,
12309 cx: &mut Context<Self>,
12310 ) {
12311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12312 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12313 s.move_cursors_with(|map, head, _| {
12314 (movement::next_word_end(map, head), SelectionGoal::None)
12315 });
12316 })
12317 }
12318
12319 pub fn move_to_next_subword_end(
12320 &mut self,
12321 _: &MoveToNextSubwordEnd,
12322 window: &mut Window,
12323 cx: &mut Context<Self>,
12324 ) {
12325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12326 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12327 s.move_cursors_with(|map, head, _| {
12328 (movement::next_subword_end(map, head), SelectionGoal::None)
12329 });
12330 })
12331 }
12332
12333 pub fn select_to_next_word_end(
12334 &mut self,
12335 _: &SelectToNextWordEnd,
12336 window: &mut Window,
12337 cx: &mut Context<Self>,
12338 ) {
12339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12340 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12341 s.move_heads_with(|map, head, _| {
12342 (movement::next_word_end(map, head), SelectionGoal::None)
12343 });
12344 })
12345 }
12346
12347 pub fn select_to_next_subword_end(
12348 &mut self,
12349 _: &SelectToNextSubwordEnd,
12350 window: &mut Window,
12351 cx: &mut Context<Self>,
12352 ) {
12353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12355 s.move_heads_with(|map, head, _| {
12356 (movement::next_subword_end(map, head), SelectionGoal::None)
12357 });
12358 })
12359 }
12360
12361 pub fn delete_to_next_word_end(
12362 &mut self,
12363 action: &DeleteToNextWordEnd,
12364 window: &mut Window,
12365 cx: &mut Context<Self>,
12366 ) {
12367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12368 self.transact(window, cx, |this, window, cx| {
12369 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12370 s.move_with(|map, selection| {
12371 if selection.is_empty() {
12372 let cursor = if action.ignore_newlines {
12373 movement::next_word_end(map, selection.head())
12374 } else {
12375 movement::next_word_end_or_newline(map, selection.head())
12376 };
12377 selection.set_head(cursor, SelectionGoal::None);
12378 }
12379 });
12380 });
12381 this.insert("", window, cx);
12382 });
12383 }
12384
12385 pub fn delete_to_next_subword_end(
12386 &mut self,
12387 _: &DeleteToNextSubwordEnd,
12388 window: &mut Window,
12389 cx: &mut Context<Self>,
12390 ) {
12391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12392 self.transact(window, cx, |this, window, cx| {
12393 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12394 s.move_with(|map, selection| {
12395 if selection.is_empty() {
12396 let cursor = movement::next_subword_end(map, selection.head());
12397 selection.set_head(cursor, SelectionGoal::None);
12398 }
12399 });
12400 });
12401 this.insert("", window, cx);
12402 });
12403 }
12404
12405 pub fn move_to_beginning_of_line(
12406 &mut self,
12407 action: &MoveToBeginningOfLine,
12408 window: &mut Window,
12409 cx: &mut Context<Self>,
12410 ) {
12411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12412 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12413 s.move_cursors_with(|map, head, _| {
12414 (
12415 movement::indented_line_beginning(
12416 map,
12417 head,
12418 action.stop_at_soft_wraps,
12419 action.stop_at_indent,
12420 ),
12421 SelectionGoal::None,
12422 )
12423 });
12424 })
12425 }
12426
12427 pub fn select_to_beginning_of_line(
12428 &mut self,
12429 action: &SelectToBeginningOfLine,
12430 window: &mut Window,
12431 cx: &mut Context<Self>,
12432 ) {
12433 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12434 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12435 s.move_heads_with(|map, head, _| {
12436 (
12437 movement::indented_line_beginning(
12438 map,
12439 head,
12440 action.stop_at_soft_wraps,
12441 action.stop_at_indent,
12442 ),
12443 SelectionGoal::None,
12444 )
12445 });
12446 });
12447 }
12448
12449 pub fn delete_to_beginning_of_line(
12450 &mut self,
12451 action: &DeleteToBeginningOfLine,
12452 window: &mut Window,
12453 cx: &mut Context<Self>,
12454 ) {
12455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12456 self.transact(window, cx, |this, window, cx| {
12457 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12458 s.move_with(|_, selection| {
12459 selection.reversed = true;
12460 });
12461 });
12462
12463 this.select_to_beginning_of_line(
12464 &SelectToBeginningOfLine {
12465 stop_at_soft_wraps: false,
12466 stop_at_indent: action.stop_at_indent,
12467 },
12468 window,
12469 cx,
12470 );
12471 this.backspace(&Backspace, window, cx);
12472 });
12473 }
12474
12475 pub fn move_to_end_of_line(
12476 &mut self,
12477 action: &MoveToEndOfLine,
12478 window: &mut Window,
12479 cx: &mut Context<Self>,
12480 ) {
12481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12482 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12483 s.move_cursors_with(|map, head, _| {
12484 (
12485 movement::line_end(map, head, action.stop_at_soft_wraps),
12486 SelectionGoal::None,
12487 )
12488 });
12489 })
12490 }
12491
12492 pub fn select_to_end_of_line(
12493 &mut self,
12494 action: &SelectToEndOfLine,
12495 window: &mut Window,
12496 cx: &mut Context<Self>,
12497 ) {
12498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12499 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12500 s.move_heads_with(|map, head, _| {
12501 (
12502 movement::line_end(map, head, action.stop_at_soft_wraps),
12503 SelectionGoal::None,
12504 )
12505 });
12506 })
12507 }
12508
12509 pub fn delete_to_end_of_line(
12510 &mut self,
12511 _: &DeleteToEndOfLine,
12512 window: &mut Window,
12513 cx: &mut Context<Self>,
12514 ) {
12515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12516 self.transact(window, cx, |this, window, cx| {
12517 this.select_to_end_of_line(
12518 &SelectToEndOfLine {
12519 stop_at_soft_wraps: false,
12520 },
12521 window,
12522 cx,
12523 );
12524 this.delete(&Delete, window, cx);
12525 });
12526 }
12527
12528 pub fn cut_to_end_of_line(
12529 &mut self,
12530 _: &CutToEndOfLine,
12531 window: &mut Window,
12532 cx: &mut Context<Self>,
12533 ) {
12534 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12535 self.transact(window, cx, |this, window, cx| {
12536 this.select_to_end_of_line(
12537 &SelectToEndOfLine {
12538 stop_at_soft_wraps: false,
12539 },
12540 window,
12541 cx,
12542 );
12543 this.cut(&Cut, window, cx);
12544 });
12545 }
12546
12547 pub fn move_to_start_of_paragraph(
12548 &mut self,
12549 _: &MoveToStartOfParagraph,
12550 window: &mut Window,
12551 cx: &mut Context<Self>,
12552 ) {
12553 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12554 cx.propagate();
12555 return;
12556 }
12557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12558 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12559 s.move_with(|map, selection| {
12560 selection.collapse_to(
12561 movement::start_of_paragraph(map, selection.head(), 1),
12562 SelectionGoal::None,
12563 )
12564 });
12565 })
12566 }
12567
12568 pub fn move_to_end_of_paragraph(
12569 &mut self,
12570 _: &MoveToEndOfParagraph,
12571 window: &mut Window,
12572 cx: &mut Context<Self>,
12573 ) {
12574 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12575 cx.propagate();
12576 return;
12577 }
12578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12579 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12580 s.move_with(|map, selection| {
12581 selection.collapse_to(
12582 movement::end_of_paragraph(map, selection.head(), 1),
12583 SelectionGoal::None,
12584 )
12585 });
12586 })
12587 }
12588
12589 pub fn select_to_start_of_paragraph(
12590 &mut self,
12591 _: &SelectToStartOfParagraph,
12592 window: &mut Window,
12593 cx: &mut Context<Self>,
12594 ) {
12595 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12596 cx.propagate();
12597 return;
12598 }
12599 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12600 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12601 s.move_heads_with(|map, head, _| {
12602 (
12603 movement::start_of_paragraph(map, head, 1),
12604 SelectionGoal::None,
12605 )
12606 });
12607 })
12608 }
12609
12610 pub fn select_to_end_of_paragraph(
12611 &mut self,
12612 _: &SelectToEndOfParagraph,
12613 window: &mut Window,
12614 cx: &mut Context<Self>,
12615 ) {
12616 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12617 cx.propagate();
12618 return;
12619 }
12620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12621 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12622 s.move_heads_with(|map, head, _| {
12623 (
12624 movement::end_of_paragraph(map, head, 1),
12625 SelectionGoal::None,
12626 )
12627 });
12628 })
12629 }
12630
12631 pub fn move_to_start_of_excerpt(
12632 &mut self,
12633 _: &MoveToStartOfExcerpt,
12634 window: &mut Window,
12635 cx: &mut Context<Self>,
12636 ) {
12637 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12638 cx.propagate();
12639 return;
12640 }
12641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12642 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12643 s.move_with(|map, selection| {
12644 selection.collapse_to(
12645 movement::start_of_excerpt(
12646 map,
12647 selection.head(),
12648 workspace::searchable::Direction::Prev,
12649 ),
12650 SelectionGoal::None,
12651 )
12652 });
12653 })
12654 }
12655
12656 pub fn move_to_start_of_next_excerpt(
12657 &mut self,
12658 _: &MoveToStartOfNextExcerpt,
12659 window: &mut Window,
12660 cx: &mut Context<Self>,
12661 ) {
12662 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12663 cx.propagate();
12664 return;
12665 }
12666
12667 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12668 s.move_with(|map, selection| {
12669 selection.collapse_to(
12670 movement::start_of_excerpt(
12671 map,
12672 selection.head(),
12673 workspace::searchable::Direction::Next,
12674 ),
12675 SelectionGoal::None,
12676 )
12677 });
12678 })
12679 }
12680
12681 pub fn move_to_end_of_excerpt(
12682 &mut self,
12683 _: &MoveToEndOfExcerpt,
12684 window: &mut Window,
12685 cx: &mut Context<Self>,
12686 ) {
12687 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12688 cx.propagate();
12689 return;
12690 }
12691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12692 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12693 s.move_with(|map, selection| {
12694 selection.collapse_to(
12695 movement::end_of_excerpt(
12696 map,
12697 selection.head(),
12698 workspace::searchable::Direction::Next,
12699 ),
12700 SelectionGoal::None,
12701 )
12702 });
12703 })
12704 }
12705
12706 pub fn move_to_end_of_previous_excerpt(
12707 &mut self,
12708 _: &MoveToEndOfPreviousExcerpt,
12709 window: &mut Window,
12710 cx: &mut Context<Self>,
12711 ) {
12712 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12713 cx.propagate();
12714 return;
12715 }
12716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12717 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12718 s.move_with(|map, selection| {
12719 selection.collapse_to(
12720 movement::end_of_excerpt(
12721 map,
12722 selection.head(),
12723 workspace::searchable::Direction::Prev,
12724 ),
12725 SelectionGoal::None,
12726 )
12727 });
12728 })
12729 }
12730
12731 pub fn select_to_start_of_excerpt(
12732 &mut self,
12733 _: &SelectToStartOfExcerpt,
12734 window: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12738 cx.propagate();
12739 return;
12740 }
12741 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12742 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12743 s.move_heads_with(|map, head, _| {
12744 (
12745 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12746 SelectionGoal::None,
12747 )
12748 });
12749 })
12750 }
12751
12752 pub fn select_to_start_of_next_excerpt(
12753 &mut self,
12754 _: &SelectToStartOfNextExcerpt,
12755 window: &mut Window,
12756 cx: &mut Context<Self>,
12757 ) {
12758 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12759 cx.propagate();
12760 return;
12761 }
12762 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12763 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12764 s.move_heads_with(|map, head, _| {
12765 (
12766 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12767 SelectionGoal::None,
12768 )
12769 });
12770 })
12771 }
12772
12773 pub fn select_to_end_of_excerpt(
12774 &mut self,
12775 _: &SelectToEndOfExcerpt,
12776 window: &mut Window,
12777 cx: &mut Context<Self>,
12778 ) {
12779 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12780 cx.propagate();
12781 return;
12782 }
12783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12784 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12785 s.move_heads_with(|map, head, _| {
12786 (
12787 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12788 SelectionGoal::None,
12789 )
12790 });
12791 })
12792 }
12793
12794 pub fn select_to_end_of_previous_excerpt(
12795 &mut self,
12796 _: &SelectToEndOfPreviousExcerpt,
12797 window: &mut Window,
12798 cx: &mut Context<Self>,
12799 ) {
12800 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12801 cx.propagate();
12802 return;
12803 }
12804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12805 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12806 s.move_heads_with(|map, head, _| {
12807 (
12808 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12809 SelectionGoal::None,
12810 )
12811 });
12812 })
12813 }
12814
12815 pub fn move_to_beginning(
12816 &mut self,
12817 _: &MoveToBeginning,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12822 cx.propagate();
12823 return;
12824 }
12825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12826 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12827 s.select_ranges(vec![0..0]);
12828 });
12829 }
12830
12831 pub fn select_to_beginning(
12832 &mut self,
12833 _: &SelectToBeginning,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 let mut selection = self.selections.last::<Point>(cx);
12838 selection.set_head(Point::zero(), SelectionGoal::None);
12839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12840 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12841 s.select(vec![selection]);
12842 });
12843 }
12844
12845 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12846 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12847 cx.propagate();
12848 return;
12849 }
12850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12851 let cursor = self.buffer.read(cx).read(cx).len();
12852 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12853 s.select_ranges(vec![cursor..cursor])
12854 });
12855 }
12856
12857 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12858 self.nav_history = nav_history;
12859 }
12860
12861 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12862 self.nav_history.as_ref()
12863 }
12864
12865 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12866 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12867 }
12868
12869 fn push_to_nav_history(
12870 &mut self,
12871 cursor_anchor: Anchor,
12872 new_position: Option<Point>,
12873 is_deactivate: bool,
12874 cx: &mut Context<Self>,
12875 ) {
12876 if let Some(nav_history) = self.nav_history.as_mut() {
12877 let buffer = self.buffer.read(cx).read(cx);
12878 let cursor_position = cursor_anchor.to_point(&buffer);
12879 let scroll_state = self.scroll_manager.anchor();
12880 let scroll_top_row = scroll_state.top_row(&buffer);
12881 drop(buffer);
12882
12883 if let Some(new_position) = new_position {
12884 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12885 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12886 return;
12887 }
12888 }
12889
12890 nav_history.push(
12891 Some(NavigationData {
12892 cursor_anchor,
12893 cursor_position,
12894 scroll_anchor: scroll_state,
12895 scroll_top_row,
12896 }),
12897 cx,
12898 );
12899 cx.emit(EditorEvent::PushedToNavHistory {
12900 anchor: cursor_anchor,
12901 is_deactivate,
12902 })
12903 }
12904 }
12905
12906 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12908 let buffer = self.buffer.read(cx).snapshot(cx);
12909 let mut selection = self.selections.first::<usize>(cx);
12910 selection.set_head(buffer.len(), SelectionGoal::None);
12911 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12912 s.select(vec![selection]);
12913 });
12914 }
12915
12916 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12918 let end = self.buffer.read(cx).read(cx).len();
12919 self.change_selections(None, window, cx, |s| {
12920 s.select_ranges(vec![0..end]);
12921 });
12922 }
12923
12924 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12927 let mut selections = self.selections.all::<Point>(cx);
12928 let max_point = display_map.buffer_snapshot.max_point();
12929 for selection in &mut selections {
12930 let rows = selection.spanned_rows(true, &display_map);
12931 selection.start = Point::new(rows.start.0, 0);
12932 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12933 selection.reversed = false;
12934 }
12935 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12936 s.select(selections);
12937 });
12938 }
12939
12940 pub fn split_selection_into_lines(
12941 &mut self,
12942 _: &SplitSelectionIntoLines,
12943 window: &mut Window,
12944 cx: &mut Context<Self>,
12945 ) {
12946 let selections = self
12947 .selections
12948 .all::<Point>(cx)
12949 .into_iter()
12950 .map(|selection| selection.start..selection.end)
12951 .collect::<Vec<_>>();
12952 self.unfold_ranges(&selections, true, true, cx);
12953
12954 let mut new_selection_ranges = Vec::new();
12955 {
12956 let buffer = self.buffer.read(cx).read(cx);
12957 for selection in selections {
12958 for row in selection.start.row..selection.end.row {
12959 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12960 new_selection_ranges.push(cursor..cursor);
12961 }
12962
12963 let is_multiline_selection = selection.start.row != selection.end.row;
12964 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12965 // so this action feels more ergonomic when paired with other selection operations
12966 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12967 if !should_skip_last {
12968 new_selection_ranges.push(selection.end..selection.end);
12969 }
12970 }
12971 }
12972 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12973 s.select_ranges(new_selection_ranges);
12974 });
12975 }
12976
12977 pub fn add_selection_above(
12978 &mut self,
12979 _: &AddSelectionAbove,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 self.add_selection(true, window, cx);
12984 }
12985
12986 pub fn add_selection_below(
12987 &mut self,
12988 _: &AddSelectionBelow,
12989 window: &mut Window,
12990 cx: &mut Context<Self>,
12991 ) {
12992 self.add_selection(false, window, cx);
12993 }
12994
12995 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12997
12998 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12999 let all_selections = self.selections.all::<Point>(cx);
13000 let text_layout_details = self.text_layout_details(window);
13001
13002 let (mut columnar_selections, new_selections_to_columnarize) = {
13003 if let Some(state) = self.add_selections_state.as_ref() {
13004 let columnar_selection_ids: HashSet<_> = state
13005 .groups
13006 .iter()
13007 .flat_map(|group| group.stack.iter())
13008 .copied()
13009 .collect();
13010
13011 all_selections
13012 .into_iter()
13013 .partition(|s| columnar_selection_ids.contains(&s.id))
13014 } else {
13015 (Vec::new(), all_selections)
13016 }
13017 };
13018
13019 let mut state = self
13020 .add_selections_state
13021 .take()
13022 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13023
13024 for selection in new_selections_to_columnarize {
13025 let range = selection.display_range(&display_map).sorted();
13026 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13027 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13028 let positions = start_x.min(end_x)..start_x.max(end_x);
13029 let mut stack = Vec::new();
13030 for row in range.start.row().0..=range.end.row().0 {
13031 if let Some(selection) = self.selections.build_columnar_selection(
13032 &display_map,
13033 DisplayRow(row),
13034 &positions,
13035 selection.reversed,
13036 &text_layout_details,
13037 ) {
13038 stack.push(selection.id);
13039 columnar_selections.push(selection);
13040 }
13041 }
13042 if !stack.is_empty() {
13043 if above {
13044 stack.reverse();
13045 }
13046 state.groups.push(AddSelectionsGroup { above, stack });
13047 }
13048 }
13049
13050 let mut final_selections = Vec::new();
13051 let end_row = if above {
13052 DisplayRow(0)
13053 } else {
13054 display_map.max_point().row()
13055 };
13056
13057 let mut last_added_item_per_group = HashMap::default();
13058 for group in state.groups.iter_mut() {
13059 if let Some(last_id) = group.stack.last() {
13060 last_added_item_per_group.insert(*last_id, group);
13061 }
13062 }
13063
13064 for selection in columnar_selections {
13065 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13066 if above == group.above {
13067 let range = selection.display_range(&display_map).sorted();
13068 debug_assert_eq!(range.start.row(), range.end.row());
13069 let mut row = range.start.row();
13070 let positions =
13071 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13072 px(start)..px(end)
13073 } else {
13074 let start_x =
13075 display_map.x_for_display_point(range.start, &text_layout_details);
13076 let end_x =
13077 display_map.x_for_display_point(range.end, &text_layout_details);
13078 start_x.min(end_x)..start_x.max(end_x)
13079 };
13080
13081 let mut maybe_new_selection = None;
13082 while row != end_row {
13083 if above {
13084 row.0 -= 1;
13085 } else {
13086 row.0 += 1;
13087 }
13088 if let Some(new_selection) = self.selections.build_columnar_selection(
13089 &display_map,
13090 row,
13091 &positions,
13092 selection.reversed,
13093 &text_layout_details,
13094 ) {
13095 maybe_new_selection = Some(new_selection);
13096 break;
13097 }
13098 }
13099
13100 if let Some(new_selection) = maybe_new_selection {
13101 group.stack.push(new_selection.id);
13102 if above {
13103 final_selections.push(new_selection);
13104 final_selections.push(selection);
13105 } else {
13106 final_selections.push(selection);
13107 final_selections.push(new_selection);
13108 }
13109 } else {
13110 final_selections.push(selection);
13111 }
13112 } else {
13113 group.stack.pop();
13114 }
13115 } else {
13116 final_selections.push(selection);
13117 }
13118 }
13119
13120 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13121 s.select(final_selections);
13122 });
13123
13124 let final_selection_ids: HashSet<_> = self
13125 .selections
13126 .all::<Point>(cx)
13127 .iter()
13128 .map(|s| s.id)
13129 .collect();
13130 state.groups.retain_mut(|group| {
13131 // selections might get merged above so we remove invalid items from stacks
13132 group.stack.retain(|id| final_selection_ids.contains(id));
13133
13134 // single selection in stack can be treated as initial state
13135 group.stack.len() > 1
13136 });
13137
13138 if !state.groups.is_empty() {
13139 self.add_selections_state = Some(state);
13140 }
13141 }
13142
13143 fn select_match_ranges(
13144 &mut self,
13145 range: Range<usize>,
13146 reversed: bool,
13147 replace_newest: bool,
13148 auto_scroll: Option<Autoscroll>,
13149 window: &mut Window,
13150 cx: &mut Context<Editor>,
13151 ) {
13152 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13153 self.change_selections(auto_scroll, window, cx, |s| {
13154 if replace_newest {
13155 s.delete(s.newest_anchor().id);
13156 }
13157 if reversed {
13158 s.insert_range(range.end..range.start);
13159 } else {
13160 s.insert_range(range);
13161 }
13162 });
13163 }
13164
13165 pub fn select_next_match_internal(
13166 &mut self,
13167 display_map: &DisplaySnapshot,
13168 replace_newest: bool,
13169 autoscroll: Option<Autoscroll>,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) -> Result<()> {
13173 let buffer = &display_map.buffer_snapshot;
13174 let mut selections = self.selections.all::<usize>(cx);
13175 if let Some(mut select_next_state) = self.select_next_state.take() {
13176 let query = &select_next_state.query;
13177 if !select_next_state.done {
13178 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13179 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13180 let mut next_selected_range = None;
13181
13182 let bytes_after_last_selection =
13183 buffer.bytes_in_range(last_selection.end..buffer.len());
13184 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13185 let query_matches = query
13186 .stream_find_iter(bytes_after_last_selection)
13187 .map(|result| (last_selection.end, result))
13188 .chain(
13189 query
13190 .stream_find_iter(bytes_before_first_selection)
13191 .map(|result| (0, result)),
13192 );
13193
13194 for (start_offset, query_match) in query_matches {
13195 let query_match = query_match.unwrap(); // can only fail due to I/O
13196 let offset_range =
13197 start_offset + query_match.start()..start_offset + query_match.end();
13198 let display_range = offset_range.start.to_display_point(display_map)
13199 ..offset_range.end.to_display_point(display_map);
13200
13201 if !select_next_state.wordwise
13202 || (!movement::is_inside_word(display_map, display_range.start)
13203 && !movement::is_inside_word(display_map, display_range.end))
13204 {
13205 // TODO: This is n^2, because we might check all the selections
13206 if !selections
13207 .iter()
13208 .any(|selection| selection.range().overlaps(&offset_range))
13209 {
13210 next_selected_range = Some(offset_range);
13211 break;
13212 }
13213 }
13214 }
13215
13216 if let Some(next_selected_range) = next_selected_range {
13217 self.select_match_ranges(
13218 next_selected_range,
13219 last_selection.reversed,
13220 replace_newest,
13221 autoscroll,
13222 window,
13223 cx,
13224 );
13225 } else {
13226 select_next_state.done = true;
13227 }
13228 }
13229
13230 self.select_next_state = Some(select_next_state);
13231 } else {
13232 let mut only_carets = true;
13233 let mut same_text_selected = true;
13234 let mut selected_text = None;
13235
13236 let mut selections_iter = selections.iter().peekable();
13237 while let Some(selection) = selections_iter.next() {
13238 if selection.start != selection.end {
13239 only_carets = false;
13240 }
13241
13242 if same_text_selected {
13243 if selected_text.is_none() {
13244 selected_text =
13245 Some(buffer.text_for_range(selection.range()).collect::<String>());
13246 }
13247
13248 if let Some(next_selection) = selections_iter.peek() {
13249 if next_selection.range().len() == selection.range().len() {
13250 let next_selected_text = buffer
13251 .text_for_range(next_selection.range())
13252 .collect::<String>();
13253 if Some(next_selected_text) != selected_text {
13254 same_text_selected = false;
13255 selected_text = None;
13256 }
13257 } else {
13258 same_text_selected = false;
13259 selected_text = None;
13260 }
13261 }
13262 }
13263 }
13264
13265 if only_carets {
13266 for selection in &mut selections {
13267 let word_range = movement::surrounding_word(
13268 display_map,
13269 selection.start.to_display_point(display_map),
13270 );
13271 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13272 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13273 selection.goal = SelectionGoal::None;
13274 selection.reversed = false;
13275 self.select_match_ranges(
13276 selection.start..selection.end,
13277 selection.reversed,
13278 replace_newest,
13279 autoscroll,
13280 window,
13281 cx,
13282 );
13283 }
13284
13285 if selections.len() == 1 {
13286 let selection = selections
13287 .last()
13288 .expect("ensured that there's only one selection");
13289 let query = buffer
13290 .text_for_range(selection.start..selection.end)
13291 .collect::<String>();
13292 let is_empty = query.is_empty();
13293 let select_state = SelectNextState {
13294 query: AhoCorasick::new(&[query])?,
13295 wordwise: true,
13296 done: is_empty,
13297 };
13298 self.select_next_state = Some(select_state);
13299 } else {
13300 self.select_next_state = None;
13301 }
13302 } else if let Some(selected_text) = selected_text {
13303 self.select_next_state = Some(SelectNextState {
13304 query: AhoCorasick::new(&[selected_text])?,
13305 wordwise: false,
13306 done: false,
13307 });
13308 self.select_next_match_internal(
13309 display_map,
13310 replace_newest,
13311 autoscroll,
13312 window,
13313 cx,
13314 )?;
13315 }
13316 }
13317 Ok(())
13318 }
13319
13320 pub fn select_all_matches(
13321 &mut self,
13322 _action: &SelectAllMatches,
13323 window: &mut Window,
13324 cx: &mut Context<Self>,
13325 ) -> Result<()> {
13326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13327
13328 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13329
13330 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13331 let Some(select_next_state) = self.select_next_state.as_mut() else {
13332 return Ok(());
13333 };
13334 if select_next_state.done {
13335 return Ok(());
13336 }
13337
13338 let mut new_selections = Vec::new();
13339
13340 let reversed = self.selections.oldest::<usize>(cx).reversed;
13341 let buffer = &display_map.buffer_snapshot;
13342 let query_matches = select_next_state
13343 .query
13344 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13345
13346 for query_match in query_matches.into_iter() {
13347 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13348 let offset_range = if reversed {
13349 query_match.end()..query_match.start()
13350 } else {
13351 query_match.start()..query_match.end()
13352 };
13353 let display_range = offset_range.start.to_display_point(&display_map)
13354 ..offset_range.end.to_display_point(&display_map);
13355
13356 if !select_next_state.wordwise
13357 || (!movement::is_inside_word(&display_map, display_range.start)
13358 && !movement::is_inside_word(&display_map, display_range.end))
13359 {
13360 new_selections.push(offset_range.start..offset_range.end);
13361 }
13362 }
13363
13364 select_next_state.done = true;
13365 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13366 self.change_selections(None, window, cx, |selections| {
13367 selections.select_ranges(new_selections)
13368 });
13369
13370 Ok(())
13371 }
13372
13373 pub fn select_next(
13374 &mut self,
13375 action: &SelectNext,
13376 window: &mut Window,
13377 cx: &mut Context<Self>,
13378 ) -> Result<()> {
13379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13381 self.select_next_match_internal(
13382 &display_map,
13383 action.replace_newest,
13384 Some(Autoscroll::newest()),
13385 window,
13386 cx,
13387 )?;
13388 Ok(())
13389 }
13390
13391 pub fn select_previous(
13392 &mut self,
13393 action: &SelectPrevious,
13394 window: &mut Window,
13395 cx: &mut Context<Self>,
13396 ) -> Result<()> {
13397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13399 let buffer = &display_map.buffer_snapshot;
13400 let mut selections = self.selections.all::<usize>(cx);
13401 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13402 let query = &select_prev_state.query;
13403 if !select_prev_state.done {
13404 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13405 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13406 let mut next_selected_range = None;
13407 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13408 let bytes_before_last_selection =
13409 buffer.reversed_bytes_in_range(0..last_selection.start);
13410 let bytes_after_first_selection =
13411 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13412 let query_matches = query
13413 .stream_find_iter(bytes_before_last_selection)
13414 .map(|result| (last_selection.start, result))
13415 .chain(
13416 query
13417 .stream_find_iter(bytes_after_first_selection)
13418 .map(|result| (buffer.len(), result)),
13419 );
13420 for (end_offset, query_match) in query_matches {
13421 let query_match = query_match.unwrap(); // can only fail due to I/O
13422 let offset_range =
13423 end_offset - query_match.end()..end_offset - query_match.start();
13424 let display_range = offset_range.start.to_display_point(&display_map)
13425 ..offset_range.end.to_display_point(&display_map);
13426
13427 if !select_prev_state.wordwise
13428 || (!movement::is_inside_word(&display_map, display_range.start)
13429 && !movement::is_inside_word(&display_map, display_range.end))
13430 {
13431 next_selected_range = Some(offset_range);
13432 break;
13433 }
13434 }
13435
13436 if let Some(next_selected_range) = next_selected_range {
13437 self.select_match_ranges(
13438 next_selected_range,
13439 last_selection.reversed,
13440 action.replace_newest,
13441 Some(Autoscroll::newest()),
13442 window,
13443 cx,
13444 );
13445 } else {
13446 select_prev_state.done = true;
13447 }
13448 }
13449
13450 self.select_prev_state = Some(select_prev_state);
13451 } else {
13452 let mut only_carets = true;
13453 let mut same_text_selected = true;
13454 let mut selected_text = None;
13455
13456 let mut selections_iter = selections.iter().peekable();
13457 while let Some(selection) = selections_iter.next() {
13458 if selection.start != selection.end {
13459 only_carets = false;
13460 }
13461
13462 if same_text_selected {
13463 if selected_text.is_none() {
13464 selected_text =
13465 Some(buffer.text_for_range(selection.range()).collect::<String>());
13466 }
13467
13468 if let Some(next_selection) = selections_iter.peek() {
13469 if next_selection.range().len() == selection.range().len() {
13470 let next_selected_text = buffer
13471 .text_for_range(next_selection.range())
13472 .collect::<String>();
13473 if Some(next_selected_text) != selected_text {
13474 same_text_selected = false;
13475 selected_text = None;
13476 }
13477 } else {
13478 same_text_selected = false;
13479 selected_text = None;
13480 }
13481 }
13482 }
13483 }
13484
13485 if only_carets {
13486 for selection in &mut selections {
13487 let word_range = movement::surrounding_word(
13488 &display_map,
13489 selection.start.to_display_point(&display_map),
13490 );
13491 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13492 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13493 selection.goal = SelectionGoal::None;
13494 selection.reversed = false;
13495 self.select_match_ranges(
13496 selection.start..selection.end,
13497 selection.reversed,
13498 action.replace_newest,
13499 Some(Autoscroll::newest()),
13500 window,
13501 cx,
13502 );
13503 }
13504 if selections.len() == 1 {
13505 let selection = selections
13506 .last()
13507 .expect("ensured that there's only one selection");
13508 let query = buffer
13509 .text_for_range(selection.start..selection.end)
13510 .collect::<String>();
13511 let is_empty = query.is_empty();
13512 let select_state = SelectNextState {
13513 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13514 wordwise: true,
13515 done: is_empty,
13516 };
13517 self.select_prev_state = Some(select_state);
13518 } else {
13519 self.select_prev_state = None;
13520 }
13521 } else if let Some(selected_text) = selected_text {
13522 self.select_prev_state = Some(SelectNextState {
13523 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13524 wordwise: false,
13525 done: false,
13526 });
13527 self.select_previous(action, window, cx)?;
13528 }
13529 }
13530 Ok(())
13531 }
13532
13533 pub fn find_next_match(
13534 &mut self,
13535 _: &FindNextMatch,
13536 window: &mut Window,
13537 cx: &mut Context<Self>,
13538 ) -> Result<()> {
13539 let selections = self.selections.disjoint_anchors();
13540 match selections.first() {
13541 Some(first) if selections.len() >= 2 => {
13542 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13543 s.select_ranges([first.range()]);
13544 });
13545 }
13546 _ => self.select_next(
13547 &SelectNext {
13548 replace_newest: true,
13549 },
13550 window,
13551 cx,
13552 )?,
13553 }
13554 Ok(())
13555 }
13556
13557 pub fn find_previous_match(
13558 &mut self,
13559 _: &FindPreviousMatch,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) -> Result<()> {
13563 let selections = self.selections.disjoint_anchors();
13564 match selections.last() {
13565 Some(last) if selections.len() >= 2 => {
13566 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13567 s.select_ranges([last.range()]);
13568 });
13569 }
13570 _ => self.select_previous(
13571 &SelectPrevious {
13572 replace_newest: true,
13573 },
13574 window,
13575 cx,
13576 )?,
13577 }
13578 Ok(())
13579 }
13580
13581 pub fn toggle_comments(
13582 &mut self,
13583 action: &ToggleComments,
13584 window: &mut Window,
13585 cx: &mut Context<Self>,
13586 ) {
13587 if self.read_only(cx) {
13588 return;
13589 }
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13591 let text_layout_details = &self.text_layout_details(window);
13592 self.transact(window, cx, |this, window, cx| {
13593 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13594 let mut edits = Vec::new();
13595 let mut selection_edit_ranges = Vec::new();
13596 let mut last_toggled_row = None;
13597 let snapshot = this.buffer.read(cx).read(cx);
13598 let empty_str: Arc<str> = Arc::default();
13599 let mut suffixes_inserted = Vec::new();
13600 let ignore_indent = action.ignore_indent;
13601
13602 fn comment_prefix_range(
13603 snapshot: &MultiBufferSnapshot,
13604 row: MultiBufferRow,
13605 comment_prefix: &str,
13606 comment_prefix_whitespace: &str,
13607 ignore_indent: bool,
13608 ) -> Range<Point> {
13609 let indent_size = if ignore_indent {
13610 0
13611 } else {
13612 snapshot.indent_size_for_line(row).len
13613 };
13614
13615 let start = Point::new(row.0, indent_size);
13616
13617 let mut line_bytes = snapshot
13618 .bytes_in_range(start..snapshot.max_point())
13619 .flatten()
13620 .copied();
13621
13622 // If this line currently begins with the line comment prefix, then record
13623 // the range containing the prefix.
13624 if line_bytes
13625 .by_ref()
13626 .take(comment_prefix.len())
13627 .eq(comment_prefix.bytes())
13628 {
13629 // Include any whitespace that matches the comment prefix.
13630 let matching_whitespace_len = line_bytes
13631 .zip(comment_prefix_whitespace.bytes())
13632 .take_while(|(a, b)| a == b)
13633 .count() as u32;
13634 let end = Point::new(
13635 start.row,
13636 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13637 );
13638 start..end
13639 } else {
13640 start..start
13641 }
13642 }
13643
13644 fn comment_suffix_range(
13645 snapshot: &MultiBufferSnapshot,
13646 row: MultiBufferRow,
13647 comment_suffix: &str,
13648 comment_suffix_has_leading_space: bool,
13649 ) -> Range<Point> {
13650 let end = Point::new(row.0, snapshot.line_len(row));
13651 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13652
13653 let mut line_end_bytes = snapshot
13654 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13655 .flatten()
13656 .copied();
13657
13658 let leading_space_len = if suffix_start_column > 0
13659 && line_end_bytes.next() == Some(b' ')
13660 && comment_suffix_has_leading_space
13661 {
13662 1
13663 } else {
13664 0
13665 };
13666
13667 // If this line currently begins with the line comment prefix, then record
13668 // the range containing the prefix.
13669 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13670 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13671 start..end
13672 } else {
13673 end..end
13674 }
13675 }
13676
13677 // TODO: Handle selections that cross excerpts
13678 for selection in &mut selections {
13679 let start_column = snapshot
13680 .indent_size_for_line(MultiBufferRow(selection.start.row))
13681 .len;
13682 let language = if let Some(language) =
13683 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13684 {
13685 language
13686 } else {
13687 continue;
13688 };
13689
13690 selection_edit_ranges.clear();
13691
13692 // If multiple selections contain a given row, avoid processing that
13693 // row more than once.
13694 let mut start_row = MultiBufferRow(selection.start.row);
13695 if last_toggled_row == Some(start_row) {
13696 start_row = start_row.next_row();
13697 }
13698 let end_row =
13699 if selection.end.row > selection.start.row && selection.end.column == 0 {
13700 MultiBufferRow(selection.end.row - 1)
13701 } else {
13702 MultiBufferRow(selection.end.row)
13703 };
13704 last_toggled_row = Some(end_row);
13705
13706 if start_row > end_row {
13707 continue;
13708 }
13709
13710 // If the language has line comments, toggle those.
13711 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13712
13713 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13714 if ignore_indent {
13715 full_comment_prefixes = full_comment_prefixes
13716 .into_iter()
13717 .map(|s| Arc::from(s.trim_end()))
13718 .collect();
13719 }
13720
13721 if !full_comment_prefixes.is_empty() {
13722 let first_prefix = full_comment_prefixes
13723 .first()
13724 .expect("prefixes is non-empty");
13725 let prefix_trimmed_lengths = full_comment_prefixes
13726 .iter()
13727 .map(|p| p.trim_end_matches(' ').len())
13728 .collect::<SmallVec<[usize; 4]>>();
13729
13730 let mut all_selection_lines_are_comments = true;
13731
13732 for row in start_row.0..=end_row.0 {
13733 let row = MultiBufferRow(row);
13734 if start_row < end_row && snapshot.is_line_blank(row) {
13735 continue;
13736 }
13737
13738 let prefix_range = full_comment_prefixes
13739 .iter()
13740 .zip(prefix_trimmed_lengths.iter().copied())
13741 .map(|(prefix, trimmed_prefix_len)| {
13742 comment_prefix_range(
13743 snapshot.deref(),
13744 row,
13745 &prefix[..trimmed_prefix_len],
13746 &prefix[trimmed_prefix_len..],
13747 ignore_indent,
13748 )
13749 })
13750 .max_by_key(|range| range.end.column - range.start.column)
13751 .expect("prefixes is non-empty");
13752
13753 if prefix_range.is_empty() {
13754 all_selection_lines_are_comments = false;
13755 }
13756
13757 selection_edit_ranges.push(prefix_range);
13758 }
13759
13760 if all_selection_lines_are_comments {
13761 edits.extend(
13762 selection_edit_ranges
13763 .iter()
13764 .cloned()
13765 .map(|range| (range, empty_str.clone())),
13766 );
13767 } else {
13768 let min_column = selection_edit_ranges
13769 .iter()
13770 .map(|range| range.start.column)
13771 .min()
13772 .unwrap_or(0);
13773 edits.extend(selection_edit_ranges.iter().map(|range| {
13774 let position = Point::new(range.start.row, min_column);
13775 (position..position, first_prefix.clone())
13776 }));
13777 }
13778 } else if let Some((full_comment_prefix, comment_suffix)) =
13779 language.block_comment_delimiters()
13780 {
13781 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13782 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13783 let prefix_range = comment_prefix_range(
13784 snapshot.deref(),
13785 start_row,
13786 comment_prefix,
13787 comment_prefix_whitespace,
13788 ignore_indent,
13789 );
13790 let suffix_range = comment_suffix_range(
13791 snapshot.deref(),
13792 end_row,
13793 comment_suffix.trim_start_matches(' '),
13794 comment_suffix.starts_with(' '),
13795 );
13796
13797 if prefix_range.is_empty() || suffix_range.is_empty() {
13798 edits.push((
13799 prefix_range.start..prefix_range.start,
13800 full_comment_prefix.clone(),
13801 ));
13802 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13803 suffixes_inserted.push((end_row, comment_suffix.len()));
13804 } else {
13805 edits.push((prefix_range, empty_str.clone()));
13806 edits.push((suffix_range, empty_str.clone()));
13807 }
13808 } else {
13809 continue;
13810 }
13811 }
13812
13813 drop(snapshot);
13814 this.buffer.update(cx, |buffer, cx| {
13815 buffer.edit(edits, None, cx);
13816 });
13817
13818 // Adjust selections so that they end before any comment suffixes that
13819 // were inserted.
13820 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13821 let mut selections = this.selections.all::<Point>(cx);
13822 let snapshot = this.buffer.read(cx).read(cx);
13823 for selection in &mut selections {
13824 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13825 match row.cmp(&MultiBufferRow(selection.end.row)) {
13826 Ordering::Less => {
13827 suffixes_inserted.next();
13828 continue;
13829 }
13830 Ordering::Greater => break,
13831 Ordering::Equal => {
13832 if selection.end.column == snapshot.line_len(row) {
13833 if selection.is_empty() {
13834 selection.start.column -= suffix_len as u32;
13835 }
13836 selection.end.column -= suffix_len as u32;
13837 }
13838 break;
13839 }
13840 }
13841 }
13842 }
13843
13844 drop(snapshot);
13845 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13846 s.select(selections)
13847 });
13848
13849 let selections = this.selections.all::<Point>(cx);
13850 let selections_on_single_row = selections.windows(2).all(|selections| {
13851 selections[0].start.row == selections[1].start.row
13852 && selections[0].end.row == selections[1].end.row
13853 && selections[0].start.row == selections[0].end.row
13854 });
13855 let selections_selecting = selections
13856 .iter()
13857 .any(|selection| selection.start != selection.end);
13858 let advance_downwards = action.advance_downwards
13859 && selections_on_single_row
13860 && !selections_selecting
13861 && !matches!(this.mode, EditorMode::SingleLine { .. });
13862
13863 if advance_downwards {
13864 let snapshot = this.buffer.read(cx).snapshot(cx);
13865
13866 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13867 s.move_cursors_with(|display_snapshot, display_point, _| {
13868 let mut point = display_point.to_point(display_snapshot);
13869 point.row += 1;
13870 point = snapshot.clip_point(point, Bias::Left);
13871 let display_point = point.to_display_point(display_snapshot);
13872 let goal = SelectionGoal::HorizontalPosition(
13873 display_snapshot
13874 .x_for_display_point(display_point, text_layout_details)
13875 .into(),
13876 );
13877 (display_point, goal)
13878 })
13879 });
13880 }
13881 });
13882 }
13883
13884 pub fn select_enclosing_symbol(
13885 &mut self,
13886 _: &SelectEnclosingSymbol,
13887 window: &mut Window,
13888 cx: &mut Context<Self>,
13889 ) {
13890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13891
13892 let buffer = self.buffer.read(cx).snapshot(cx);
13893 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13894
13895 fn update_selection(
13896 selection: &Selection<usize>,
13897 buffer_snap: &MultiBufferSnapshot,
13898 ) -> Option<Selection<usize>> {
13899 let cursor = selection.head();
13900 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13901 for symbol in symbols.iter().rev() {
13902 let start = symbol.range.start.to_offset(buffer_snap);
13903 let end = symbol.range.end.to_offset(buffer_snap);
13904 let new_range = start..end;
13905 if start < selection.start || end > selection.end {
13906 return Some(Selection {
13907 id: selection.id,
13908 start: new_range.start,
13909 end: new_range.end,
13910 goal: SelectionGoal::None,
13911 reversed: selection.reversed,
13912 });
13913 }
13914 }
13915 None
13916 }
13917
13918 let mut selected_larger_symbol = false;
13919 let new_selections = old_selections
13920 .iter()
13921 .map(|selection| match update_selection(selection, &buffer) {
13922 Some(new_selection) => {
13923 if new_selection.range() != selection.range() {
13924 selected_larger_symbol = true;
13925 }
13926 new_selection
13927 }
13928 None => selection.clone(),
13929 })
13930 .collect::<Vec<_>>();
13931
13932 if selected_larger_symbol {
13933 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13934 s.select(new_selections);
13935 });
13936 }
13937 }
13938
13939 pub fn select_larger_syntax_node(
13940 &mut self,
13941 _: &SelectLargerSyntaxNode,
13942 window: &mut Window,
13943 cx: &mut Context<Self>,
13944 ) {
13945 let Some(visible_row_count) = self.visible_row_count() else {
13946 return;
13947 };
13948 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13949 if old_selections.is_empty() {
13950 return;
13951 }
13952
13953 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13954
13955 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13956 let buffer = self.buffer.read(cx).snapshot(cx);
13957
13958 let mut selected_larger_node = false;
13959 let mut new_selections = old_selections
13960 .iter()
13961 .map(|selection| {
13962 let old_range = selection.start..selection.end;
13963
13964 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13965 // manually select word at selection
13966 if ["string_content", "inline"].contains(&node.kind()) {
13967 let word_range = {
13968 let display_point = buffer
13969 .offset_to_point(old_range.start)
13970 .to_display_point(&display_map);
13971 let Range { start, end } =
13972 movement::surrounding_word(&display_map, display_point);
13973 start.to_point(&display_map).to_offset(&buffer)
13974 ..end.to_point(&display_map).to_offset(&buffer)
13975 };
13976 // ignore if word is already selected
13977 if !word_range.is_empty() && old_range != word_range {
13978 let last_word_range = {
13979 let display_point = buffer
13980 .offset_to_point(old_range.end)
13981 .to_display_point(&display_map);
13982 let Range { start, end } =
13983 movement::surrounding_word(&display_map, display_point);
13984 start.to_point(&display_map).to_offset(&buffer)
13985 ..end.to_point(&display_map).to_offset(&buffer)
13986 };
13987 // only select word if start and end point belongs to same word
13988 if word_range == last_word_range {
13989 selected_larger_node = true;
13990 return Selection {
13991 id: selection.id,
13992 start: word_range.start,
13993 end: word_range.end,
13994 goal: SelectionGoal::None,
13995 reversed: selection.reversed,
13996 };
13997 }
13998 }
13999 }
14000 }
14001
14002 let mut new_range = old_range.clone();
14003 while let Some((_node, containing_range)) =
14004 buffer.syntax_ancestor(new_range.clone())
14005 {
14006 new_range = match containing_range {
14007 MultiOrSingleBufferOffsetRange::Single(_) => break,
14008 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14009 };
14010 if !display_map.intersects_fold(new_range.start)
14011 && !display_map.intersects_fold(new_range.end)
14012 {
14013 break;
14014 }
14015 }
14016
14017 selected_larger_node |= new_range != old_range;
14018 Selection {
14019 id: selection.id,
14020 start: new_range.start,
14021 end: new_range.end,
14022 goal: SelectionGoal::None,
14023 reversed: selection.reversed,
14024 }
14025 })
14026 .collect::<Vec<_>>();
14027
14028 if !selected_larger_node {
14029 return; // don't put this call in the history
14030 }
14031
14032 // scroll based on transformation done to the last selection created by the user
14033 let (last_old, last_new) = old_selections
14034 .last()
14035 .zip(new_selections.last().cloned())
14036 .expect("old_selections isn't empty");
14037
14038 // revert selection
14039 let is_selection_reversed = {
14040 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14041 new_selections.last_mut().expect("checked above").reversed =
14042 should_newest_selection_be_reversed;
14043 should_newest_selection_be_reversed
14044 };
14045
14046 if selected_larger_node {
14047 self.select_syntax_node_history.disable_clearing = true;
14048 self.change_selections(None, window, cx, |s| {
14049 s.select(new_selections.clone());
14050 });
14051 self.select_syntax_node_history.disable_clearing = false;
14052 }
14053
14054 let start_row = last_new.start.to_display_point(&display_map).row().0;
14055 let end_row = last_new.end.to_display_point(&display_map).row().0;
14056 let selection_height = end_row - start_row + 1;
14057 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14058
14059 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14060 let scroll_behavior = if fits_on_the_screen {
14061 self.request_autoscroll(Autoscroll::fit(), cx);
14062 SelectSyntaxNodeScrollBehavior::FitSelection
14063 } else if is_selection_reversed {
14064 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14065 SelectSyntaxNodeScrollBehavior::CursorTop
14066 } else {
14067 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14068 SelectSyntaxNodeScrollBehavior::CursorBottom
14069 };
14070
14071 self.select_syntax_node_history.push((
14072 old_selections,
14073 scroll_behavior,
14074 is_selection_reversed,
14075 ));
14076 }
14077
14078 pub fn select_smaller_syntax_node(
14079 &mut self,
14080 _: &SelectSmallerSyntaxNode,
14081 window: &mut Window,
14082 cx: &mut Context<Self>,
14083 ) {
14084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14085
14086 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14087 self.select_syntax_node_history.pop()
14088 {
14089 if let Some(selection) = selections.last_mut() {
14090 selection.reversed = is_selection_reversed;
14091 }
14092
14093 self.select_syntax_node_history.disable_clearing = true;
14094 self.change_selections(None, window, cx, |s| {
14095 s.select(selections.to_vec());
14096 });
14097 self.select_syntax_node_history.disable_clearing = false;
14098
14099 match scroll_behavior {
14100 SelectSyntaxNodeScrollBehavior::CursorTop => {
14101 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14102 }
14103 SelectSyntaxNodeScrollBehavior::FitSelection => {
14104 self.request_autoscroll(Autoscroll::fit(), cx);
14105 }
14106 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14107 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14108 }
14109 }
14110 }
14111 }
14112
14113 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14114 if !EditorSettings::get_global(cx).gutter.runnables {
14115 self.clear_tasks();
14116 return Task::ready(());
14117 }
14118 let project = self.project.as_ref().map(Entity::downgrade);
14119 let task_sources = self.lsp_task_sources(cx);
14120 let multi_buffer = self.buffer.downgrade();
14121 cx.spawn_in(window, async move |editor, cx| {
14122 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14123 let Some(project) = project.and_then(|p| p.upgrade()) else {
14124 return;
14125 };
14126 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14127 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14128 }) else {
14129 return;
14130 };
14131
14132 let hide_runnables = project
14133 .update(cx, |project, cx| {
14134 // Do not display any test indicators in non-dev server remote projects.
14135 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14136 })
14137 .unwrap_or(true);
14138 if hide_runnables {
14139 return;
14140 }
14141 let new_rows =
14142 cx.background_spawn({
14143 let snapshot = display_snapshot.clone();
14144 async move {
14145 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14146 }
14147 })
14148 .await;
14149 let Ok(lsp_tasks) =
14150 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14151 else {
14152 return;
14153 };
14154 let lsp_tasks = lsp_tasks.await;
14155
14156 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14157 lsp_tasks
14158 .into_iter()
14159 .flat_map(|(kind, tasks)| {
14160 tasks.into_iter().filter_map(move |(location, task)| {
14161 Some((kind.clone(), location?, task))
14162 })
14163 })
14164 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14165 let buffer = location.target.buffer;
14166 let buffer_snapshot = buffer.read(cx).snapshot();
14167 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14168 |(excerpt_id, snapshot, _)| {
14169 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14170 display_snapshot
14171 .buffer_snapshot
14172 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14173 } else {
14174 None
14175 }
14176 },
14177 );
14178 if let Some(offset) = offset {
14179 let task_buffer_range =
14180 location.target.range.to_point(&buffer_snapshot);
14181 let context_buffer_range =
14182 task_buffer_range.to_offset(&buffer_snapshot);
14183 let context_range = BufferOffset(context_buffer_range.start)
14184 ..BufferOffset(context_buffer_range.end);
14185
14186 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14187 .or_insert_with(|| RunnableTasks {
14188 templates: Vec::new(),
14189 offset,
14190 column: task_buffer_range.start.column,
14191 extra_variables: HashMap::default(),
14192 context_range,
14193 })
14194 .templates
14195 .push((kind, task.original_task().clone()));
14196 }
14197
14198 acc
14199 })
14200 }) else {
14201 return;
14202 };
14203
14204 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14205 buffer.language_settings(cx).tasks.prefer_lsp
14206 }) else {
14207 return;
14208 };
14209
14210 let rows = Self::runnable_rows(
14211 project,
14212 display_snapshot,
14213 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14214 new_rows,
14215 cx.clone(),
14216 )
14217 .await;
14218 editor
14219 .update(cx, |editor, _| {
14220 editor.clear_tasks();
14221 for (key, mut value) in rows {
14222 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14223 value.templates.extend(lsp_tasks.templates);
14224 }
14225
14226 editor.insert_tasks(key, value);
14227 }
14228 for (key, value) in lsp_tasks_by_rows {
14229 editor.insert_tasks(key, value);
14230 }
14231 })
14232 .ok();
14233 })
14234 }
14235 fn fetch_runnable_ranges(
14236 snapshot: &DisplaySnapshot,
14237 range: Range<Anchor>,
14238 ) -> Vec<language::RunnableRange> {
14239 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14240 }
14241
14242 fn runnable_rows(
14243 project: Entity<Project>,
14244 snapshot: DisplaySnapshot,
14245 prefer_lsp: bool,
14246 runnable_ranges: Vec<RunnableRange>,
14247 cx: AsyncWindowContext,
14248 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14249 cx.spawn(async move |cx| {
14250 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14251 for mut runnable in runnable_ranges {
14252 let Some(tasks) = cx
14253 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14254 .ok()
14255 else {
14256 continue;
14257 };
14258 let mut tasks = tasks.await;
14259
14260 if prefer_lsp {
14261 tasks.retain(|(task_kind, _)| {
14262 !matches!(task_kind, TaskSourceKind::Language { .. })
14263 });
14264 }
14265 if tasks.is_empty() {
14266 continue;
14267 }
14268
14269 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14270 let Some(row) = snapshot
14271 .buffer_snapshot
14272 .buffer_line_for_row(MultiBufferRow(point.row))
14273 .map(|(_, range)| range.start.row)
14274 else {
14275 continue;
14276 };
14277
14278 let context_range =
14279 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14280 runnable_rows.push((
14281 (runnable.buffer_id, row),
14282 RunnableTasks {
14283 templates: tasks,
14284 offset: snapshot
14285 .buffer_snapshot
14286 .anchor_before(runnable.run_range.start),
14287 context_range,
14288 column: point.column,
14289 extra_variables: runnable.extra_captures,
14290 },
14291 ));
14292 }
14293 runnable_rows
14294 })
14295 }
14296
14297 fn templates_with_tags(
14298 project: &Entity<Project>,
14299 runnable: &mut Runnable,
14300 cx: &mut App,
14301 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14302 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14303 let (worktree_id, file) = project
14304 .buffer_for_id(runnable.buffer, cx)
14305 .and_then(|buffer| buffer.read(cx).file())
14306 .map(|file| (file.worktree_id(cx), file.clone()))
14307 .unzip();
14308
14309 (
14310 project.task_store().read(cx).task_inventory().cloned(),
14311 worktree_id,
14312 file,
14313 )
14314 });
14315
14316 let tags = mem::take(&mut runnable.tags);
14317 let language = runnable.language.clone();
14318 cx.spawn(async move |cx| {
14319 let mut templates_with_tags = Vec::new();
14320 if let Some(inventory) = inventory {
14321 for RunnableTag(tag) in tags {
14322 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14323 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14324 }) else {
14325 return templates_with_tags;
14326 };
14327 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14328 move |(_, template)| {
14329 template.tags.iter().any(|source_tag| source_tag == &tag)
14330 },
14331 ));
14332 }
14333 }
14334 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14335
14336 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14337 // Strongest source wins; if we have worktree tag binding, prefer that to
14338 // global and language bindings;
14339 // if we have a global binding, prefer that to language binding.
14340 let first_mismatch = templates_with_tags
14341 .iter()
14342 .position(|(tag_source, _)| tag_source != leading_tag_source);
14343 if let Some(index) = first_mismatch {
14344 templates_with_tags.truncate(index);
14345 }
14346 }
14347
14348 templates_with_tags
14349 })
14350 }
14351
14352 pub fn move_to_enclosing_bracket(
14353 &mut self,
14354 _: &MoveToEnclosingBracket,
14355 window: &mut Window,
14356 cx: &mut Context<Self>,
14357 ) {
14358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14359 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14360 s.move_offsets_with(|snapshot, selection| {
14361 let Some(enclosing_bracket_ranges) =
14362 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14363 else {
14364 return;
14365 };
14366
14367 let mut best_length = usize::MAX;
14368 let mut best_inside = false;
14369 let mut best_in_bracket_range = false;
14370 let mut best_destination = None;
14371 for (open, close) in enclosing_bracket_ranges {
14372 let close = close.to_inclusive();
14373 let length = close.end() - open.start;
14374 let inside = selection.start >= open.end && selection.end <= *close.start();
14375 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14376 || close.contains(&selection.head());
14377
14378 // If best is next to a bracket and current isn't, skip
14379 if !in_bracket_range && best_in_bracket_range {
14380 continue;
14381 }
14382
14383 // Prefer smaller lengths unless best is inside and current isn't
14384 if length > best_length && (best_inside || !inside) {
14385 continue;
14386 }
14387
14388 best_length = length;
14389 best_inside = inside;
14390 best_in_bracket_range = in_bracket_range;
14391 best_destination = Some(
14392 if close.contains(&selection.start) && close.contains(&selection.end) {
14393 if inside { open.end } else { open.start }
14394 } else if inside {
14395 *close.start()
14396 } else {
14397 *close.end()
14398 },
14399 );
14400 }
14401
14402 if let Some(destination) = best_destination {
14403 selection.collapse_to(destination, SelectionGoal::None);
14404 }
14405 })
14406 });
14407 }
14408
14409 pub fn undo_selection(
14410 &mut self,
14411 _: &UndoSelection,
14412 window: &mut Window,
14413 cx: &mut Context<Self>,
14414 ) {
14415 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14416 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14417 self.selection_history.mode = SelectionHistoryMode::Undoing;
14418 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14419 this.end_selection(window, cx);
14420 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14421 s.select_anchors(entry.selections.to_vec())
14422 });
14423 });
14424 self.selection_history.mode = SelectionHistoryMode::Normal;
14425
14426 self.select_next_state = entry.select_next_state;
14427 self.select_prev_state = entry.select_prev_state;
14428 self.add_selections_state = entry.add_selections_state;
14429 }
14430 }
14431
14432 pub fn redo_selection(
14433 &mut self,
14434 _: &RedoSelection,
14435 window: &mut Window,
14436 cx: &mut Context<Self>,
14437 ) {
14438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14439 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14440 self.selection_history.mode = SelectionHistoryMode::Redoing;
14441 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14442 this.end_selection(window, cx);
14443 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14444 s.select_anchors(entry.selections.to_vec())
14445 });
14446 });
14447 self.selection_history.mode = SelectionHistoryMode::Normal;
14448
14449 self.select_next_state = entry.select_next_state;
14450 self.select_prev_state = entry.select_prev_state;
14451 self.add_selections_state = entry.add_selections_state;
14452 }
14453 }
14454
14455 pub fn expand_excerpts(
14456 &mut self,
14457 action: &ExpandExcerpts,
14458 _: &mut Window,
14459 cx: &mut Context<Self>,
14460 ) {
14461 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14462 }
14463
14464 pub fn expand_excerpts_down(
14465 &mut self,
14466 action: &ExpandExcerptsDown,
14467 _: &mut Window,
14468 cx: &mut Context<Self>,
14469 ) {
14470 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14471 }
14472
14473 pub fn expand_excerpts_up(
14474 &mut self,
14475 action: &ExpandExcerptsUp,
14476 _: &mut Window,
14477 cx: &mut Context<Self>,
14478 ) {
14479 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14480 }
14481
14482 pub fn expand_excerpts_for_direction(
14483 &mut self,
14484 lines: u32,
14485 direction: ExpandExcerptDirection,
14486
14487 cx: &mut Context<Self>,
14488 ) {
14489 let selections = self.selections.disjoint_anchors();
14490
14491 let lines = if lines == 0 {
14492 EditorSettings::get_global(cx).expand_excerpt_lines
14493 } else {
14494 lines
14495 };
14496
14497 self.buffer.update(cx, |buffer, cx| {
14498 let snapshot = buffer.snapshot(cx);
14499 let mut excerpt_ids = selections
14500 .iter()
14501 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14502 .collect::<Vec<_>>();
14503 excerpt_ids.sort();
14504 excerpt_ids.dedup();
14505 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14506 })
14507 }
14508
14509 pub fn expand_excerpt(
14510 &mut self,
14511 excerpt: ExcerptId,
14512 direction: ExpandExcerptDirection,
14513 window: &mut Window,
14514 cx: &mut Context<Self>,
14515 ) {
14516 let current_scroll_position = self.scroll_position(cx);
14517 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14518 let mut should_scroll_up = false;
14519
14520 if direction == ExpandExcerptDirection::Down {
14521 let multi_buffer = self.buffer.read(cx);
14522 let snapshot = multi_buffer.snapshot(cx);
14523 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14524 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14525 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14526 let buffer_snapshot = buffer.read(cx).snapshot();
14527 let excerpt_end_row =
14528 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14529 let last_row = buffer_snapshot.max_point().row;
14530 let lines_below = last_row.saturating_sub(excerpt_end_row);
14531 should_scroll_up = lines_below >= lines_to_expand;
14532 }
14533 }
14534 }
14535 }
14536
14537 self.buffer.update(cx, |buffer, cx| {
14538 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14539 });
14540
14541 if should_scroll_up {
14542 let new_scroll_position =
14543 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14544 self.set_scroll_position(new_scroll_position, window, cx);
14545 }
14546 }
14547
14548 pub fn go_to_singleton_buffer_point(
14549 &mut self,
14550 point: Point,
14551 window: &mut Window,
14552 cx: &mut Context<Self>,
14553 ) {
14554 self.go_to_singleton_buffer_range(point..point, window, cx);
14555 }
14556
14557 pub fn go_to_singleton_buffer_range(
14558 &mut self,
14559 range: Range<Point>,
14560 window: &mut Window,
14561 cx: &mut Context<Self>,
14562 ) {
14563 let multibuffer = self.buffer().read(cx);
14564 let Some(buffer) = multibuffer.as_singleton() else {
14565 return;
14566 };
14567 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14568 return;
14569 };
14570 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14571 return;
14572 };
14573 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14574 s.select_anchor_ranges([start..end])
14575 });
14576 }
14577
14578 pub fn go_to_diagnostic(
14579 &mut self,
14580 _: &GoToDiagnostic,
14581 window: &mut Window,
14582 cx: &mut Context<Self>,
14583 ) {
14584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14585 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14586 }
14587
14588 pub fn go_to_prev_diagnostic(
14589 &mut self,
14590 _: &GoToPreviousDiagnostic,
14591 window: &mut Window,
14592 cx: &mut Context<Self>,
14593 ) {
14594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14595 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14596 }
14597
14598 pub fn go_to_diagnostic_impl(
14599 &mut self,
14600 direction: Direction,
14601 window: &mut Window,
14602 cx: &mut Context<Self>,
14603 ) {
14604 let buffer = self.buffer.read(cx).snapshot(cx);
14605 let selection = self.selections.newest::<usize>(cx);
14606
14607 let mut active_group_id = None;
14608 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14609 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14610 active_group_id = Some(active_group.group_id);
14611 }
14612 }
14613
14614 fn filtered(
14615 snapshot: EditorSnapshot,
14616 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14617 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14618 diagnostics
14619 .filter(|entry| entry.range.start != entry.range.end)
14620 .filter(|entry| !entry.diagnostic.is_unnecessary)
14621 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14622 }
14623
14624 let snapshot = self.snapshot(window, cx);
14625 let before = filtered(
14626 snapshot.clone(),
14627 buffer
14628 .diagnostics_in_range(0..selection.start)
14629 .filter(|entry| entry.range.start <= selection.start),
14630 );
14631 let after = filtered(
14632 snapshot,
14633 buffer
14634 .diagnostics_in_range(selection.start..buffer.len())
14635 .filter(|entry| entry.range.start >= selection.start),
14636 );
14637
14638 let mut found: Option<DiagnosticEntry<usize>> = None;
14639 if direction == Direction::Prev {
14640 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14641 {
14642 for diagnostic in prev_diagnostics.into_iter().rev() {
14643 if diagnostic.range.start != selection.start
14644 || active_group_id
14645 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14646 {
14647 found = Some(diagnostic);
14648 break 'outer;
14649 }
14650 }
14651 }
14652 } else {
14653 for diagnostic in after.chain(before) {
14654 if diagnostic.range.start != selection.start
14655 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14656 {
14657 found = Some(diagnostic);
14658 break;
14659 }
14660 }
14661 }
14662 let Some(next_diagnostic) = found else {
14663 return;
14664 };
14665
14666 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14667 return;
14668 };
14669 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14670 s.select_ranges(vec![
14671 next_diagnostic.range.start..next_diagnostic.range.start,
14672 ])
14673 });
14674 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14675 self.refresh_inline_completion(false, true, window, cx);
14676 }
14677
14678 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14680 let snapshot = self.snapshot(window, cx);
14681 let selection = self.selections.newest::<Point>(cx);
14682 self.go_to_hunk_before_or_after_position(
14683 &snapshot,
14684 selection.head(),
14685 Direction::Next,
14686 window,
14687 cx,
14688 );
14689 }
14690
14691 pub fn go_to_hunk_before_or_after_position(
14692 &mut self,
14693 snapshot: &EditorSnapshot,
14694 position: Point,
14695 direction: Direction,
14696 window: &mut Window,
14697 cx: &mut Context<Editor>,
14698 ) {
14699 let row = if direction == Direction::Next {
14700 self.hunk_after_position(snapshot, position)
14701 .map(|hunk| hunk.row_range.start)
14702 } else {
14703 self.hunk_before_position(snapshot, position)
14704 };
14705
14706 if let Some(row) = row {
14707 let destination = Point::new(row.0, 0);
14708 let autoscroll = Autoscroll::center();
14709
14710 self.unfold_ranges(&[destination..destination], false, false, cx);
14711 self.change_selections(Some(autoscroll), window, cx, |s| {
14712 s.select_ranges([destination..destination]);
14713 });
14714 }
14715 }
14716
14717 fn hunk_after_position(
14718 &mut self,
14719 snapshot: &EditorSnapshot,
14720 position: Point,
14721 ) -> Option<MultiBufferDiffHunk> {
14722 snapshot
14723 .buffer_snapshot
14724 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14725 .find(|hunk| hunk.row_range.start.0 > position.row)
14726 .or_else(|| {
14727 snapshot
14728 .buffer_snapshot
14729 .diff_hunks_in_range(Point::zero()..position)
14730 .find(|hunk| hunk.row_range.end.0 < position.row)
14731 })
14732 }
14733
14734 fn go_to_prev_hunk(
14735 &mut self,
14736 _: &GoToPreviousHunk,
14737 window: &mut Window,
14738 cx: &mut Context<Self>,
14739 ) {
14740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14741 let snapshot = self.snapshot(window, cx);
14742 let selection = self.selections.newest::<Point>(cx);
14743 self.go_to_hunk_before_or_after_position(
14744 &snapshot,
14745 selection.head(),
14746 Direction::Prev,
14747 window,
14748 cx,
14749 );
14750 }
14751
14752 fn hunk_before_position(
14753 &mut self,
14754 snapshot: &EditorSnapshot,
14755 position: Point,
14756 ) -> Option<MultiBufferRow> {
14757 snapshot
14758 .buffer_snapshot
14759 .diff_hunk_before(position)
14760 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14761 }
14762
14763 fn go_to_next_change(
14764 &mut self,
14765 _: &GoToNextChange,
14766 window: &mut Window,
14767 cx: &mut Context<Self>,
14768 ) {
14769 if let Some(selections) = self
14770 .change_list
14771 .next_change(1, Direction::Next)
14772 .map(|s| s.to_vec())
14773 {
14774 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14775 let map = s.display_map();
14776 s.select_display_ranges(selections.iter().map(|a| {
14777 let point = a.to_display_point(&map);
14778 point..point
14779 }))
14780 })
14781 }
14782 }
14783
14784 fn go_to_previous_change(
14785 &mut self,
14786 _: &GoToPreviousChange,
14787 window: &mut Window,
14788 cx: &mut Context<Self>,
14789 ) {
14790 if let Some(selections) = self
14791 .change_list
14792 .next_change(1, Direction::Prev)
14793 .map(|s| s.to_vec())
14794 {
14795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14796 let map = s.display_map();
14797 s.select_display_ranges(selections.iter().map(|a| {
14798 let point = a.to_display_point(&map);
14799 point..point
14800 }))
14801 })
14802 }
14803 }
14804
14805 fn go_to_line<T: 'static>(
14806 &mut self,
14807 position: Anchor,
14808 highlight_color: Option<Hsla>,
14809 window: &mut Window,
14810 cx: &mut Context<Self>,
14811 ) {
14812 let snapshot = self.snapshot(window, cx).display_snapshot;
14813 let position = position.to_point(&snapshot.buffer_snapshot);
14814 let start = snapshot
14815 .buffer_snapshot
14816 .clip_point(Point::new(position.row, 0), Bias::Left);
14817 let end = start + Point::new(1, 0);
14818 let start = snapshot.buffer_snapshot.anchor_before(start);
14819 let end = snapshot.buffer_snapshot.anchor_before(end);
14820
14821 self.highlight_rows::<T>(
14822 start..end,
14823 highlight_color
14824 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14825 Default::default(),
14826 cx,
14827 );
14828
14829 if self.buffer.read(cx).is_singleton() {
14830 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14831 }
14832 }
14833
14834 pub fn go_to_definition(
14835 &mut self,
14836 _: &GoToDefinition,
14837 window: &mut Window,
14838 cx: &mut Context<Self>,
14839 ) -> Task<Result<Navigated>> {
14840 let definition =
14841 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14842 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14843 cx.spawn_in(window, async move |editor, cx| {
14844 if definition.await? == Navigated::Yes {
14845 return Ok(Navigated::Yes);
14846 }
14847 match fallback_strategy {
14848 GoToDefinitionFallback::None => Ok(Navigated::No),
14849 GoToDefinitionFallback::FindAllReferences => {
14850 match editor.update_in(cx, |editor, window, cx| {
14851 editor.find_all_references(&FindAllReferences, window, cx)
14852 })? {
14853 Some(references) => references.await,
14854 None => Ok(Navigated::No),
14855 }
14856 }
14857 }
14858 })
14859 }
14860
14861 pub fn go_to_declaration(
14862 &mut self,
14863 _: &GoToDeclaration,
14864 window: &mut Window,
14865 cx: &mut Context<Self>,
14866 ) -> Task<Result<Navigated>> {
14867 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14868 }
14869
14870 pub fn go_to_declaration_split(
14871 &mut self,
14872 _: &GoToDeclaration,
14873 window: &mut Window,
14874 cx: &mut Context<Self>,
14875 ) -> Task<Result<Navigated>> {
14876 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14877 }
14878
14879 pub fn go_to_implementation(
14880 &mut self,
14881 _: &GoToImplementation,
14882 window: &mut Window,
14883 cx: &mut Context<Self>,
14884 ) -> Task<Result<Navigated>> {
14885 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14886 }
14887
14888 pub fn go_to_implementation_split(
14889 &mut self,
14890 _: &GoToImplementationSplit,
14891 window: &mut Window,
14892 cx: &mut Context<Self>,
14893 ) -> Task<Result<Navigated>> {
14894 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14895 }
14896
14897 pub fn go_to_type_definition(
14898 &mut self,
14899 _: &GoToTypeDefinition,
14900 window: &mut Window,
14901 cx: &mut Context<Self>,
14902 ) -> Task<Result<Navigated>> {
14903 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14904 }
14905
14906 pub fn go_to_definition_split(
14907 &mut self,
14908 _: &GoToDefinitionSplit,
14909 window: &mut Window,
14910 cx: &mut Context<Self>,
14911 ) -> Task<Result<Navigated>> {
14912 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14913 }
14914
14915 pub fn go_to_type_definition_split(
14916 &mut self,
14917 _: &GoToTypeDefinitionSplit,
14918 window: &mut Window,
14919 cx: &mut Context<Self>,
14920 ) -> Task<Result<Navigated>> {
14921 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14922 }
14923
14924 fn go_to_definition_of_kind(
14925 &mut self,
14926 kind: GotoDefinitionKind,
14927 split: bool,
14928 window: &mut Window,
14929 cx: &mut Context<Self>,
14930 ) -> Task<Result<Navigated>> {
14931 let Some(provider) = self.semantics_provider.clone() else {
14932 return Task::ready(Ok(Navigated::No));
14933 };
14934 let head = self.selections.newest::<usize>(cx).head();
14935 let buffer = self.buffer.read(cx);
14936 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14937 text_anchor
14938 } else {
14939 return Task::ready(Ok(Navigated::No));
14940 };
14941
14942 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14943 return Task::ready(Ok(Navigated::No));
14944 };
14945
14946 cx.spawn_in(window, async move |editor, cx| {
14947 let definitions = definitions.await?;
14948 let navigated = editor
14949 .update_in(cx, |editor, window, cx| {
14950 editor.navigate_to_hover_links(
14951 Some(kind),
14952 definitions
14953 .into_iter()
14954 .filter(|location| {
14955 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14956 })
14957 .map(HoverLink::Text)
14958 .collect::<Vec<_>>(),
14959 split,
14960 window,
14961 cx,
14962 )
14963 })?
14964 .await?;
14965 anyhow::Ok(navigated)
14966 })
14967 }
14968
14969 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14970 let selection = self.selections.newest_anchor();
14971 let head = selection.head();
14972 let tail = selection.tail();
14973
14974 let Some((buffer, start_position)) =
14975 self.buffer.read(cx).text_anchor_for_position(head, cx)
14976 else {
14977 return;
14978 };
14979
14980 let end_position = if head != tail {
14981 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14982 return;
14983 };
14984 Some(pos)
14985 } else {
14986 None
14987 };
14988
14989 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14990 let url = if let Some(end_pos) = end_position {
14991 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14992 } else {
14993 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14994 };
14995
14996 if let Some(url) = url {
14997 editor.update(cx, |_, cx| {
14998 cx.open_url(&url);
14999 })
15000 } else {
15001 Ok(())
15002 }
15003 });
15004
15005 url_finder.detach();
15006 }
15007
15008 pub fn open_selected_filename(
15009 &mut self,
15010 _: &OpenSelectedFilename,
15011 window: &mut Window,
15012 cx: &mut Context<Self>,
15013 ) {
15014 let Some(workspace) = self.workspace() else {
15015 return;
15016 };
15017
15018 let position = self.selections.newest_anchor().head();
15019
15020 let Some((buffer, buffer_position)) =
15021 self.buffer.read(cx).text_anchor_for_position(position, cx)
15022 else {
15023 return;
15024 };
15025
15026 let project = self.project.clone();
15027
15028 cx.spawn_in(window, async move |_, cx| {
15029 let result = find_file(&buffer, project, buffer_position, cx).await;
15030
15031 if let Some((_, path)) = result {
15032 workspace
15033 .update_in(cx, |workspace, window, cx| {
15034 workspace.open_resolved_path(path, window, cx)
15035 })?
15036 .await?;
15037 }
15038 anyhow::Ok(())
15039 })
15040 .detach();
15041 }
15042
15043 pub(crate) fn navigate_to_hover_links(
15044 &mut self,
15045 kind: Option<GotoDefinitionKind>,
15046 mut definitions: Vec<HoverLink>,
15047 split: bool,
15048 window: &mut Window,
15049 cx: &mut Context<Editor>,
15050 ) -> Task<Result<Navigated>> {
15051 // If there is one definition, just open it directly
15052 if definitions.len() == 1 {
15053 let definition = definitions.pop().unwrap();
15054
15055 enum TargetTaskResult {
15056 Location(Option<Location>),
15057 AlreadyNavigated,
15058 }
15059
15060 let target_task = match definition {
15061 HoverLink::Text(link) => {
15062 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15063 }
15064 HoverLink::InlayHint(lsp_location, server_id) => {
15065 let computation =
15066 self.compute_target_location(lsp_location, server_id, window, cx);
15067 cx.background_spawn(async move {
15068 let location = computation.await?;
15069 Ok(TargetTaskResult::Location(location))
15070 })
15071 }
15072 HoverLink::Url(url) => {
15073 cx.open_url(&url);
15074 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15075 }
15076 HoverLink::File(path) => {
15077 if let Some(workspace) = self.workspace() {
15078 cx.spawn_in(window, async move |_, cx| {
15079 workspace
15080 .update_in(cx, |workspace, window, cx| {
15081 workspace.open_resolved_path(path, window, cx)
15082 })?
15083 .await
15084 .map(|_| TargetTaskResult::AlreadyNavigated)
15085 })
15086 } else {
15087 Task::ready(Ok(TargetTaskResult::Location(None)))
15088 }
15089 }
15090 };
15091 cx.spawn_in(window, async move |editor, cx| {
15092 let target = match target_task.await.context("target resolution task")? {
15093 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15094 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15095 TargetTaskResult::Location(Some(target)) => target,
15096 };
15097
15098 editor.update_in(cx, |editor, window, cx| {
15099 let Some(workspace) = editor.workspace() else {
15100 return Navigated::No;
15101 };
15102 let pane = workspace.read(cx).active_pane().clone();
15103
15104 let range = target.range.to_point(target.buffer.read(cx));
15105 let range = editor.range_for_match(&range);
15106 let range = collapse_multiline_range(range);
15107
15108 if !split
15109 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15110 {
15111 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15112 } else {
15113 window.defer(cx, move |window, cx| {
15114 let target_editor: Entity<Self> =
15115 workspace.update(cx, |workspace, cx| {
15116 let pane = if split {
15117 workspace.adjacent_pane(window, cx)
15118 } else {
15119 workspace.active_pane().clone()
15120 };
15121
15122 workspace.open_project_item(
15123 pane,
15124 target.buffer.clone(),
15125 true,
15126 true,
15127 window,
15128 cx,
15129 )
15130 });
15131 target_editor.update(cx, |target_editor, cx| {
15132 // When selecting a definition in a different buffer, disable the nav history
15133 // to avoid creating a history entry at the previous cursor location.
15134 pane.update(cx, |pane, _| pane.disable_history());
15135 target_editor.go_to_singleton_buffer_range(range, window, cx);
15136 pane.update(cx, |pane, _| pane.enable_history());
15137 });
15138 });
15139 }
15140 Navigated::Yes
15141 })
15142 })
15143 } else if !definitions.is_empty() {
15144 cx.spawn_in(window, async move |editor, cx| {
15145 let (title, location_tasks, workspace) = editor
15146 .update_in(cx, |editor, window, cx| {
15147 let tab_kind = match kind {
15148 Some(GotoDefinitionKind::Implementation) => "Implementations",
15149 _ => "Definitions",
15150 };
15151 let title = definitions
15152 .iter()
15153 .find_map(|definition| match definition {
15154 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15155 let buffer = origin.buffer.read(cx);
15156 format!(
15157 "{} for {}",
15158 tab_kind,
15159 buffer
15160 .text_for_range(origin.range.clone())
15161 .collect::<String>()
15162 )
15163 }),
15164 HoverLink::InlayHint(_, _) => None,
15165 HoverLink::Url(_) => None,
15166 HoverLink::File(_) => None,
15167 })
15168 .unwrap_or(tab_kind.to_string());
15169 let location_tasks = definitions
15170 .into_iter()
15171 .map(|definition| match definition {
15172 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15173 HoverLink::InlayHint(lsp_location, server_id) => editor
15174 .compute_target_location(lsp_location, server_id, window, cx),
15175 HoverLink::Url(_) => Task::ready(Ok(None)),
15176 HoverLink::File(_) => Task::ready(Ok(None)),
15177 })
15178 .collect::<Vec<_>>();
15179 (title, location_tasks, editor.workspace().clone())
15180 })
15181 .context("location tasks preparation")?;
15182
15183 let locations: Vec<Location> = future::join_all(location_tasks)
15184 .await
15185 .into_iter()
15186 .filter_map(|location| location.transpose())
15187 .collect::<Result<_>>()
15188 .context("location tasks")?;
15189
15190 if locations.is_empty() {
15191 return Ok(Navigated::No);
15192 }
15193
15194 let Some(workspace) = workspace else {
15195 return Ok(Navigated::No);
15196 };
15197
15198 let opened = workspace
15199 .update_in(cx, |workspace, window, cx| {
15200 Self::open_locations_in_multibuffer(
15201 workspace,
15202 locations,
15203 title,
15204 split,
15205 MultibufferSelectionMode::First,
15206 window,
15207 cx,
15208 )
15209 })
15210 .ok();
15211
15212 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15213 })
15214 } else {
15215 Task::ready(Ok(Navigated::No))
15216 }
15217 }
15218
15219 fn compute_target_location(
15220 &self,
15221 lsp_location: lsp::Location,
15222 server_id: LanguageServerId,
15223 window: &mut Window,
15224 cx: &mut Context<Self>,
15225 ) -> Task<anyhow::Result<Option<Location>>> {
15226 let Some(project) = self.project.clone() else {
15227 return Task::ready(Ok(None));
15228 };
15229
15230 cx.spawn_in(window, async move |editor, cx| {
15231 let location_task = editor.update(cx, |_, cx| {
15232 project.update(cx, |project, cx| {
15233 let language_server_name = project
15234 .language_server_statuses(cx)
15235 .find(|(id, _)| server_id == *id)
15236 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15237 language_server_name.map(|language_server_name| {
15238 project.open_local_buffer_via_lsp(
15239 lsp_location.uri.clone(),
15240 server_id,
15241 language_server_name,
15242 cx,
15243 )
15244 })
15245 })
15246 })?;
15247 let location = match location_task {
15248 Some(task) => Some({
15249 let target_buffer_handle = task.await.context("open local buffer")?;
15250 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15251 let target_start = target_buffer
15252 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15253 let target_end = target_buffer
15254 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15255 target_buffer.anchor_after(target_start)
15256 ..target_buffer.anchor_before(target_end)
15257 })?;
15258 Location {
15259 buffer: target_buffer_handle,
15260 range,
15261 }
15262 }),
15263 None => None,
15264 };
15265 Ok(location)
15266 })
15267 }
15268
15269 pub fn find_all_references(
15270 &mut self,
15271 _: &FindAllReferences,
15272 window: &mut Window,
15273 cx: &mut Context<Self>,
15274 ) -> Option<Task<Result<Navigated>>> {
15275 let selection = self.selections.newest::<usize>(cx);
15276 let multi_buffer = self.buffer.read(cx);
15277 let head = selection.head();
15278
15279 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15280 let head_anchor = multi_buffer_snapshot.anchor_at(
15281 head,
15282 if head < selection.tail() {
15283 Bias::Right
15284 } else {
15285 Bias::Left
15286 },
15287 );
15288
15289 match self
15290 .find_all_references_task_sources
15291 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15292 {
15293 Ok(_) => {
15294 log::info!(
15295 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15296 );
15297 return None;
15298 }
15299 Err(i) => {
15300 self.find_all_references_task_sources.insert(i, head_anchor);
15301 }
15302 }
15303
15304 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15305 let workspace = self.workspace()?;
15306 let project = workspace.read(cx).project().clone();
15307 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15308 Some(cx.spawn_in(window, async move |editor, cx| {
15309 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15310 if let Ok(i) = editor
15311 .find_all_references_task_sources
15312 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15313 {
15314 editor.find_all_references_task_sources.remove(i);
15315 }
15316 });
15317
15318 let locations = references.await?;
15319 if locations.is_empty() {
15320 return anyhow::Ok(Navigated::No);
15321 }
15322
15323 workspace.update_in(cx, |workspace, window, cx| {
15324 let title = locations
15325 .first()
15326 .as_ref()
15327 .map(|location| {
15328 let buffer = location.buffer.read(cx);
15329 format!(
15330 "References to `{}`",
15331 buffer
15332 .text_for_range(location.range.clone())
15333 .collect::<String>()
15334 )
15335 })
15336 .unwrap();
15337 Self::open_locations_in_multibuffer(
15338 workspace,
15339 locations,
15340 title,
15341 false,
15342 MultibufferSelectionMode::First,
15343 window,
15344 cx,
15345 );
15346 Navigated::Yes
15347 })
15348 }))
15349 }
15350
15351 /// Opens a multibuffer with the given project locations in it
15352 pub fn open_locations_in_multibuffer(
15353 workspace: &mut Workspace,
15354 mut locations: Vec<Location>,
15355 title: String,
15356 split: bool,
15357 multibuffer_selection_mode: MultibufferSelectionMode,
15358 window: &mut Window,
15359 cx: &mut Context<Workspace>,
15360 ) {
15361 if locations.is_empty() {
15362 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15363 return;
15364 }
15365
15366 // If there are multiple definitions, open them in a multibuffer
15367 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15368 let mut locations = locations.into_iter().peekable();
15369 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15370 let capability = workspace.project().read(cx).capability();
15371
15372 let excerpt_buffer = cx.new(|cx| {
15373 let mut multibuffer = MultiBuffer::new(capability);
15374 while let Some(location) = locations.next() {
15375 let buffer = location.buffer.read(cx);
15376 let mut ranges_for_buffer = Vec::new();
15377 let range = location.range.to_point(buffer);
15378 ranges_for_buffer.push(range.clone());
15379
15380 while let Some(next_location) = locations.peek() {
15381 if next_location.buffer == location.buffer {
15382 ranges_for_buffer.push(next_location.range.to_point(buffer));
15383 locations.next();
15384 } else {
15385 break;
15386 }
15387 }
15388
15389 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15390 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15391 PathKey::for_buffer(&location.buffer, cx),
15392 location.buffer.clone(),
15393 ranges_for_buffer,
15394 DEFAULT_MULTIBUFFER_CONTEXT,
15395 cx,
15396 );
15397 ranges.extend(new_ranges)
15398 }
15399
15400 multibuffer.with_title(title)
15401 });
15402
15403 let editor = cx.new(|cx| {
15404 Editor::for_multibuffer(
15405 excerpt_buffer,
15406 Some(workspace.project().clone()),
15407 window,
15408 cx,
15409 )
15410 });
15411 editor.update(cx, |editor, cx| {
15412 match multibuffer_selection_mode {
15413 MultibufferSelectionMode::First => {
15414 if let Some(first_range) = ranges.first() {
15415 editor.change_selections(None, window, cx, |selections| {
15416 selections.clear_disjoint();
15417 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15418 });
15419 }
15420 editor.highlight_background::<Self>(
15421 &ranges,
15422 |theme| theme.colors().editor_highlighted_line_background,
15423 cx,
15424 );
15425 }
15426 MultibufferSelectionMode::All => {
15427 editor.change_selections(None, window, cx, |selections| {
15428 selections.clear_disjoint();
15429 selections.select_anchor_ranges(ranges);
15430 });
15431 }
15432 }
15433 editor.register_buffers_with_language_servers(cx);
15434 });
15435
15436 let item = Box::new(editor);
15437 let item_id = item.item_id();
15438
15439 if split {
15440 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15441 } else {
15442 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15443 let (preview_item_id, preview_item_idx) =
15444 workspace.active_pane().read_with(cx, |pane, _| {
15445 (pane.preview_item_id(), pane.preview_item_idx())
15446 });
15447
15448 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15449
15450 if let Some(preview_item_id) = preview_item_id {
15451 workspace.active_pane().update(cx, |pane, cx| {
15452 pane.remove_item(preview_item_id, false, false, window, cx);
15453 });
15454 }
15455 } else {
15456 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15457 }
15458 }
15459 workspace.active_pane().update(cx, |pane, cx| {
15460 pane.set_preview_item_id(Some(item_id), cx);
15461 });
15462 }
15463
15464 pub fn rename(
15465 &mut self,
15466 _: &Rename,
15467 window: &mut Window,
15468 cx: &mut Context<Self>,
15469 ) -> Option<Task<Result<()>>> {
15470 use language::ToOffset as _;
15471
15472 let provider = self.semantics_provider.clone()?;
15473 let selection = self.selections.newest_anchor().clone();
15474 let (cursor_buffer, cursor_buffer_position) = self
15475 .buffer
15476 .read(cx)
15477 .text_anchor_for_position(selection.head(), cx)?;
15478 let (tail_buffer, cursor_buffer_position_end) = self
15479 .buffer
15480 .read(cx)
15481 .text_anchor_for_position(selection.tail(), cx)?;
15482 if tail_buffer != cursor_buffer {
15483 return None;
15484 }
15485
15486 let snapshot = cursor_buffer.read(cx).snapshot();
15487 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15488 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15489 let prepare_rename = provider
15490 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15491 .unwrap_or_else(|| Task::ready(Ok(None)));
15492 drop(snapshot);
15493
15494 Some(cx.spawn_in(window, async move |this, cx| {
15495 let rename_range = if let Some(range) = prepare_rename.await? {
15496 Some(range)
15497 } else {
15498 this.update(cx, |this, cx| {
15499 let buffer = this.buffer.read(cx).snapshot(cx);
15500 let mut buffer_highlights = this
15501 .document_highlights_for_position(selection.head(), &buffer)
15502 .filter(|highlight| {
15503 highlight.start.excerpt_id == selection.head().excerpt_id
15504 && highlight.end.excerpt_id == selection.head().excerpt_id
15505 });
15506 buffer_highlights
15507 .next()
15508 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15509 })?
15510 };
15511 if let Some(rename_range) = rename_range {
15512 this.update_in(cx, |this, window, cx| {
15513 let snapshot = cursor_buffer.read(cx).snapshot();
15514 let rename_buffer_range = rename_range.to_offset(&snapshot);
15515 let cursor_offset_in_rename_range =
15516 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15517 let cursor_offset_in_rename_range_end =
15518 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15519
15520 this.take_rename(false, window, cx);
15521 let buffer = this.buffer.read(cx).read(cx);
15522 let cursor_offset = selection.head().to_offset(&buffer);
15523 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15524 let rename_end = rename_start + rename_buffer_range.len();
15525 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15526 let mut old_highlight_id = None;
15527 let old_name: Arc<str> = buffer
15528 .chunks(rename_start..rename_end, true)
15529 .map(|chunk| {
15530 if old_highlight_id.is_none() {
15531 old_highlight_id = chunk.syntax_highlight_id;
15532 }
15533 chunk.text
15534 })
15535 .collect::<String>()
15536 .into();
15537
15538 drop(buffer);
15539
15540 // Position the selection in the rename editor so that it matches the current selection.
15541 this.show_local_selections = false;
15542 let rename_editor = cx.new(|cx| {
15543 let mut editor = Editor::single_line(window, cx);
15544 editor.buffer.update(cx, |buffer, cx| {
15545 buffer.edit([(0..0, old_name.clone())], None, cx)
15546 });
15547 let rename_selection_range = match cursor_offset_in_rename_range
15548 .cmp(&cursor_offset_in_rename_range_end)
15549 {
15550 Ordering::Equal => {
15551 editor.select_all(&SelectAll, window, cx);
15552 return editor;
15553 }
15554 Ordering::Less => {
15555 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15556 }
15557 Ordering::Greater => {
15558 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15559 }
15560 };
15561 if rename_selection_range.end > old_name.len() {
15562 editor.select_all(&SelectAll, window, cx);
15563 } else {
15564 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15565 s.select_ranges([rename_selection_range]);
15566 });
15567 }
15568 editor
15569 });
15570 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15571 if e == &EditorEvent::Focused {
15572 cx.emit(EditorEvent::FocusedIn)
15573 }
15574 })
15575 .detach();
15576
15577 let write_highlights =
15578 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15579 let read_highlights =
15580 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15581 let ranges = write_highlights
15582 .iter()
15583 .flat_map(|(_, ranges)| ranges.iter())
15584 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15585 .cloned()
15586 .collect();
15587
15588 this.highlight_text::<Rename>(
15589 ranges,
15590 HighlightStyle {
15591 fade_out: Some(0.6),
15592 ..Default::default()
15593 },
15594 cx,
15595 );
15596 let rename_focus_handle = rename_editor.focus_handle(cx);
15597 window.focus(&rename_focus_handle);
15598 let block_id = this.insert_blocks(
15599 [BlockProperties {
15600 style: BlockStyle::Flex,
15601 placement: BlockPlacement::Below(range.start),
15602 height: Some(1),
15603 render: Arc::new({
15604 let rename_editor = rename_editor.clone();
15605 move |cx: &mut BlockContext| {
15606 let mut text_style = cx.editor_style.text.clone();
15607 if let Some(highlight_style) = old_highlight_id
15608 .and_then(|h| h.style(&cx.editor_style.syntax))
15609 {
15610 text_style = text_style.highlight(highlight_style);
15611 }
15612 div()
15613 .block_mouse_except_scroll()
15614 .pl(cx.anchor_x)
15615 .child(EditorElement::new(
15616 &rename_editor,
15617 EditorStyle {
15618 background: cx.theme().system().transparent,
15619 local_player: cx.editor_style.local_player,
15620 text: text_style,
15621 scrollbar_width: cx.editor_style.scrollbar_width,
15622 syntax: cx.editor_style.syntax.clone(),
15623 status: cx.editor_style.status.clone(),
15624 inlay_hints_style: HighlightStyle {
15625 font_weight: Some(FontWeight::BOLD),
15626 ..make_inlay_hints_style(cx.app)
15627 },
15628 inline_completion_styles: make_suggestion_styles(
15629 cx.app,
15630 ),
15631 ..EditorStyle::default()
15632 },
15633 ))
15634 .into_any_element()
15635 }
15636 }),
15637 priority: 0,
15638 render_in_minimap: true,
15639 }],
15640 Some(Autoscroll::fit()),
15641 cx,
15642 )[0];
15643 this.pending_rename = Some(RenameState {
15644 range,
15645 old_name,
15646 editor: rename_editor,
15647 block_id,
15648 });
15649 })?;
15650 }
15651
15652 Ok(())
15653 }))
15654 }
15655
15656 pub fn confirm_rename(
15657 &mut self,
15658 _: &ConfirmRename,
15659 window: &mut Window,
15660 cx: &mut Context<Self>,
15661 ) -> Option<Task<Result<()>>> {
15662 let rename = self.take_rename(false, window, cx)?;
15663 let workspace = self.workspace()?.downgrade();
15664 let (buffer, start) = self
15665 .buffer
15666 .read(cx)
15667 .text_anchor_for_position(rename.range.start, cx)?;
15668 let (end_buffer, _) = self
15669 .buffer
15670 .read(cx)
15671 .text_anchor_for_position(rename.range.end, cx)?;
15672 if buffer != end_buffer {
15673 return None;
15674 }
15675
15676 let old_name = rename.old_name;
15677 let new_name = rename.editor.read(cx).text(cx);
15678
15679 let rename = self.semantics_provider.as_ref()?.perform_rename(
15680 &buffer,
15681 start,
15682 new_name.clone(),
15683 cx,
15684 )?;
15685
15686 Some(cx.spawn_in(window, async move |editor, cx| {
15687 let project_transaction = rename.await?;
15688 Self::open_project_transaction(
15689 &editor,
15690 workspace,
15691 project_transaction,
15692 format!("Rename: {} → {}", old_name, new_name),
15693 cx,
15694 )
15695 .await?;
15696
15697 editor.update(cx, |editor, cx| {
15698 editor.refresh_document_highlights(cx);
15699 })?;
15700 Ok(())
15701 }))
15702 }
15703
15704 fn take_rename(
15705 &mut self,
15706 moving_cursor: bool,
15707 window: &mut Window,
15708 cx: &mut Context<Self>,
15709 ) -> Option<RenameState> {
15710 let rename = self.pending_rename.take()?;
15711 if rename.editor.focus_handle(cx).is_focused(window) {
15712 window.focus(&self.focus_handle);
15713 }
15714
15715 self.remove_blocks(
15716 [rename.block_id].into_iter().collect(),
15717 Some(Autoscroll::fit()),
15718 cx,
15719 );
15720 self.clear_highlights::<Rename>(cx);
15721 self.show_local_selections = true;
15722
15723 if moving_cursor {
15724 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15725 editor.selections.newest::<usize>(cx).head()
15726 });
15727
15728 // Update the selection to match the position of the selection inside
15729 // the rename editor.
15730 let snapshot = self.buffer.read(cx).read(cx);
15731 let rename_range = rename.range.to_offset(&snapshot);
15732 let cursor_in_editor = snapshot
15733 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15734 .min(rename_range.end);
15735 drop(snapshot);
15736
15737 self.change_selections(None, window, cx, |s| {
15738 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15739 });
15740 } else {
15741 self.refresh_document_highlights(cx);
15742 }
15743
15744 Some(rename)
15745 }
15746
15747 pub fn pending_rename(&self) -> Option<&RenameState> {
15748 self.pending_rename.as_ref()
15749 }
15750
15751 fn format(
15752 &mut self,
15753 _: &Format,
15754 window: &mut Window,
15755 cx: &mut Context<Self>,
15756 ) -> Option<Task<Result<()>>> {
15757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15758
15759 let project = match &self.project {
15760 Some(project) => project.clone(),
15761 None => return None,
15762 };
15763
15764 Some(self.perform_format(
15765 project,
15766 FormatTrigger::Manual,
15767 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15768 window,
15769 cx,
15770 ))
15771 }
15772
15773 fn format_selections(
15774 &mut self,
15775 _: &FormatSelections,
15776 window: &mut Window,
15777 cx: &mut Context<Self>,
15778 ) -> Option<Task<Result<()>>> {
15779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15780
15781 let project = match &self.project {
15782 Some(project) => project.clone(),
15783 None => return None,
15784 };
15785
15786 let ranges = self
15787 .selections
15788 .all_adjusted(cx)
15789 .into_iter()
15790 .map(|selection| selection.range())
15791 .collect_vec();
15792
15793 Some(self.perform_format(
15794 project,
15795 FormatTrigger::Manual,
15796 FormatTarget::Ranges(ranges),
15797 window,
15798 cx,
15799 ))
15800 }
15801
15802 fn perform_format(
15803 &mut self,
15804 project: Entity<Project>,
15805 trigger: FormatTrigger,
15806 target: FormatTarget,
15807 window: &mut Window,
15808 cx: &mut Context<Self>,
15809 ) -> Task<Result<()>> {
15810 let buffer = self.buffer.clone();
15811 let (buffers, target) = match target {
15812 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15813 FormatTarget::Ranges(selection_ranges) => {
15814 let multi_buffer = buffer.read(cx);
15815 let snapshot = multi_buffer.read(cx);
15816 let mut buffers = HashSet::default();
15817 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15818 BTreeMap::new();
15819 for selection_range in selection_ranges {
15820 for (buffer, buffer_range, _) in
15821 snapshot.range_to_buffer_ranges(selection_range)
15822 {
15823 let buffer_id = buffer.remote_id();
15824 let start = buffer.anchor_before(buffer_range.start);
15825 let end = buffer.anchor_after(buffer_range.end);
15826 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15827 buffer_id_to_ranges
15828 .entry(buffer_id)
15829 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15830 .or_insert_with(|| vec![start..end]);
15831 }
15832 }
15833 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15834 }
15835 };
15836
15837 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15838 let selections_prev = transaction_id_prev
15839 .and_then(|transaction_id_prev| {
15840 // default to selections as they were after the last edit, if we have them,
15841 // instead of how they are now.
15842 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15843 // will take you back to where you made the last edit, instead of staying where you scrolled
15844 self.selection_history
15845 .transaction(transaction_id_prev)
15846 .map(|t| t.0.clone())
15847 })
15848 .unwrap_or_else(|| {
15849 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15850 self.selections.disjoint_anchors()
15851 });
15852
15853 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15854 let format = project.update(cx, |project, cx| {
15855 project.format(buffers, target, true, trigger, cx)
15856 });
15857
15858 cx.spawn_in(window, async move |editor, cx| {
15859 let transaction = futures::select_biased! {
15860 transaction = format.log_err().fuse() => transaction,
15861 () = timeout => {
15862 log::warn!("timed out waiting for formatting");
15863 None
15864 }
15865 };
15866
15867 buffer
15868 .update(cx, |buffer, cx| {
15869 if let Some(transaction) = transaction {
15870 if !buffer.is_singleton() {
15871 buffer.push_transaction(&transaction.0, cx);
15872 }
15873 }
15874 cx.notify();
15875 })
15876 .ok();
15877
15878 if let Some(transaction_id_now) =
15879 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15880 {
15881 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15882 if has_new_transaction {
15883 _ = editor.update(cx, |editor, _| {
15884 editor
15885 .selection_history
15886 .insert_transaction(transaction_id_now, selections_prev);
15887 });
15888 }
15889 }
15890
15891 Ok(())
15892 })
15893 }
15894
15895 fn organize_imports(
15896 &mut self,
15897 _: &OrganizeImports,
15898 window: &mut Window,
15899 cx: &mut Context<Self>,
15900 ) -> Option<Task<Result<()>>> {
15901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15902 let project = match &self.project {
15903 Some(project) => project.clone(),
15904 None => return None,
15905 };
15906 Some(self.perform_code_action_kind(
15907 project,
15908 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15909 window,
15910 cx,
15911 ))
15912 }
15913
15914 fn perform_code_action_kind(
15915 &mut self,
15916 project: Entity<Project>,
15917 kind: CodeActionKind,
15918 window: &mut Window,
15919 cx: &mut Context<Self>,
15920 ) -> Task<Result<()>> {
15921 let buffer = self.buffer.clone();
15922 let buffers = buffer.read(cx).all_buffers();
15923 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15924 let apply_action = project.update(cx, |project, cx| {
15925 project.apply_code_action_kind(buffers, kind, true, cx)
15926 });
15927 cx.spawn_in(window, async move |_, cx| {
15928 let transaction = futures::select_biased! {
15929 () = timeout => {
15930 log::warn!("timed out waiting for executing code action");
15931 None
15932 }
15933 transaction = apply_action.log_err().fuse() => transaction,
15934 };
15935 buffer
15936 .update(cx, |buffer, cx| {
15937 // check if we need this
15938 if let Some(transaction) = transaction {
15939 if !buffer.is_singleton() {
15940 buffer.push_transaction(&transaction.0, cx);
15941 }
15942 }
15943 cx.notify();
15944 })
15945 .ok();
15946 Ok(())
15947 })
15948 }
15949
15950 fn restart_language_server(
15951 &mut self,
15952 _: &RestartLanguageServer,
15953 _: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) {
15956 if let Some(project) = self.project.clone() {
15957 self.buffer.update(cx, |multi_buffer, cx| {
15958 project.update(cx, |project, cx| {
15959 project.restart_language_servers_for_buffers(
15960 multi_buffer.all_buffers().into_iter().collect(),
15961 cx,
15962 );
15963 });
15964 })
15965 }
15966 }
15967
15968 fn stop_language_server(
15969 &mut self,
15970 _: &StopLanguageServer,
15971 _: &mut Window,
15972 cx: &mut Context<Self>,
15973 ) {
15974 if let Some(project) = self.project.clone() {
15975 self.buffer.update(cx, |multi_buffer, cx| {
15976 project.update(cx, |project, cx| {
15977 project.stop_language_servers_for_buffers(
15978 multi_buffer.all_buffers().into_iter().collect(),
15979 cx,
15980 );
15981 cx.emit(project::Event::RefreshInlayHints);
15982 });
15983 });
15984 }
15985 }
15986
15987 fn cancel_language_server_work(
15988 workspace: &mut Workspace,
15989 _: &actions::CancelLanguageServerWork,
15990 _: &mut Window,
15991 cx: &mut Context<Workspace>,
15992 ) {
15993 let project = workspace.project();
15994 let buffers = workspace
15995 .active_item(cx)
15996 .and_then(|item| item.act_as::<Editor>(cx))
15997 .map_or(HashSet::default(), |editor| {
15998 editor.read(cx).buffer.read(cx).all_buffers()
15999 });
16000 project.update(cx, |project, cx| {
16001 project.cancel_language_server_work_for_buffers(buffers, cx);
16002 });
16003 }
16004
16005 fn show_character_palette(
16006 &mut self,
16007 _: &ShowCharacterPalette,
16008 window: &mut Window,
16009 _: &mut Context<Self>,
16010 ) {
16011 window.show_character_palette();
16012 }
16013
16014 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16015 if self.mode.is_minimap() {
16016 return;
16017 }
16018
16019 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16020 let buffer = self.buffer.read(cx).snapshot(cx);
16021 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16022 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16023 let is_valid = buffer
16024 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16025 .any(|entry| {
16026 entry.diagnostic.is_primary
16027 && !entry.range.is_empty()
16028 && entry.range.start == primary_range_start
16029 && entry.diagnostic.message == active_diagnostics.active_message
16030 });
16031
16032 if !is_valid {
16033 self.dismiss_diagnostics(cx);
16034 }
16035 }
16036 }
16037
16038 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16039 match &self.active_diagnostics {
16040 ActiveDiagnostic::Group(group) => Some(group),
16041 _ => None,
16042 }
16043 }
16044
16045 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16046 self.dismiss_diagnostics(cx);
16047 self.active_diagnostics = ActiveDiagnostic::All;
16048 }
16049
16050 fn activate_diagnostics(
16051 &mut self,
16052 buffer_id: BufferId,
16053 diagnostic: DiagnosticEntry<usize>,
16054 window: &mut Window,
16055 cx: &mut Context<Self>,
16056 ) {
16057 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16058 return;
16059 }
16060 self.dismiss_diagnostics(cx);
16061 let snapshot = self.snapshot(window, cx);
16062 let buffer = self.buffer.read(cx).snapshot(cx);
16063 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16064 return;
16065 };
16066
16067 let diagnostic_group = buffer
16068 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16069 .collect::<Vec<_>>();
16070
16071 let blocks =
16072 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16073
16074 let blocks = self.display_map.update(cx, |display_map, cx| {
16075 display_map.insert_blocks(blocks, cx).into_iter().collect()
16076 });
16077 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16078 active_range: buffer.anchor_before(diagnostic.range.start)
16079 ..buffer.anchor_after(diagnostic.range.end),
16080 active_message: diagnostic.diagnostic.message.clone(),
16081 group_id: diagnostic.diagnostic.group_id,
16082 blocks,
16083 });
16084 cx.notify();
16085 }
16086
16087 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16088 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16089 return;
16090 };
16091
16092 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16093 if let ActiveDiagnostic::Group(group) = prev {
16094 self.display_map.update(cx, |display_map, cx| {
16095 display_map.remove_blocks(group.blocks, cx);
16096 });
16097 cx.notify();
16098 }
16099 }
16100
16101 /// Disable inline diagnostics rendering for this editor.
16102 pub fn disable_inline_diagnostics(&mut self) {
16103 self.inline_diagnostics_enabled = false;
16104 self.inline_diagnostics_update = Task::ready(());
16105 self.inline_diagnostics.clear();
16106 }
16107
16108 pub fn diagnostics_enabled(&self) -> bool {
16109 self.mode.is_full()
16110 }
16111
16112 pub fn inline_diagnostics_enabled(&self) -> bool {
16113 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16114 }
16115
16116 pub fn show_inline_diagnostics(&self) -> bool {
16117 self.show_inline_diagnostics
16118 }
16119
16120 pub fn toggle_inline_diagnostics(
16121 &mut self,
16122 _: &ToggleInlineDiagnostics,
16123 window: &mut Window,
16124 cx: &mut Context<Editor>,
16125 ) {
16126 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16127 self.refresh_inline_diagnostics(false, window, cx);
16128 }
16129
16130 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16131 self.diagnostics_max_severity = severity;
16132 self.display_map.update(cx, |display_map, _| {
16133 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16134 });
16135 }
16136
16137 pub fn toggle_diagnostics(
16138 &mut self,
16139 _: &ToggleDiagnostics,
16140 window: &mut Window,
16141 cx: &mut Context<Editor>,
16142 ) {
16143 if !self.diagnostics_enabled() {
16144 return;
16145 }
16146
16147 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16148 EditorSettings::get_global(cx)
16149 .diagnostics_max_severity
16150 .filter(|severity| severity != &DiagnosticSeverity::Off)
16151 .unwrap_or(DiagnosticSeverity::Hint)
16152 } else {
16153 DiagnosticSeverity::Off
16154 };
16155 self.set_max_diagnostics_severity(new_severity, cx);
16156 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16157 self.active_diagnostics = ActiveDiagnostic::None;
16158 self.inline_diagnostics_update = Task::ready(());
16159 self.inline_diagnostics.clear();
16160 } else {
16161 self.refresh_inline_diagnostics(false, window, cx);
16162 }
16163
16164 cx.notify();
16165 }
16166
16167 pub fn toggle_minimap(
16168 &mut self,
16169 _: &ToggleMinimap,
16170 window: &mut Window,
16171 cx: &mut Context<Editor>,
16172 ) {
16173 if self.supports_minimap(cx) {
16174 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16175 }
16176 }
16177
16178 fn refresh_inline_diagnostics(
16179 &mut self,
16180 debounce: bool,
16181 window: &mut Window,
16182 cx: &mut Context<Self>,
16183 ) {
16184 let max_severity = ProjectSettings::get_global(cx)
16185 .diagnostics
16186 .inline
16187 .max_severity
16188 .unwrap_or(self.diagnostics_max_severity);
16189
16190 if !self.inline_diagnostics_enabled()
16191 || !self.show_inline_diagnostics
16192 || max_severity == DiagnosticSeverity::Off
16193 {
16194 self.inline_diagnostics_update = Task::ready(());
16195 self.inline_diagnostics.clear();
16196 return;
16197 }
16198
16199 let debounce_ms = ProjectSettings::get_global(cx)
16200 .diagnostics
16201 .inline
16202 .update_debounce_ms;
16203 let debounce = if debounce && debounce_ms > 0 {
16204 Some(Duration::from_millis(debounce_ms))
16205 } else {
16206 None
16207 };
16208 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16209 if let Some(debounce) = debounce {
16210 cx.background_executor().timer(debounce).await;
16211 }
16212 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16213 editor
16214 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16215 .ok()
16216 }) else {
16217 return;
16218 };
16219
16220 let new_inline_diagnostics = cx
16221 .background_spawn(async move {
16222 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16223 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16224 let message = diagnostic_entry
16225 .diagnostic
16226 .message
16227 .split_once('\n')
16228 .map(|(line, _)| line)
16229 .map(SharedString::new)
16230 .unwrap_or_else(|| {
16231 SharedString::from(diagnostic_entry.diagnostic.message)
16232 });
16233 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16234 let (Ok(i) | Err(i)) = inline_diagnostics
16235 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16236 inline_diagnostics.insert(
16237 i,
16238 (
16239 start_anchor,
16240 InlineDiagnostic {
16241 message,
16242 group_id: diagnostic_entry.diagnostic.group_id,
16243 start: diagnostic_entry.range.start.to_point(&snapshot),
16244 is_primary: diagnostic_entry.diagnostic.is_primary,
16245 severity: diagnostic_entry.diagnostic.severity,
16246 },
16247 ),
16248 );
16249 }
16250 inline_diagnostics
16251 })
16252 .await;
16253
16254 editor
16255 .update(cx, |editor, cx| {
16256 editor.inline_diagnostics = new_inline_diagnostics;
16257 cx.notify();
16258 })
16259 .ok();
16260 });
16261 }
16262
16263 fn pull_diagnostics(
16264 &mut self,
16265 buffer_id: Option<BufferId>,
16266 window: &Window,
16267 cx: &mut Context<Self>,
16268 ) -> Option<()> {
16269 if !self.mode().is_full() {
16270 return None;
16271 }
16272 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16273 .diagnostics
16274 .lsp_pull_diagnostics;
16275 if !pull_diagnostics_settings.enabled {
16276 return None;
16277 }
16278 let project = self.project.as_ref()?.downgrade();
16279 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16280 let mut buffers = self.buffer.read(cx).all_buffers();
16281 if let Some(buffer_id) = buffer_id {
16282 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16283 }
16284
16285 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16286 cx.background_executor().timer(debounce).await;
16287
16288 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16289 buffers
16290 .into_iter()
16291 .filter_map(|buffer| {
16292 project
16293 .update(cx, |project, cx| {
16294 project.lsp_store().update(cx, |lsp_store, cx| {
16295 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16296 })
16297 })
16298 .ok()
16299 })
16300 .collect::<FuturesUnordered<_>>()
16301 }) else {
16302 return;
16303 };
16304
16305 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16306 match pull_task {
16307 Ok(()) => {
16308 if editor
16309 .update_in(cx, |editor, window, cx| {
16310 editor.update_diagnostics_state(window, cx);
16311 })
16312 .is_err()
16313 {
16314 return;
16315 }
16316 }
16317 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16318 }
16319 }
16320 });
16321
16322 Some(())
16323 }
16324
16325 pub fn set_selections_from_remote(
16326 &mut self,
16327 selections: Vec<Selection<Anchor>>,
16328 pending_selection: Option<Selection<Anchor>>,
16329 window: &mut Window,
16330 cx: &mut Context<Self>,
16331 ) {
16332 let old_cursor_position = self.selections.newest_anchor().head();
16333 self.selections.change_with(cx, |s| {
16334 s.select_anchors(selections);
16335 if let Some(pending_selection) = pending_selection {
16336 s.set_pending(pending_selection, SelectMode::Character);
16337 } else {
16338 s.clear_pending();
16339 }
16340 });
16341 self.selections_did_change(
16342 false,
16343 &old_cursor_position,
16344 SelectionEffects::default(),
16345 window,
16346 cx,
16347 );
16348 }
16349
16350 pub fn transact(
16351 &mut self,
16352 window: &mut Window,
16353 cx: &mut Context<Self>,
16354 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16355 ) -> Option<TransactionId> {
16356 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16357 this.start_transaction_at(Instant::now(), window, cx);
16358 update(this, window, cx);
16359 this.end_transaction_at(Instant::now(), cx)
16360 })
16361 }
16362
16363 pub fn start_transaction_at(
16364 &mut self,
16365 now: Instant,
16366 window: &mut Window,
16367 cx: &mut Context<Self>,
16368 ) {
16369 self.end_selection(window, cx);
16370 if let Some(tx_id) = self
16371 .buffer
16372 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16373 {
16374 self.selection_history
16375 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16376 cx.emit(EditorEvent::TransactionBegun {
16377 transaction_id: tx_id,
16378 })
16379 }
16380 }
16381
16382 pub fn end_transaction_at(
16383 &mut self,
16384 now: Instant,
16385 cx: &mut Context<Self>,
16386 ) -> Option<TransactionId> {
16387 if let Some(transaction_id) = self
16388 .buffer
16389 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16390 {
16391 if let Some((_, end_selections)) =
16392 self.selection_history.transaction_mut(transaction_id)
16393 {
16394 *end_selections = Some(self.selections.disjoint_anchors());
16395 } else {
16396 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16397 }
16398
16399 cx.emit(EditorEvent::Edited { transaction_id });
16400 Some(transaction_id)
16401 } else {
16402 None
16403 }
16404 }
16405
16406 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16407 if self.selection_mark_mode {
16408 self.change_selections(None, window, cx, |s| {
16409 s.move_with(|_, sel| {
16410 sel.collapse_to(sel.head(), SelectionGoal::None);
16411 });
16412 })
16413 }
16414 self.selection_mark_mode = true;
16415 cx.notify();
16416 }
16417
16418 pub fn swap_selection_ends(
16419 &mut self,
16420 _: &actions::SwapSelectionEnds,
16421 window: &mut Window,
16422 cx: &mut Context<Self>,
16423 ) {
16424 self.change_selections(None, window, cx, |s| {
16425 s.move_with(|_, sel| {
16426 if sel.start != sel.end {
16427 sel.reversed = !sel.reversed
16428 }
16429 });
16430 });
16431 self.request_autoscroll(Autoscroll::newest(), cx);
16432 cx.notify();
16433 }
16434
16435 pub fn toggle_fold(
16436 &mut self,
16437 _: &actions::ToggleFold,
16438 window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) {
16441 if self.is_singleton(cx) {
16442 let selection = self.selections.newest::<Point>(cx);
16443
16444 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16445 let range = if selection.is_empty() {
16446 let point = selection.head().to_display_point(&display_map);
16447 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16448 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16449 .to_point(&display_map);
16450 start..end
16451 } else {
16452 selection.range()
16453 };
16454 if display_map.folds_in_range(range).next().is_some() {
16455 self.unfold_lines(&Default::default(), window, cx)
16456 } else {
16457 self.fold(&Default::default(), window, cx)
16458 }
16459 } else {
16460 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16461 let buffer_ids: HashSet<_> = self
16462 .selections
16463 .disjoint_anchor_ranges()
16464 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16465 .collect();
16466
16467 let should_unfold = buffer_ids
16468 .iter()
16469 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16470
16471 for buffer_id in buffer_ids {
16472 if should_unfold {
16473 self.unfold_buffer(buffer_id, cx);
16474 } else {
16475 self.fold_buffer(buffer_id, cx);
16476 }
16477 }
16478 }
16479 }
16480
16481 pub fn toggle_fold_recursive(
16482 &mut self,
16483 _: &actions::ToggleFoldRecursive,
16484 window: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) {
16487 let selection = self.selections.newest::<Point>(cx);
16488
16489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16490 let range = if selection.is_empty() {
16491 let point = selection.head().to_display_point(&display_map);
16492 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16493 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16494 .to_point(&display_map);
16495 start..end
16496 } else {
16497 selection.range()
16498 };
16499 if display_map.folds_in_range(range).next().is_some() {
16500 self.unfold_recursive(&Default::default(), window, cx)
16501 } else {
16502 self.fold_recursive(&Default::default(), window, cx)
16503 }
16504 }
16505
16506 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16507 if self.is_singleton(cx) {
16508 let mut to_fold = Vec::new();
16509 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16510 let selections = self.selections.all_adjusted(cx);
16511
16512 for selection in selections {
16513 let range = selection.range().sorted();
16514 let buffer_start_row = range.start.row;
16515
16516 if range.start.row != range.end.row {
16517 let mut found = false;
16518 let mut row = range.start.row;
16519 while row <= range.end.row {
16520 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16521 {
16522 found = true;
16523 row = crease.range().end.row + 1;
16524 to_fold.push(crease);
16525 } else {
16526 row += 1
16527 }
16528 }
16529 if found {
16530 continue;
16531 }
16532 }
16533
16534 for row in (0..=range.start.row).rev() {
16535 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16536 if crease.range().end.row >= buffer_start_row {
16537 to_fold.push(crease);
16538 if row <= range.start.row {
16539 break;
16540 }
16541 }
16542 }
16543 }
16544 }
16545
16546 self.fold_creases(to_fold, true, window, cx);
16547 } else {
16548 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16549 let buffer_ids = self
16550 .selections
16551 .disjoint_anchor_ranges()
16552 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16553 .collect::<HashSet<_>>();
16554 for buffer_id in buffer_ids {
16555 self.fold_buffer(buffer_id, cx);
16556 }
16557 }
16558 }
16559
16560 fn fold_at_level(
16561 &mut self,
16562 fold_at: &FoldAtLevel,
16563 window: &mut Window,
16564 cx: &mut Context<Self>,
16565 ) {
16566 if !self.buffer.read(cx).is_singleton() {
16567 return;
16568 }
16569
16570 let fold_at_level = fold_at.0;
16571 let snapshot = self.buffer.read(cx).snapshot(cx);
16572 let mut to_fold = Vec::new();
16573 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16574
16575 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16576 while start_row < end_row {
16577 match self
16578 .snapshot(window, cx)
16579 .crease_for_buffer_row(MultiBufferRow(start_row))
16580 {
16581 Some(crease) => {
16582 let nested_start_row = crease.range().start.row + 1;
16583 let nested_end_row = crease.range().end.row;
16584
16585 if current_level < fold_at_level {
16586 stack.push((nested_start_row, nested_end_row, current_level + 1));
16587 } else if current_level == fold_at_level {
16588 to_fold.push(crease);
16589 }
16590
16591 start_row = nested_end_row + 1;
16592 }
16593 None => start_row += 1,
16594 }
16595 }
16596 }
16597
16598 self.fold_creases(to_fold, true, window, cx);
16599 }
16600
16601 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16602 if self.buffer.read(cx).is_singleton() {
16603 let mut fold_ranges = Vec::new();
16604 let snapshot = self.buffer.read(cx).snapshot(cx);
16605
16606 for row in 0..snapshot.max_row().0 {
16607 if let Some(foldable_range) = self
16608 .snapshot(window, cx)
16609 .crease_for_buffer_row(MultiBufferRow(row))
16610 {
16611 fold_ranges.push(foldable_range);
16612 }
16613 }
16614
16615 self.fold_creases(fold_ranges, true, window, cx);
16616 } else {
16617 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16618 editor
16619 .update_in(cx, |editor, _, cx| {
16620 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16621 editor.fold_buffer(buffer_id, cx);
16622 }
16623 })
16624 .ok();
16625 });
16626 }
16627 }
16628
16629 pub fn fold_function_bodies(
16630 &mut self,
16631 _: &actions::FoldFunctionBodies,
16632 window: &mut Window,
16633 cx: &mut Context<Self>,
16634 ) {
16635 let snapshot = self.buffer.read(cx).snapshot(cx);
16636
16637 let ranges = snapshot
16638 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16639 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16640 .collect::<Vec<_>>();
16641
16642 let creases = ranges
16643 .into_iter()
16644 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16645 .collect();
16646
16647 self.fold_creases(creases, true, window, cx);
16648 }
16649
16650 pub fn fold_recursive(
16651 &mut self,
16652 _: &actions::FoldRecursive,
16653 window: &mut Window,
16654 cx: &mut Context<Self>,
16655 ) {
16656 let mut to_fold = Vec::new();
16657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16658 let selections = self.selections.all_adjusted(cx);
16659
16660 for selection in selections {
16661 let range = selection.range().sorted();
16662 let buffer_start_row = range.start.row;
16663
16664 if range.start.row != range.end.row {
16665 let mut found = false;
16666 for row in range.start.row..=range.end.row {
16667 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16668 found = true;
16669 to_fold.push(crease);
16670 }
16671 }
16672 if found {
16673 continue;
16674 }
16675 }
16676
16677 for row in (0..=range.start.row).rev() {
16678 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16679 if crease.range().end.row >= buffer_start_row {
16680 to_fold.push(crease);
16681 } else {
16682 break;
16683 }
16684 }
16685 }
16686 }
16687
16688 self.fold_creases(to_fold, true, window, cx);
16689 }
16690
16691 pub fn fold_at(
16692 &mut self,
16693 buffer_row: MultiBufferRow,
16694 window: &mut Window,
16695 cx: &mut Context<Self>,
16696 ) {
16697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16698
16699 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16700 let autoscroll = self
16701 .selections
16702 .all::<Point>(cx)
16703 .iter()
16704 .any(|selection| crease.range().overlaps(&selection.range()));
16705
16706 self.fold_creases(vec![crease], autoscroll, window, cx);
16707 }
16708 }
16709
16710 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16711 if self.is_singleton(cx) {
16712 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16713 let buffer = &display_map.buffer_snapshot;
16714 let selections = self.selections.all::<Point>(cx);
16715 let ranges = selections
16716 .iter()
16717 .map(|s| {
16718 let range = s.display_range(&display_map).sorted();
16719 let mut start = range.start.to_point(&display_map);
16720 let mut end = range.end.to_point(&display_map);
16721 start.column = 0;
16722 end.column = buffer.line_len(MultiBufferRow(end.row));
16723 start..end
16724 })
16725 .collect::<Vec<_>>();
16726
16727 self.unfold_ranges(&ranges, true, true, cx);
16728 } else {
16729 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16730 let buffer_ids = self
16731 .selections
16732 .disjoint_anchor_ranges()
16733 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16734 .collect::<HashSet<_>>();
16735 for buffer_id in buffer_ids {
16736 self.unfold_buffer(buffer_id, cx);
16737 }
16738 }
16739 }
16740
16741 pub fn unfold_recursive(
16742 &mut self,
16743 _: &UnfoldRecursive,
16744 _window: &mut Window,
16745 cx: &mut Context<Self>,
16746 ) {
16747 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16748 let selections = self.selections.all::<Point>(cx);
16749 let ranges = selections
16750 .iter()
16751 .map(|s| {
16752 let mut range = s.display_range(&display_map).sorted();
16753 *range.start.column_mut() = 0;
16754 *range.end.column_mut() = display_map.line_len(range.end.row());
16755 let start = range.start.to_point(&display_map);
16756 let end = range.end.to_point(&display_map);
16757 start..end
16758 })
16759 .collect::<Vec<_>>();
16760
16761 self.unfold_ranges(&ranges, true, true, cx);
16762 }
16763
16764 pub fn unfold_at(
16765 &mut self,
16766 buffer_row: MultiBufferRow,
16767 _window: &mut Window,
16768 cx: &mut Context<Self>,
16769 ) {
16770 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16771
16772 let intersection_range = Point::new(buffer_row.0, 0)
16773 ..Point::new(
16774 buffer_row.0,
16775 display_map.buffer_snapshot.line_len(buffer_row),
16776 );
16777
16778 let autoscroll = self
16779 .selections
16780 .all::<Point>(cx)
16781 .iter()
16782 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16783
16784 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16785 }
16786
16787 pub fn unfold_all(
16788 &mut self,
16789 _: &actions::UnfoldAll,
16790 _window: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) {
16793 if self.buffer.read(cx).is_singleton() {
16794 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16795 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16796 } else {
16797 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16798 editor
16799 .update(cx, |editor, cx| {
16800 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16801 editor.unfold_buffer(buffer_id, cx);
16802 }
16803 })
16804 .ok();
16805 });
16806 }
16807 }
16808
16809 pub fn fold_selected_ranges(
16810 &mut self,
16811 _: &FoldSelectedRanges,
16812 window: &mut Window,
16813 cx: &mut Context<Self>,
16814 ) {
16815 let selections = self.selections.all_adjusted(cx);
16816 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16817 let ranges = selections
16818 .into_iter()
16819 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16820 .collect::<Vec<_>>();
16821 self.fold_creases(ranges, true, window, cx);
16822 }
16823
16824 pub fn fold_ranges<T: ToOffset + Clone>(
16825 &mut self,
16826 ranges: Vec<Range<T>>,
16827 auto_scroll: bool,
16828 window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) {
16831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16832 let ranges = ranges
16833 .into_iter()
16834 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16835 .collect::<Vec<_>>();
16836 self.fold_creases(ranges, auto_scroll, window, cx);
16837 }
16838
16839 pub fn fold_creases<T: ToOffset + Clone>(
16840 &mut self,
16841 creases: Vec<Crease<T>>,
16842 auto_scroll: bool,
16843 _window: &mut Window,
16844 cx: &mut Context<Self>,
16845 ) {
16846 if creases.is_empty() {
16847 return;
16848 }
16849
16850 let mut buffers_affected = HashSet::default();
16851 let multi_buffer = self.buffer().read(cx);
16852 for crease in &creases {
16853 if let Some((_, buffer, _)) =
16854 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16855 {
16856 buffers_affected.insert(buffer.read(cx).remote_id());
16857 };
16858 }
16859
16860 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16861
16862 if auto_scroll {
16863 self.request_autoscroll(Autoscroll::fit(), cx);
16864 }
16865
16866 cx.notify();
16867
16868 self.scrollbar_marker_state.dirty = true;
16869 self.folds_did_change(cx);
16870 }
16871
16872 /// Removes any folds whose ranges intersect any of the given ranges.
16873 pub fn unfold_ranges<T: ToOffset + Clone>(
16874 &mut self,
16875 ranges: &[Range<T>],
16876 inclusive: bool,
16877 auto_scroll: bool,
16878 cx: &mut Context<Self>,
16879 ) {
16880 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16881 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16882 });
16883 self.folds_did_change(cx);
16884 }
16885
16886 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16887 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16888 return;
16889 }
16890 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16891 self.display_map.update(cx, |display_map, cx| {
16892 display_map.fold_buffers([buffer_id], cx)
16893 });
16894 cx.emit(EditorEvent::BufferFoldToggled {
16895 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16896 folded: true,
16897 });
16898 cx.notify();
16899 }
16900
16901 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16902 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16903 return;
16904 }
16905 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16906 self.display_map.update(cx, |display_map, cx| {
16907 display_map.unfold_buffers([buffer_id], cx);
16908 });
16909 cx.emit(EditorEvent::BufferFoldToggled {
16910 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16911 folded: false,
16912 });
16913 cx.notify();
16914 }
16915
16916 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16917 self.display_map.read(cx).is_buffer_folded(buffer)
16918 }
16919
16920 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16921 self.display_map.read(cx).folded_buffers()
16922 }
16923
16924 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16925 self.display_map.update(cx, |display_map, cx| {
16926 display_map.disable_header_for_buffer(buffer_id, cx);
16927 });
16928 cx.notify();
16929 }
16930
16931 /// Removes any folds with the given ranges.
16932 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16933 &mut self,
16934 ranges: &[Range<T>],
16935 type_id: TypeId,
16936 auto_scroll: bool,
16937 cx: &mut Context<Self>,
16938 ) {
16939 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16940 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16941 });
16942 self.folds_did_change(cx);
16943 }
16944
16945 fn remove_folds_with<T: ToOffset + Clone>(
16946 &mut self,
16947 ranges: &[Range<T>],
16948 auto_scroll: bool,
16949 cx: &mut Context<Self>,
16950 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16951 ) {
16952 if ranges.is_empty() {
16953 return;
16954 }
16955
16956 let mut buffers_affected = HashSet::default();
16957 let multi_buffer = self.buffer().read(cx);
16958 for range in ranges {
16959 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16960 buffers_affected.insert(buffer.read(cx).remote_id());
16961 };
16962 }
16963
16964 self.display_map.update(cx, update);
16965
16966 if auto_scroll {
16967 self.request_autoscroll(Autoscroll::fit(), cx);
16968 }
16969
16970 cx.notify();
16971 self.scrollbar_marker_state.dirty = true;
16972 self.active_indent_guides_state.dirty = true;
16973 }
16974
16975 pub fn update_fold_widths(
16976 &mut self,
16977 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16978 cx: &mut Context<Self>,
16979 ) -> bool {
16980 self.display_map
16981 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16982 }
16983
16984 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16985 self.display_map.read(cx).fold_placeholder.clone()
16986 }
16987
16988 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16989 self.buffer.update(cx, |buffer, cx| {
16990 buffer.set_all_diff_hunks_expanded(cx);
16991 });
16992 }
16993
16994 pub fn expand_all_diff_hunks(
16995 &mut self,
16996 _: &ExpandAllDiffHunks,
16997 _window: &mut Window,
16998 cx: &mut Context<Self>,
16999 ) {
17000 self.buffer.update(cx, |buffer, cx| {
17001 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17002 });
17003 }
17004
17005 pub fn toggle_selected_diff_hunks(
17006 &mut self,
17007 _: &ToggleSelectedDiffHunks,
17008 _window: &mut Window,
17009 cx: &mut Context<Self>,
17010 ) {
17011 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17012 self.toggle_diff_hunks_in_ranges(ranges, cx);
17013 }
17014
17015 pub fn diff_hunks_in_ranges<'a>(
17016 &'a self,
17017 ranges: &'a [Range<Anchor>],
17018 buffer: &'a MultiBufferSnapshot,
17019 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17020 ranges.iter().flat_map(move |range| {
17021 let end_excerpt_id = range.end.excerpt_id;
17022 let range = range.to_point(buffer);
17023 let mut peek_end = range.end;
17024 if range.end.row < buffer.max_row().0 {
17025 peek_end = Point::new(range.end.row + 1, 0);
17026 }
17027 buffer
17028 .diff_hunks_in_range(range.start..peek_end)
17029 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17030 })
17031 }
17032
17033 pub fn has_stageable_diff_hunks_in_ranges(
17034 &self,
17035 ranges: &[Range<Anchor>],
17036 snapshot: &MultiBufferSnapshot,
17037 ) -> bool {
17038 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17039 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17040 }
17041
17042 pub fn toggle_staged_selected_diff_hunks(
17043 &mut self,
17044 _: &::git::ToggleStaged,
17045 _: &mut Window,
17046 cx: &mut Context<Self>,
17047 ) {
17048 let snapshot = self.buffer.read(cx).snapshot(cx);
17049 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17050 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17051 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17052 }
17053
17054 pub fn set_render_diff_hunk_controls(
17055 &mut self,
17056 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17057 cx: &mut Context<Self>,
17058 ) {
17059 self.render_diff_hunk_controls = render_diff_hunk_controls;
17060 cx.notify();
17061 }
17062
17063 pub fn stage_and_next(
17064 &mut self,
17065 _: &::git::StageAndNext,
17066 window: &mut Window,
17067 cx: &mut Context<Self>,
17068 ) {
17069 self.do_stage_or_unstage_and_next(true, window, cx);
17070 }
17071
17072 pub fn unstage_and_next(
17073 &mut self,
17074 _: &::git::UnstageAndNext,
17075 window: &mut Window,
17076 cx: &mut Context<Self>,
17077 ) {
17078 self.do_stage_or_unstage_and_next(false, window, cx);
17079 }
17080
17081 pub fn stage_or_unstage_diff_hunks(
17082 &mut self,
17083 stage: bool,
17084 ranges: Vec<Range<Anchor>>,
17085 cx: &mut Context<Self>,
17086 ) {
17087 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17088 cx.spawn(async move |this, cx| {
17089 task.await?;
17090 this.update(cx, |this, cx| {
17091 let snapshot = this.buffer.read(cx).snapshot(cx);
17092 let chunk_by = this
17093 .diff_hunks_in_ranges(&ranges, &snapshot)
17094 .chunk_by(|hunk| hunk.buffer_id);
17095 for (buffer_id, hunks) in &chunk_by {
17096 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17097 }
17098 })
17099 })
17100 .detach_and_log_err(cx);
17101 }
17102
17103 fn save_buffers_for_ranges_if_needed(
17104 &mut self,
17105 ranges: &[Range<Anchor>],
17106 cx: &mut Context<Editor>,
17107 ) -> Task<Result<()>> {
17108 let multibuffer = self.buffer.read(cx);
17109 let snapshot = multibuffer.read(cx);
17110 let buffer_ids: HashSet<_> = ranges
17111 .iter()
17112 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17113 .collect();
17114 drop(snapshot);
17115
17116 let mut buffers = HashSet::default();
17117 for buffer_id in buffer_ids {
17118 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17119 let buffer = buffer_entity.read(cx);
17120 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17121 {
17122 buffers.insert(buffer_entity);
17123 }
17124 }
17125 }
17126
17127 if let Some(project) = &self.project {
17128 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17129 } else {
17130 Task::ready(Ok(()))
17131 }
17132 }
17133
17134 fn do_stage_or_unstage_and_next(
17135 &mut self,
17136 stage: bool,
17137 window: &mut Window,
17138 cx: &mut Context<Self>,
17139 ) {
17140 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17141
17142 if ranges.iter().any(|range| range.start != range.end) {
17143 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17144 return;
17145 }
17146
17147 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17148 let snapshot = self.snapshot(window, cx);
17149 let position = self.selections.newest::<Point>(cx).head();
17150 let mut row = snapshot
17151 .buffer_snapshot
17152 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17153 .find(|hunk| hunk.row_range.start.0 > position.row)
17154 .map(|hunk| hunk.row_range.start);
17155
17156 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17157 // Outside of the project diff editor, wrap around to the beginning.
17158 if !all_diff_hunks_expanded {
17159 row = row.or_else(|| {
17160 snapshot
17161 .buffer_snapshot
17162 .diff_hunks_in_range(Point::zero()..position)
17163 .find(|hunk| hunk.row_range.end.0 < position.row)
17164 .map(|hunk| hunk.row_range.start)
17165 });
17166 }
17167
17168 if let Some(row) = row {
17169 let destination = Point::new(row.0, 0);
17170 let autoscroll = Autoscroll::center();
17171
17172 self.unfold_ranges(&[destination..destination], false, false, cx);
17173 self.change_selections(Some(autoscroll), window, cx, |s| {
17174 s.select_ranges([destination..destination]);
17175 });
17176 }
17177 }
17178
17179 fn do_stage_or_unstage(
17180 &self,
17181 stage: bool,
17182 buffer_id: BufferId,
17183 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17184 cx: &mut App,
17185 ) -> Option<()> {
17186 let project = self.project.as_ref()?;
17187 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17188 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17189 let buffer_snapshot = buffer.read(cx).snapshot();
17190 let file_exists = buffer_snapshot
17191 .file()
17192 .is_some_and(|file| file.disk_state().exists());
17193 diff.update(cx, |diff, cx| {
17194 diff.stage_or_unstage_hunks(
17195 stage,
17196 &hunks
17197 .map(|hunk| buffer_diff::DiffHunk {
17198 buffer_range: hunk.buffer_range,
17199 diff_base_byte_range: hunk.diff_base_byte_range,
17200 secondary_status: hunk.secondary_status,
17201 range: Point::zero()..Point::zero(), // unused
17202 })
17203 .collect::<Vec<_>>(),
17204 &buffer_snapshot,
17205 file_exists,
17206 cx,
17207 )
17208 });
17209 None
17210 }
17211
17212 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17213 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17214 self.buffer
17215 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17216 }
17217
17218 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17219 self.buffer.update(cx, |buffer, cx| {
17220 let ranges = vec![Anchor::min()..Anchor::max()];
17221 if !buffer.all_diff_hunks_expanded()
17222 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17223 {
17224 buffer.collapse_diff_hunks(ranges, cx);
17225 true
17226 } else {
17227 false
17228 }
17229 })
17230 }
17231
17232 fn toggle_diff_hunks_in_ranges(
17233 &mut self,
17234 ranges: Vec<Range<Anchor>>,
17235 cx: &mut Context<Editor>,
17236 ) {
17237 self.buffer.update(cx, |buffer, cx| {
17238 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17239 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17240 })
17241 }
17242
17243 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17244 self.buffer.update(cx, |buffer, cx| {
17245 let snapshot = buffer.snapshot(cx);
17246 let excerpt_id = range.end.excerpt_id;
17247 let point_range = range.to_point(&snapshot);
17248 let expand = !buffer.single_hunk_is_expanded(range, cx);
17249 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17250 })
17251 }
17252
17253 pub(crate) fn apply_all_diff_hunks(
17254 &mut self,
17255 _: &ApplyAllDiffHunks,
17256 window: &mut Window,
17257 cx: &mut Context<Self>,
17258 ) {
17259 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17260
17261 let buffers = self.buffer.read(cx).all_buffers();
17262 for branch_buffer in buffers {
17263 branch_buffer.update(cx, |branch_buffer, cx| {
17264 branch_buffer.merge_into_base(Vec::new(), cx);
17265 });
17266 }
17267
17268 if let Some(project) = self.project.clone() {
17269 self.save(
17270 SaveOptions {
17271 format: true,
17272 autosave: false,
17273 },
17274 project,
17275 window,
17276 cx,
17277 )
17278 .detach_and_log_err(cx);
17279 }
17280 }
17281
17282 pub(crate) fn apply_selected_diff_hunks(
17283 &mut self,
17284 _: &ApplyDiffHunk,
17285 window: &mut Window,
17286 cx: &mut Context<Self>,
17287 ) {
17288 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17289 let snapshot = self.snapshot(window, cx);
17290 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17291 let mut ranges_by_buffer = HashMap::default();
17292 self.transact(window, cx, |editor, _window, cx| {
17293 for hunk in hunks {
17294 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17295 ranges_by_buffer
17296 .entry(buffer.clone())
17297 .or_insert_with(Vec::new)
17298 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17299 }
17300 }
17301
17302 for (buffer, ranges) in ranges_by_buffer {
17303 buffer.update(cx, |buffer, cx| {
17304 buffer.merge_into_base(ranges, cx);
17305 });
17306 }
17307 });
17308
17309 if let Some(project) = self.project.clone() {
17310 self.save(
17311 SaveOptions {
17312 format: true,
17313 autosave: false,
17314 },
17315 project,
17316 window,
17317 cx,
17318 )
17319 .detach_and_log_err(cx);
17320 }
17321 }
17322
17323 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17324 if hovered != self.gutter_hovered {
17325 self.gutter_hovered = hovered;
17326 cx.notify();
17327 }
17328 }
17329
17330 pub fn insert_blocks(
17331 &mut self,
17332 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17333 autoscroll: Option<Autoscroll>,
17334 cx: &mut Context<Self>,
17335 ) -> Vec<CustomBlockId> {
17336 let blocks = self
17337 .display_map
17338 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17339 if let Some(autoscroll) = autoscroll {
17340 self.request_autoscroll(autoscroll, cx);
17341 }
17342 cx.notify();
17343 blocks
17344 }
17345
17346 pub fn resize_blocks(
17347 &mut self,
17348 heights: HashMap<CustomBlockId, u32>,
17349 autoscroll: Option<Autoscroll>,
17350 cx: &mut Context<Self>,
17351 ) {
17352 self.display_map
17353 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17354 if let Some(autoscroll) = autoscroll {
17355 self.request_autoscroll(autoscroll, cx);
17356 }
17357 cx.notify();
17358 }
17359
17360 pub fn replace_blocks(
17361 &mut self,
17362 renderers: HashMap<CustomBlockId, RenderBlock>,
17363 autoscroll: Option<Autoscroll>,
17364 cx: &mut Context<Self>,
17365 ) {
17366 self.display_map
17367 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17368 if let Some(autoscroll) = autoscroll {
17369 self.request_autoscroll(autoscroll, cx);
17370 }
17371 cx.notify();
17372 }
17373
17374 pub fn remove_blocks(
17375 &mut self,
17376 block_ids: HashSet<CustomBlockId>,
17377 autoscroll: Option<Autoscroll>,
17378 cx: &mut Context<Self>,
17379 ) {
17380 self.display_map.update(cx, |display_map, cx| {
17381 display_map.remove_blocks(block_ids, cx)
17382 });
17383 if let Some(autoscroll) = autoscroll {
17384 self.request_autoscroll(autoscroll, cx);
17385 }
17386 cx.notify();
17387 }
17388
17389 pub fn row_for_block(
17390 &self,
17391 block_id: CustomBlockId,
17392 cx: &mut Context<Self>,
17393 ) -> Option<DisplayRow> {
17394 self.display_map
17395 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17396 }
17397
17398 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17399 self.focused_block = Some(focused_block);
17400 }
17401
17402 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17403 self.focused_block.take()
17404 }
17405
17406 pub fn insert_creases(
17407 &mut self,
17408 creases: impl IntoIterator<Item = Crease<Anchor>>,
17409 cx: &mut Context<Self>,
17410 ) -> Vec<CreaseId> {
17411 self.display_map
17412 .update(cx, |map, cx| map.insert_creases(creases, cx))
17413 }
17414
17415 pub fn remove_creases(
17416 &mut self,
17417 ids: impl IntoIterator<Item = CreaseId>,
17418 cx: &mut Context<Self>,
17419 ) -> Vec<(CreaseId, Range<Anchor>)> {
17420 self.display_map
17421 .update(cx, |map, cx| map.remove_creases(ids, cx))
17422 }
17423
17424 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17425 self.display_map
17426 .update(cx, |map, cx| map.snapshot(cx))
17427 .longest_row()
17428 }
17429
17430 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17431 self.display_map
17432 .update(cx, |map, cx| map.snapshot(cx))
17433 .max_point()
17434 }
17435
17436 pub fn text(&self, cx: &App) -> String {
17437 self.buffer.read(cx).read(cx).text()
17438 }
17439
17440 pub fn is_empty(&self, cx: &App) -> bool {
17441 self.buffer.read(cx).read(cx).is_empty()
17442 }
17443
17444 pub fn text_option(&self, cx: &App) -> Option<String> {
17445 let text = self.text(cx);
17446 let text = text.trim();
17447
17448 if text.is_empty() {
17449 return None;
17450 }
17451
17452 Some(text.to_string())
17453 }
17454
17455 pub fn set_text(
17456 &mut self,
17457 text: impl Into<Arc<str>>,
17458 window: &mut Window,
17459 cx: &mut Context<Self>,
17460 ) {
17461 self.transact(window, cx, |this, _, cx| {
17462 this.buffer
17463 .read(cx)
17464 .as_singleton()
17465 .expect("you can only call set_text on editors for singleton buffers")
17466 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17467 });
17468 }
17469
17470 pub fn display_text(&self, cx: &mut App) -> String {
17471 self.display_map
17472 .update(cx, |map, cx| map.snapshot(cx))
17473 .text()
17474 }
17475
17476 fn create_minimap(
17477 &self,
17478 minimap_settings: MinimapSettings,
17479 window: &mut Window,
17480 cx: &mut Context<Self>,
17481 ) -> Option<Entity<Self>> {
17482 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17483 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17484 }
17485
17486 fn initialize_new_minimap(
17487 &self,
17488 minimap_settings: MinimapSettings,
17489 window: &mut Window,
17490 cx: &mut Context<Self>,
17491 ) -> Entity<Self> {
17492 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17493
17494 let mut minimap = Editor::new_internal(
17495 EditorMode::Minimap {
17496 parent: cx.weak_entity(),
17497 },
17498 self.buffer.clone(),
17499 self.project.clone(),
17500 Some(self.display_map.clone()),
17501 window,
17502 cx,
17503 );
17504 minimap.scroll_manager.clone_state(&self.scroll_manager);
17505 minimap.set_text_style_refinement(TextStyleRefinement {
17506 font_size: Some(MINIMAP_FONT_SIZE),
17507 font_weight: Some(MINIMAP_FONT_WEIGHT),
17508 ..Default::default()
17509 });
17510 minimap.update_minimap_configuration(minimap_settings, cx);
17511 cx.new(|_| minimap)
17512 }
17513
17514 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17515 let current_line_highlight = minimap_settings
17516 .current_line_highlight
17517 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17518 self.set_current_line_highlight(Some(current_line_highlight));
17519 }
17520
17521 pub fn minimap(&self) -> Option<&Entity<Self>> {
17522 self.minimap
17523 .as_ref()
17524 .filter(|_| self.minimap_visibility.visible())
17525 }
17526
17527 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17528 let mut wrap_guides = smallvec![];
17529
17530 if self.show_wrap_guides == Some(false) {
17531 return wrap_guides;
17532 }
17533
17534 let settings = self.buffer.read(cx).language_settings(cx);
17535 if settings.show_wrap_guides {
17536 match self.soft_wrap_mode(cx) {
17537 SoftWrap::Column(soft_wrap) => {
17538 wrap_guides.push((soft_wrap as usize, true));
17539 }
17540 SoftWrap::Bounded(soft_wrap) => {
17541 wrap_guides.push((soft_wrap as usize, true));
17542 }
17543 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17544 }
17545 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17546 }
17547
17548 wrap_guides
17549 }
17550
17551 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17552 let settings = self.buffer.read(cx).language_settings(cx);
17553 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17554 match mode {
17555 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17556 SoftWrap::None
17557 }
17558 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17559 language_settings::SoftWrap::PreferredLineLength => {
17560 SoftWrap::Column(settings.preferred_line_length)
17561 }
17562 language_settings::SoftWrap::Bounded => {
17563 SoftWrap::Bounded(settings.preferred_line_length)
17564 }
17565 }
17566 }
17567
17568 pub fn set_soft_wrap_mode(
17569 &mut self,
17570 mode: language_settings::SoftWrap,
17571
17572 cx: &mut Context<Self>,
17573 ) {
17574 self.soft_wrap_mode_override = Some(mode);
17575 cx.notify();
17576 }
17577
17578 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17579 self.hard_wrap = hard_wrap;
17580 cx.notify();
17581 }
17582
17583 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17584 self.text_style_refinement = Some(style);
17585 }
17586
17587 /// called by the Element so we know what style we were most recently rendered with.
17588 pub(crate) fn set_style(
17589 &mut self,
17590 style: EditorStyle,
17591 window: &mut Window,
17592 cx: &mut Context<Self>,
17593 ) {
17594 // We intentionally do not inform the display map about the minimap style
17595 // so that wrapping is not recalculated and stays consistent for the editor
17596 // and its linked minimap.
17597 if !self.mode.is_minimap() {
17598 let rem_size = window.rem_size();
17599 self.display_map.update(cx, |map, cx| {
17600 map.set_font(
17601 style.text.font(),
17602 style.text.font_size.to_pixels(rem_size),
17603 cx,
17604 )
17605 });
17606 }
17607 self.style = Some(style);
17608 }
17609
17610 pub fn style(&self) -> Option<&EditorStyle> {
17611 self.style.as_ref()
17612 }
17613
17614 // Called by the element. This method is not designed to be called outside of the editor
17615 // element's layout code because it does not notify when rewrapping is computed synchronously.
17616 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17617 self.display_map
17618 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17619 }
17620
17621 pub fn set_soft_wrap(&mut self) {
17622 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17623 }
17624
17625 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17626 if self.soft_wrap_mode_override.is_some() {
17627 self.soft_wrap_mode_override.take();
17628 } else {
17629 let soft_wrap = match self.soft_wrap_mode(cx) {
17630 SoftWrap::GitDiff => return,
17631 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17632 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17633 language_settings::SoftWrap::None
17634 }
17635 };
17636 self.soft_wrap_mode_override = Some(soft_wrap);
17637 }
17638 cx.notify();
17639 }
17640
17641 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17642 let Some(workspace) = self.workspace() else {
17643 return;
17644 };
17645 let fs = workspace.read(cx).app_state().fs.clone();
17646 let current_show = TabBarSettings::get_global(cx).show;
17647 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17648 setting.show = Some(!current_show);
17649 });
17650 }
17651
17652 pub fn toggle_indent_guides(
17653 &mut self,
17654 _: &ToggleIndentGuides,
17655 _: &mut Window,
17656 cx: &mut Context<Self>,
17657 ) {
17658 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17659 self.buffer
17660 .read(cx)
17661 .language_settings(cx)
17662 .indent_guides
17663 .enabled
17664 });
17665 self.show_indent_guides = Some(!currently_enabled);
17666 cx.notify();
17667 }
17668
17669 fn should_show_indent_guides(&self) -> Option<bool> {
17670 self.show_indent_guides
17671 }
17672
17673 pub fn toggle_line_numbers(
17674 &mut self,
17675 _: &ToggleLineNumbers,
17676 _: &mut Window,
17677 cx: &mut Context<Self>,
17678 ) {
17679 let mut editor_settings = EditorSettings::get_global(cx).clone();
17680 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17681 EditorSettings::override_global(editor_settings, cx);
17682 }
17683
17684 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17685 if let Some(show_line_numbers) = self.show_line_numbers {
17686 return show_line_numbers;
17687 }
17688 EditorSettings::get_global(cx).gutter.line_numbers
17689 }
17690
17691 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17692 self.use_relative_line_numbers
17693 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17694 }
17695
17696 pub fn toggle_relative_line_numbers(
17697 &mut self,
17698 _: &ToggleRelativeLineNumbers,
17699 _: &mut Window,
17700 cx: &mut Context<Self>,
17701 ) {
17702 let is_relative = self.should_use_relative_line_numbers(cx);
17703 self.set_relative_line_number(Some(!is_relative), cx)
17704 }
17705
17706 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17707 self.use_relative_line_numbers = is_relative;
17708 cx.notify();
17709 }
17710
17711 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17712 self.show_gutter = show_gutter;
17713 cx.notify();
17714 }
17715
17716 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17717 self.show_scrollbars = ScrollbarAxes {
17718 horizontal: show,
17719 vertical: show,
17720 };
17721 cx.notify();
17722 }
17723
17724 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17725 self.show_scrollbars.vertical = show;
17726 cx.notify();
17727 }
17728
17729 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17730 self.show_scrollbars.horizontal = show;
17731 cx.notify();
17732 }
17733
17734 pub fn set_minimap_visibility(
17735 &mut self,
17736 minimap_visibility: MinimapVisibility,
17737 window: &mut Window,
17738 cx: &mut Context<Self>,
17739 ) {
17740 if self.minimap_visibility != minimap_visibility {
17741 if minimap_visibility.visible() && self.minimap.is_none() {
17742 let minimap_settings = EditorSettings::get_global(cx).minimap;
17743 self.minimap =
17744 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17745 }
17746 self.minimap_visibility = minimap_visibility;
17747 cx.notify();
17748 }
17749 }
17750
17751 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17752 self.set_show_scrollbars(false, cx);
17753 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17754 }
17755
17756 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17757 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17758 }
17759
17760 /// Normally the text in full mode and auto height editors is padded on the
17761 /// left side by roughly half a character width for improved hit testing.
17762 ///
17763 /// Use this method to disable this for cases where this is not wanted (e.g.
17764 /// if you want to align the editor text with some other text above or below)
17765 /// or if you want to add this padding to single-line editors.
17766 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17767 self.offset_content = offset_content;
17768 cx.notify();
17769 }
17770
17771 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17772 self.show_line_numbers = Some(show_line_numbers);
17773 cx.notify();
17774 }
17775
17776 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17777 self.disable_expand_excerpt_buttons = true;
17778 cx.notify();
17779 }
17780
17781 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17782 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17783 cx.notify();
17784 }
17785
17786 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17787 self.show_code_actions = Some(show_code_actions);
17788 cx.notify();
17789 }
17790
17791 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17792 self.show_runnables = Some(show_runnables);
17793 cx.notify();
17794 }
17795
17796 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17797 self.show_breakpoints = Some(show_breakpoints);
17798 cx.notify();
17799 }
17800
17801 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17802 if self.display_map.read(cx).masked != masked {
17803 self.display_map.update(cx, |map, _| map.masked = masked);
17804 }
17805 cx.notify()
17806 }
17807
17808 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17809 self.show_wrap_guides = Some(show_wrap_guides);
17810 cx.notify();
17811 }
17812
17813 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17814 self.show_indent_guides = Some(show_indent_guides);
17815 cx.notify();
17816 }
17817
17818 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17819 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17820 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17821 if let Some(dir) = file.abs_path(cx).parent() {
17822 return Some(dir.to_owned());
17823 }
17824 }
17825
17826 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17827 return Some(project_path.path.to_path_buf());
17828 }
17829 }
17830
17831 None
17832 }
17833
17834 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17835 self.active_excerpt(cx)?
17836 .1
17837 .read(cx)
17838 .file()
17839 .and_then(|f| f.as_local())
17840 }
17841
17842 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17843 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17844 let buffer = buffer.read(cx);
17845 if let Some(project_path) = buffer.project_path(cx) {
17846 let project = self.project.as_ref()?.read(cx);
17847 project.absolute_path(&project_path, cx)
17848 } else {
17849 buffer
17850 .file()
17851 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17852 }
17853 })
17854 }
17855
17856 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17857 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17858 let project_path = buffer.read(cx).project_path(cx)?;
17859 let project = self.project.as_ref()?.read(cx);
17860 let entry = project.entry_for_path(&project_path, cx)?;
17861 let path = entry.path.to_path_buf();
17862 Some(path)
17863 })
17864 }
17865
17866 pub fn reveal_in_finder(
17867 &mut self,
17868 _: &RevealInFileManager,
17869 _window: &mut Window,
17870 cx: &mut Context<Self>,
17871 ) {
17872 if let Some(target) = self.target_file(cx) {
17873 cx.reveal_path(&target.abs_path(cx));
17874 }
17875 }
17876
17877 pub fn copy_path(
17878 &mut self,
17879 _: &zed_actions::workspace::CopyPath,
17880 _window: &mut Window,
17881 cx: &mut Context<Self>,
17882 ) {
17883 if let Some(path) = self.target_file_abs_path(cx) {
17884 if let Some(path) = path.to_str() {
17885 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17886 }
17887 }
17888 }
17889
17890 pub fn copy_relative_path(
17891 &mut self,
17892 _: &zed_actions::workspace::CopyRelativePath,
17893 _window: &mut Window,
17894 cx: &mut Context<Self>,
17895 ) {
17896 if let Some(path) = self.target_file_path(cx) {
17897 if let Some(path) = path.to_str() {
17898 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17899 }
17900 }
17901 }
17902
17903 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17904 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17905 buffer.read(cx).project_path(cx)
17906 } else {
17907 None
17908 }
17909 }
17910
17911 // Returns true if the editor handled a go-to-line request
17912 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17913 maybe!({
17914 let breakpoint_store = self.breakpoint_store.as_ref()?;
17915
17916 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17917 else {
17918 self.clear_row_highlights::<ActiveDebugLine>();
17919 return None;
17920 };
17921
17922 let position = active_stack_frame.position;
17923 let buffer_id = position.buffer_id?;
17924 let snapshot = self
17925 .project
17926 .as_ref()?
17927 .read(cx)
17928 .buffer_for_id(buffer_id, cx)?
17929 .read(cx)
17930 .snapshot();
17931
17932 let mut handled = false;
17933 for (id, ExcerptRange { context, .. }) in
17934 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17935 {
17936 if context.start.cmp(&position, &snapshot).is_ge()
17937 || context.end.cmp(&position, &snapshot).is_lt()
17938 {
17939 continue;
17940 }
17941 let snapshot = self.buffer.read(cx).snapshot(cx);
17942 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17943
17944 handled = true;
17945 self.clear_row_highlights::<ActiveDebugLine>();
17946
17947 self.go_to_line::<ActiveDebugLine>(
17948 multibuffer_anchor,
17949 Some(cx.theme().colors().editor_debugger_active_line_background),
17950 window,
17951 cx,
17952 );
17953
17954 cx.notify();
17955 }
17956
17957 handled.then_some(())
17958 })
17959 .is_some()
17960 }
17961
17962 pub fn copy_file_name_without_extension(
17963 &mut self,
17964 _: &CopyFileNameWithoutExtension,
17965 _: &mut Window,
17966 cx: &mut Context<Self>,
17967 ) {
17968 if let Some(file) = self.target_file(cx) {
17969 if let Some(file_stem) = file.path().file_stem() {
17970 if let Some(name) = file_stem.to_str() {
17971 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17972 }
17973 }
17974 }
17975 }
17976
17977 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17978 if let Some(file) = self.target_file(cx) {
17979 if let Some(file_name) = file.path().file_name() {
17980 if let Some(name) = file_name.to_str() {
17981 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17982 }
17983 }
17984 }
17985 }
17986
17987 pub fn toggle_git_blame(
17988 &mut self,
17989 _: &::git::Blame,
17990 window: &mut Window,
17991 cx: &mut Context<Self>,
17992 ) {
17993 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17994
17995 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17996 self.start_git_blame(true, window, cx);
17997 }
17998
17999 cx.notify();
18000 }
18001
18002 pub fn toggle_git_blame_inline(
18003 &mut self,
18004 _: &ToggleGitBlameInline,
18005 window: &mut Window,
18006 cx: &mut Context<Self>,
18007 ) {
18008 self.toggle_git_blame_inline_internal(true, window, cx);
18009 cx.notify();
18010 }
18011
18012 pub fn open_git_blame_commit(
18013 &mut self,
18014 _: &OpenGitBlameCommit,
18015 window: &mut Window,
18016 cx: &mut Context<Self>,
18017 ) {
18018 self.open_git_blame_commit_internal(window, cx);
18019 }
18020
18021 fn open_git_blame_commit_internal(
18022 &mut self,
18023 window: &mut Window,
18024 cx: &mut Context<Self>,
18025 ) -> Option<()> {
18026 let blame = self.blame.as_ref()?;
18027 let snapshot = self.snapshot(window, cx);
18028 let cursor = self.selections.newest::<Point>(cx).head();
18029 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18030 let blame_entry = blame
18031 .update(cx, |blame, cx| {
18032 blame
18033 .blame_for_rows(
18034 &[RowInfo {
18035 buffer_id: Some(buffer.remote_id()),
18036 buffer_row: Some(point.row),
18037 ..Default::default()
18038 }],
18039 cx,
18040 )
18041 .next()
18042 })
18043 .flatten()?;
18044 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18045 let repo = blame.read(cx).repository(cx)?;
18046 let workspace = self.workspace()?.downgrade();
18047 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18048 None
18049 }
18050
18051 pub fn git_blame_inline_enabled(&self) -> bool {
18052 self.git_blame_inline_enabled
18053 }
18054
18055 pub fn toggle_selection_menu(
18056 &mut self,
18057 _: &ToggleSelectionMenu,
18058 _: &mut Window,
18059 cx: &mut Context<Self>,
18060 ) {
18061 self.show_selection_menu = self
18062 .show_selection_menu
18063 .map(|show_selections_menu| !show_selections_menu)
18064 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18065
18066 cx.notify();
18067 }
18068
18069 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18070 self.show_selection_menu
18071 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18072 }
18073
18074 fn start_git_blame(
18075 &mut self,
18076 user_triggered: bool,
18077 window: &mut Window,
18078 cx: &mut Context<Self>,
18079 ) {
18080 if let Some(project) = self.project.as_ref() {
18081 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18082 return;
18083 };
18084
18085 if buffer.read(cx).file().is_none() {
18086 return;
18087 }
18088
18089 let focused = self.focus_handle(cx).contains_focused(window, cx);
18090
18091 let project = project.clone();
18092 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18093 self.blame_subscription =
18094 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18095 self.blame = Some(blame);
18096 }
18097 }
18098
18099 fn toggle_git_blame_inline_internal(
18100 &mut self,
18101 user_triggered: bool,
18102 window: &mut Window,
18103 cx: &mut Context<Self>,
18104 ) {
18105 if self.git_blame_inline_enabled {
18106 self.git_blame_inline_enabled = false;
18107 self.show_git_blame_inline = false;
18108 self.show_git_blame_inline_delay_task.take();
18109 } else {
18110 self.git_blame_inline_enabled = true;
18111 self.start_git_blame_inline(user_triggered, window, cx);
18112 }
18113
18114 cx.notify();
18115 }
18116
18117 fn start_git_blame_inline(
18118 &mut self,
18119 user_triggered: bool,
18120 window: &mut Window,
18121 cx: &mut Context<Self>,
18122 ) {
18123 self.start_git_blame(user_triggered, window, cx);
18124
18125 if ProjectSettings::get_global(cx)
18126 .git
18127 .inline_blame_delay()
18128 .is_some()
18129 {
18130 self.start_inline_blame_timer(window, cx);
18131 } else {
18132 self.show_git_blame_inline = true
18133 }
18134 }
18135
18136 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18137 self.blame.as_ref()
18138 }
18139
18140 pub fn show_git_blame_gutter(&self) -> bool {
18141 self.show_git_blame_gutter
18142 }
18143
18144 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18145 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18146 }
18147
18148 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18149 self.show_git_blame_inline
18150 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18151 && !self.newest_selection_head_on_empty_line(cx)
18152 && self.has_blame_entries(cx)
18153 }
18154
18155 fn has_blame_entries(&self, cx: &App) -> bool {
18156 self.blame()
18157 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18158 }
18159
18160 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18161 let cursor_anchor = self.selections.newest_anchor().head();
18162
18163 let snapshot = self.buffer.read(cx).snapshot(cx);
18164 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18165
18166 snapshot.line_len(buffer_row) == 0
18167 }
18168
18169 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18170 let buffer_and_selection = maybe!({
18171 let selection = self.selections.newest::<Point>(cx);
18172 let selection_range = selection.range();
18173
18174 let multi_buffer = self.buffer().read(cx);
18175 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18176 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18177
18178 let (buffer, range, _) = if selection.reversed {
18179 buffer_ranges.first()
18180 } else {
18181 buffer_ranges.last()
18182 }?;
18183
18184 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18185 ..text::ToPoint::to_point(&range.end, &buffer).row;
18186 Some((
18187 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18188 selection,
18189 ))
18190 });
18191
18192 let Some((buffer, selection)) = buffer_and_selection else {
18193 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18194 };
18195
18196 let Some(project) = self.project.as_ref() else {
18197 return Task::ready(Err(anyhow!("editor does not have project")));
18198 };
18199
18200 project.update(cx, |project, cx| {
18201 project.get_permalink_to_line(&buffer, selection, cx)
18202 })
18203 }
18204
18205 pub fn copy_permalink_to_line(
18206 &mut self,
18207 _: &CopyPermalinkToLine,
18208 window: &mut Window,
18209 cx: &mut Context<Self>,
18210 ) {
18211 let permalink_task = self.get_permalink_to_line(cx);
18212 let workspace = self.workspace();
18213
18214 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18215 Ok(permalink) => {
18216 cx.update(|_, cx| {
18217 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18218 })
18219 .ok();
18220 }
18221 Err(err) => {
18222 let message = format!("Failed to copy permalink: {err}");
18223
18224 anyhow::Result::<()>::Err(err).log_err();
18225
18226 if let Some(workspace) = workspace {
18227 workspace
18228 .update_in(cx, |workspace, _, cx| {
18229 struct CopyPermalinkToLine;
18230
18231 workspace.show_toast(
18232 Toast::new(
18233 NotificationId::unique::<CopyPermalinkToLine>(),
18234 message,
18235 ),
18236 cx,
18237 )
18238 })
18239 .ok();
18240 }
18241 }
18242 })
18243 .detach();
18244 }
18245
18246 pub fn copy_file_location(
18247 &mut self,
18248 _: &CopyFileLocation,
18249 _: &mut Window,
18250 cx: &mut Context<Self>,
18251 ) {
18252 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18253 if let Some(file) = self.target_file(cx) {
18254 if let Some(path) = file.path().to_str() {
18255 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18256 }
18257 }
18258 }
18259
18260 pub fn open_permalink_to_line(
18261 &mut self,
18262 _: &OpenPermalinkToLine,
18263 window: &mut Window,
18264 cx: &mut Context<Self>,
18265 ) {
18266 let permalink_task = self.get_permalink_to_line(cx);
18267 let workspace = self.workspace();
18268
18269 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18270 Ok(permalink) => {
18271 cx.update(|_, cx| {
18272 cx.open_url(permalink.as_ref());
18273 })
18274 .ok();
18275 }
18276 Err(err) => {
18277 let message = format!("Failed to open permalink: {err}");
18278
18279 anyhow::Result::<()>::Err(err).log_err();
18280
18281 if let Some(workspace) = workspace {
18282 workspace
18283 .update(cx, |workspace, cx| {
18284 struct OpenPermalinkToLine;
18285
18286 workspace.show_toast(
18287 Toast::new(
18288 NotificationId::unique::<OpenPermalinkToLine>(),
18289 message,
18290 ),
18291 cx,
18292 )
18293 })
18294 .ok();
18295 }
18296 }
18297 })
18298 .detach();
18299 }
18300
18301 pub fn insert_uuid_v4(
18302 &mut self,
18303 _: &InsertUuidV4,
18304 window: &mut Window,
18305 cx: &mut Context<Self>,
18306 ) {
18307 self.insert_uuid(UuidVersion::V4, window, cx);
18308 }
18309
18310 pub fn insert_uuid_v7(
18311 &mut self,
18312 _: &InsertUuidV7,
18313 window: &mut Window,
18314 cx: &mut Context<Self>,
18315 ) {
18316 self.insert_uuid(UuidVersion::V7, window, cx);
18317 }
18318
18319 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18321 self.transact(window, cx, |this, window, cx| {
18322 let edits = this
18323 .selections
18324 .all::<Point>(cx)
18325 .into_iter()
18326 .map(|selection| {
18327 let uuid = match version {
18328 UuidVersion::V4 => uuid::Uuid::new_v4(),
18329 UuidVersion::V7 => uuid::Uuid::now_v7(),
18330 };
18331
18332 (selection.range(), uuid.to_string())
18333 });
18334 this.edit(edits, cx);
18335 this.refresh_inline_completion(true, false, window, cx);
18336 });
18337 }
18338
18339 pub fn open_selections_in_multibuffer(
18340 &mut self,
18341 _: &OpenSelectionsInMultibuffer,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) {
18345 let multibuffer = self.buffer.read(cx);
18346
18347 let Some(buffer) = multibuffer.as_singleton() else {
18348 return;
18349 };
18350
18351 let Some(workspace) = self.workspace() else {
18352 return;
18353 };
18354
18355 let title = multibuffer.title(cx).to_string();
18356
18357 let locations = self
18358 .selections
18359 .all_anchors(cx)
18360 .into_iter()
18361 .map(|selection| Location {
18362 buffer: buffer.clone(),
18363 range: selection.start.text_anchor..selection.end.text_anchor,
18364 })
18365 .collect::<Vec<_>>();
18366
18367 cx.spawn_in(window, async move |_, cx| {
18368 workspace.update_in(cx, |workspace, window, cx| {
18369 Self::open_locations_in_multibuffer(
18370 workspace,
18371 locations,
18372 format!("Selections for '{title}'"),
18373 false,
18374 MultibufferSelectionMode::All,
18375 window,
18376 cx,
18377 );
18378 })
18379 })
18380 .detach();
18381 }
18382
18383 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18384 /// last highlight added will be used.
18385 ///
18386 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18387 pub fn highlight_rows<T: 'static>(
18388 &mut self,
18389 range: Range<Anchor>,
18390 color: Hsla,
18391 options: RowHighlightOptions,
18392 cx: &mut Context<Self>,
18393 ) {
18394 let snapshot = self.buffer().read(cx).snapshot(cx);
18395 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18396 let ix = row_highlights.binary_search_by(|highlight| {
18397 Ordering::Equal
18398 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18399 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18400 });
18401
18402 if let Err(mut ix) = ix {
18403 let index = post_inc(&mut self.highlight_order);
18404
18405 // If this range intersects with the preceding highlight, then merge it with
18406 // the preceding highlight. Otherwise insert a new highlight.
18407 let mut merged = false;
18408 if ix > 0 {
18409 let prev_highlight = &mut row_highlights[ix - 1];
18410 if prev_highlight
18411 .range
18412 .end
18413 .cmp(&range.start, &snapshot)
18414 .is_ge()
18415 {
18416 ix -= 1;
18417 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18418 prev_highlight.range.end = range.end;
18419 }
18420 merged = true;
18421 prev_highlight.index = index;
18422 prev_highlight.color = color;
18423 prev_highlight.options = options;
18424 }
18425 }
18426
18427 if !merged {
18428 row_highlights.insert(
18429 ix,
18430 RowHighlight {
18431 range: range.clone(),
18432 index,
18433 color,
18434 options,
18435 type_id: TypeId::of::<T>(),
18436 },
18437 );
18438 }
18439
18440 // If any of the following highlights intersect with this one, merge them.
18441 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18442 let highlight = &row_highlights[ix];
18443 if next_highlight
18444 .range
18445 .start
18446 .cmp(&highlight.range.end, &snapshot)
18447 .is_le()
18448 {
18449 if next_highlight
18450 .range
18451 .end
18452 .cmp(&highlight.range.end, &snapshot)
18453 .is_gt()
18454 {
18455 row_highlights[ix].range.end = next_highlight.range.end;
18456 }
18457 row_highlights.remove(ix + 1);
18458 } else {
18459 break;
18460 }
18461 }
18462 }
18463 }
18464
18465 /// Remove any highlighted row ranges of the given type that intersect the
18466 /// given ranges.
18467 pub fn remove_highlighted_rows<T: 'static>(
18468 &mut self,
18469 ranges_to_remove: Vec<Range<Anchor>>,
18470 cx: &mut Context<Self>,
18471 ) {
18472 let snapshot = self.buffer().read(cx).snapshot(cx);
18473 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18474 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18475 row_highlights.retain(|highlight| {
18476 while let Some(range_to_remove) = ranges_to_remove.peek() {
18477 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18478 Ordering::Less | Ordering::Equal => {
18479 ranges_to_remove.next();
18480 }
18481 Ordering::Greater => {
18482 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18483 Ordering::Less | Ordering::Equal => {
18484 return false;
18485 }
18486 Ordering::Greater => break,
18487 }
18488 }
18489 }
18490 }
18491
18492 true
18493 })
18494 }
18495
18496 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18497 pub fn clear_row_highlights<T: 'static>(&mut self) {
18498 self.highlighted_rows.remove(&TypeId::of::<T>());
18499 }
18500
18501 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18502 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18503 self.highlighted_rows
18504 .get(&TypeId::of::<T>())
18505 .map_or(&[] as &[_], |vec| vec.as_slice())
18506 .iter()
18507 .map(|highlight| (highlight.range.clone(), highlight.color))
18508 }
18509
18510 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18511 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18512 /// Allows to ignore certain kinds of highlights.
18513 pub fn highlighted_display_rows(
18514 &self,
18515 window: &mut Window,
18516 cx: &mut App,
18517 ) -> BTreeMap<DisplayRow, LineHighlight> {
18518 let snapshot = self.snapshot(window, cx);
18519 let mut used_highlight_orders = HashMap::default();
18520 self.highlighted_rows
18521 .iter()
18522 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18523 .fold(
18524 BTreeMap::<DisplayRow, LineHighlight>::new(),
18525 |mut unique_rows, highlight| {
18526 let start = highlight.range.start.to_display_point(&snapshot);
18527 let end = highlight.range.end.to_display_point(&snapshot);
18528 let start_row = start.row().0;
18529 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18530 && end.column() == 0
18531 {
18532 end.row().0.saturating_sub(1)
18533 } else {
18534 end.row().0
18535 };
18536 for row in start_row..=end_row {
18537 let used_index =
18538 used_highlight_orders.entry(row).or_insert(highlight.index);
18539 if highlight.index >= *used_index {
18540 *used_index = highlight.index;
18541 unique_rows.insert(
18542 DisplayRow(row),
18543 LineHighlight {
18544 include_gutter: highlight.options.include_gutter,
18545 border: None,
18546 background: highlight.color.into(),
18547 type_id: Some(highlight.type_id),
18548 },
18549 );
18550 }
18551 }
18552 unique_rows
18553 },
18554 )
18555 }
18556
18557 pub fn highlighted_display_row_for_autoscroll(
18558 &self,
18559 snapshot: &DisplaySnapshot,
18560 ) -> Option<DisplayRow> {
18561 self.highlighted_rows
18562 .values()
18563 .flat_map(|highlighted_rows| highlighted_rows.iter())
18564 .filter_map(|highlight| {
18565 if highlight.options.autoscroll {
18566 Some(highlight.range.start.to_display_point(snapshot).row())
18567 } else {
18568 None
18569 }
18570 })
18571 .min()
18572 }
18573
18574 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18575 self.highlight_background::<SearchWithinRange>(
18576 ranges,
18577 |colors| colors.colors().editor_document_highlight_read_background,
18578 cx,
18579 )
18580 }
18581
18582 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18583 self.breadcrumb_header = Some(new_header);
18584 }
18585
18586 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18587 self.clear_background_highlights::<SearchWithinRange>(cx);
18588 }
18589
18590 pub fn highlight_background<T: 'static>(
18591 &mut self,
18592 ranges: &[Range<Anchor>],
18593 color_fetcher: fn(&Theme) -> Hsla,
18594 cx: &mut Context<Self>,
18595 ) {
18596 self.background_highlights.insert(
18597 HighlightKey::Type(TypeId::of::<T>()),
18598 (color_fetcher, Arc::from(ranges)),
18599 );
18600 self.scrollbar_marker_state.dirty = true;
18601 cx.notify();
18602 }
18603
18604 pub fn highlight_background_key<T: 'static>(
18605 &mut self,
18606 key: usize,
18607 ranges: &[Range<Anchor>],
18608 color_fetcher: fn(&Theme) -> Hsla,
18609 cx: &mut Context<Self>,
18610 ) {
18611 self.background_highlights.insert(
18612 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18613 (color_fetcher, Arc::from(ranges)),
18614 );
18615 self.scrollbar_marker_state.dirty = true;
18616 cx.notify();
18617 }
18618
18619 pub fn clear_background_highlights<T: 'static>(
18620 &mut self,
18621 cx: &mut Context<Self>,
18622 ) -> Option<BackgroundHighlight> {
18623 let text_highlights = self
18624 .background_highlights
18625 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18626 if !text_highlights.1.is_empty() {
18627 self.scrollbar_marker_state.dirty = true;
18628 cx.notify();
18629 }
18630 Some(text_highlights)
18631 }
18632
18633 pub fn highlight_gutter<T: 'static>(
18634 &mut self,
18635 ranges: impl Into<Vec<Range<Anchor>>>,
18636 color_fetcher: fn(&App) -> Hsla,
18637 cx: &mut Context<Self>,
18638 ) {
18639 self.gutter_highlights
18640 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18641 cx.notify();
18642 }
18643
18644 pub fn clear_gutter_highlights<T: 'static>(
18645 &mut self,
18646 cx: &mut Context<Self>,
18647 ) -> Option<GutterHighlight> {
18648 cx.notify();
18649 self.gutter_highlights.remove(&TypeId::of::<T>())
18650 }
18651
18652 pub fn insert_gutter_highlight<T: 'static>(
18653 &mut self,
18654 range: Range<Anchor>,
18655 color_fetcher: fn(&App) -> Hsla,
18656 cx: &mut Context<Self>,
18657 ) {
18658 let snapshot = self.buffer().read(cx).snapshot(cx);
18659 let mut highlights = self
18660 .gutter_highlights
18661 .remove(&TypeId::of::<T>())
18662 .map(|(_, highlights)| highlights)
18663 .unwrap_or_default();
18664 let ix = highlights.binary_search_by(|highlight| {
18665 Ordering::Equal
18666 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18667 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18668 });
18669 if let Err(ix) = ix {
18670 highlights.insert(ix, range);
18671 }
18672 self.gutter_highlights
18673 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18674 }
18675
18676 pub fn remove_gutter_highlights<T: 'static>(
18677 &mut self,
18678 ranges_to_remove: Vec<Range<Anchor>>,
18679 cx: &mut Context<Self>,
18680 ) {
18681 let snapshot = self.buffer().read(cx).snapshot(cx);
18682 let Some((color_fetcher, mut gutter_highlights)) =
18683 self.gutter_highlights.remove(&TypeId::of::<T>())
18684 else {
18685 return;
18686 };
18687 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18688 gutter_highlights.retain(|highlight| {
18689 while let Some(range_to_remove) = ranges_to_remove.peek() {
18690 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18691 Ordering::Less | Ordering::Equal => {
18692 ranges_to_remove.next();
18693 }
18694 Ordering::Greater => {
18695 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18696 Ordering::Less | Ordering::Equal => {
18697 return false;
18698 }
18699 Ordering::Greater => break,
18700 }
18701 }
18702 }
18703 }
18704
18705 true
18706 });
18707 self.gutter_highlights
18708 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18709 }
18710
18711 #[cfg(feature = "test-support")]
18712 pub fn all_text_highlights(
18713 &self,
18714 window: &mut Window,
18715 cx: &mut Context<Self>,
18716 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
18717 let snapshot = self.snapshot(window, cx);
18718 self.display_map.update(cx, |display_map, _| {
18719 display_map
18720 .all_text_highlights()
18721 .map(|highlight| {
18722 let (style, ranges) = highlight.as_ref();
18723 (
18724 *style,
18725 ranges
18726 .iter()
18727 .map(|range| range.clone().to_display_points(&snapshot))
18728 .collect(),
18729 )
18730 })
18731 .collect()
18732 })
18733 }
18734
18735 #[cfg(feature = "test-support")]
18736 pub fn all_text_background_highlights(
18737 &self,
18738 window: &mut Window,
18739 cx: &mut Context<Self>,
18740 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18741 let snapshot = self.snapshot(window, cx);
18742 let buffer = &snapshot.buffer_snapshot;
18743 let start = buffer.anchor_before(0);
18744 let end = buffer.anchor_after(buffer.len());
18745 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
18746 }
18747
18748 #[cfg(feature = "test-support")]
18749 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18750 let snapshot = self.buffer().read(cx).snapshot(cx);
18751
18752 let highlights = self
18753 .background_highlights
18754 .get(&HighlightKey::Type(TypeId::of::<
18755 items::BufferSearchHighlights,
18756 >()));
18757
18758 if let Some((_color, ranges)) = highlights {
18759 ranges
18760 .iter()
18761 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18762 .collect_vec()
18763 } else {
18764 vec![]
18765 }
18766 }
18767
18768 fn document_highlights_for_position<'a>(
18769 &'a self,
18770 position: Anchor,
18771 buffer: &'a MultiBufferSnapshot,
18772 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18773 let read_highlights = self
18774 .background_highlights
18775 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
18776 .map(|h| &h.1);
18777 let write_highlights = self
18778 .background_highlights
18779 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
18780 .map(|h| &h.1);
18781 let left_position = position.bias_left(buffer);
18782 let right_position = position.bias_right(buffer);
18783 read_highlights
18784 .into_iter()
18785 .chain(write_highlights)
18786 .flat_map(move |ranges| {
18787 let start_ix = match ranges.binary_search_by(|probe| {
18788 let cmp = probe.end.cmp(&left_position, buffer);
18789 if cmp.is_ge() {
18790 Ordering::Greater
18791 } else {
18792 Ordering::Less
18793 }
18794 }) {
18795 Ok(i) | Err(i) => i,
18796 };
18797
18798 ranges[start_ix..]
18799 .iter()
18800 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18801 })
18802 }
18803
18804 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18805 self.background_highlights
18806 .get(&HighlightKey::Type(TypeId::of::<T>()))
18807 .map_or(false, |(_, highlights)| !highlights.is_empty())
18808 }
18809
18810 pub fn background_highlights_in_range(
18811 &self,
18812 search_range: Range<Anchor>,
18813 display_snapshot: &DisplaySnapshot,
18814 theme: &Theme,
18815 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18816 let mut results = Vec::new();
18817 for (color_fetcher, ranges) in self.background_highlights.values() {
18818 let color = color_fetcher(theme);
18819 let start_ix = match ranges.binary_search_by(|probe| {
18820 let cmp = probe
18821 .end
18822 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18823 if cmp.is_gt() {
18824 Ordering::Greater
18825 } else {
18826 Ordering::Less
18827 }
18828 }) {
18829 Ok(i) | Err(i) => i,
18830 };
18831 for range in &ranges[start_ix..] {
18832 if range
18833 .start
18834 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18835 .is_ge()
18836 {
18837 break;
18838 }
18839
18840 let start = range.start.to_display_point(display_snapshot);
18841 let end = range.end.to_display_point(display_snapshot);
18842 results.push((start..end, color))
18843 }
18844 }
18845 results
18846 }
18847
18848 pub fn background_highlight_row_ranges<T: 'static>(
18849 &self,
18850 search_range: Range<Anchor>,
18851 display_snapshot: &DisplaySnapshot,
18852 count: usize,
18853 ) -> Vec<RangeInclusive<DisplayPoint>> {
18854 let mut results = Vec::new();
18855 let Some((_, ranges)) = self
18856 .background_highlights
18857 .get(&HighlightKey::Type(TypeId::of::<T>()))
18858 else {
18859 return vec![];
18860 };
18861
18862 let start_ix = match ranges.binary_search_by(|probe| {
18863 let cmp = probe
18864 .end
18865 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18866 if cmp.is_gt() {
18867 Ordering::Greater
18868 } else {
18869 Ordering::Less
18870 }
18871 }) {
18872 Ok(i) | Err(i) => i,
18873 };
18874 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18875 if let (Some(start_display), Some(end_display)) = (start, end) {
18876 results.push(
18877 start_display.to_display_point(display_snapshot)
18878 ..=end_display.to_display_point(display_snapshot),
18879 );
18880 }
18881 };
18882 let mut start_row: Option<Point> = None;
18883 let mut end_row: Option<Point> = None;
18884 if ranges.len() > count {
18885 return Vec::new();
18886 }
18887 for range in &ranges[start_ix..] {
18888 if range
18889 .start
18890 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18891 .is_ge()
18892 {
18893 break;
18894 }
18895 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18896 if let Some(current_row) = &end_row {
18897 if end.row == current_row.row {
18898 continue;
18899 }
18900 }
18901 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18902 if start_row.is_none() {
18903 assert_eq!(end_row, None);
18904 start_row = Some(start);
18905 end_row = Some(end);
18906 continue;
18907 }
18908 if let Some(current_end) = end_row.as_mut() {
18909 if start.row > current_end.row + 1 {
18910 push_region(start_row, end_row);
18911 start_row = Some(start);
18912 end_row = Some(end);
18913 } else {
18914 // Merge two hunks.
18915 *current_end = end;
18916 }
18917 } else {
18918 unreachable!();
18919 }
18920 }
18921 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18922 push_region(start_row, end_row);
18923 results
18924 }
18925
18926 pub fn gutter_highlights_in_range(
18927 &self,
18928 search_range: Range<Anchor>,
18929 display_snapshot: &DisplaySnapshot,
18930 cx: &App,
18931 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18932 let mut results = Vec::new();
18933 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18934 let color = color_fetcher(cx);
18935 let start_ix = match ranges.binary_search_by(|probe| {
18936 let cmp = probe
18937 .end
18938 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18939 if cmp.is_gt() {
18940 Ordering::Greater
18941 } else {
18942 Ordering::Less
18943 }
18944 }) {
18945 Ok(i) | Err(i) => i,
18946 };
18947 for range in &ranges[start_ix..] {
18948 if range
18949 .start
18950 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18951 .is_ge()
18952 {
18953 break;
18954 }
18955
18956 let start = range.start.to_display_point(display_snapshot);
18957 let end = range.end.to_display_point(display_snapshot);
18958 results.push((start..end, color))
18959 }
18960 }
18961 results
18962 }
18963
18964 /// Get the text ranges corresponding to the redaction query
18965 pub fn redacted_ranges(
18966 &self,
18967 search_range: Range<Anchor>,
18968 display_snapshot: &DisplaySnapshot,
18969 cx: &App,
18970 ) -> Vec<Range<DisplayPoint>> {
18971 display_snapshot
18972 .buffer_snapshot
18973 .redacted_ranges(search_range, |file| {
18974 if let Some(file) = file {
18975 file.is_private()
18976 && EditorSettings::get(
18977 Some(SettingsLocation {
18978 worktree_id: file.worktree_id(cx),
18979 path: file.path().as_ref(),
18980 }),
18981 cx,
18982 )
18983 .redact_private_values
18984 } else {
18985 false
18986 }
18987 })
18988 .map(|range| {
18989 range.start.to_display_point(display_snapshot)
18990 ..range.end.to_display_point(display_snapshot)
18991 })
18992 .collect()
18993 }
18994
18995 pub fn highlight_text_key<T: 'static>(
18996 &mut self,
18997 key: usize,
18998 ranges: Vec<Range<Anchor>>,
18999 style: HighlightStyle,
19000 cx: &mut Context<Self>,
19001 ) {
19002 self.display_map.update(cx, |map, _| {
19003 map.highlight_text(
19004 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19005 ranges,
19006 style,
19007 );
19008 });
19009 cx.notify();
19010 }
19011
19012 pub fn highlight_text<T: 'static>(
19013 &mut self,
19014 ranges: Vec<Range<Anchor>>,
19015 style: HighlightStyle,
19016 cx: &mut Context<Self>,
19017 ) {
19018 self.display_map.update(cx, |map, _| {
19019 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19020 });
19021 cx.notify();
19022 }
19023
19024 pub(crate) fn highlight_inlays<T: 'static>(
19025 &mut self,
19026 highlights: Vec<InlayHighlight>,
19027 style: HighlightStyle,
19028 cx: &mut Context<Self>,
19029 ) {
19030 self.display_map.update(cx, |map, _| {
19031 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19032 });
19033 cx.notify();
19034 }
19035
19036 pub fn text_highlights<'a, T: 'static>(
19037 &'a self,
19038 cx: &'a App,
19039 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19040 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19041 }
19042
19043 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19044 let cleared = self
19045 .display_map
19046 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19047 if cleared {
19048 cx.notify();
19049 }
19050 }
19051
19052 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19053 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19054 && self.focus_handle.is_focused(window)
19055 }
19056
19057 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19058 self.show_cursor_when_unfocused = is_enabled;
19059 cx.notify();
19060 }
19061
19062 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19063 cx.notify();
19064 }
19065
19066 fn on_debug_session_event(
19067 &mut self,
19068 _session: Entity<Session>,
19069 event: &SessionEvent,
19070 cx: &mut Context<Self>,
19071 ) {
19072 match event {
19073 SessionEvent::InvalidateInlineValue => {
19074 self.refresh_inline_values(cx);
19075 }
19076 _ => {}
19077 }
19078 }
19079
19080 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19081 let Some(project) = self.project.clone() else {
19082 return;
19083 };
19084
19085 if !self.inline_value_cache.enabled {
19086 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19087 self.splice_inlays(&inlays, Vec::new(), cx);
19088 return;
19089 }
19090
19091 let current_execution_position = self
19092 .highlighted_rows
19093 .get(&TypeId::of::<ActiveDebugLine>())
19094 .and_then(|lines| lines.last().map(|line| line.range.start));
19095
19096 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19097 let inline_values = editor
19098 .update(cx, |editor, cx| {
19099 let Some(current_execution_position) = current_execution_position else {
19100 return Some(Task::ready(Ok(Vec::new())));
19101 };
19102
19103 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19104 let snapshot = buffer.snapshot(cx);
19105
19106 let excerpt = snapshot.excerpt_containing(
19107 current_execution_position..current_execution_position,
19108 )?;
19109
19110 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19111 })?;
19112
19113 let range =
19114 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19115
19116 project.inline_values(buffer, range, cx)
19117 })
19118 .ok()
19119 .flatten()?
19120 .await
19121 .context("refreshing debugger inlays")
19122 .log_err()?;
19123
19124 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19125
19126 for (buffer_id, inline_value) in inline_values
19127 .into_iter()
19128 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19129 {
19130 buffer_inline_values
19131 .entry(buffer_id)
19132 .or_default()
19133 .push(inline_value);
19134 }
19135
19136 editor
19137 .update(cx, |editor, cx| {
19138 let snapshot = editor.buffer.read(cx).snapshot(cx);
19139 let mut new_inlays = Vec::default();
19140
19141 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19142 let buffer_id = buffer_snapshot.remote_id();
19143 buffer_inline_values
19144 .get(&buffer_id)
19145 .into_iter()
19146 .flatten()
19147 .for_each(|hint| {
19148 let inlay = Inlay::debugger(
19149 post_inc(&mut editor.next_inlay_id),
19150 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19151 hint.text(),
19152 );
19153
19154 new_inlays.push(inlay);
19155 });
19156 }
19157
19158 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19159 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19160
19161 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19162 })
19163 .ok()?;
19164 Some(())
19165 });
19166 }
19167
19168 fn on_buffer_event(
19169 &mut self,
19170 multibuffer: &Entity<MultiBuffer>,
19171 event: &multi_buffer::Event,
19172 window: &mut Window,
19173 cx: &mut Context<Self>,
19174 ) {
19175 match event {
19176 multi_buffer::Event::Edited {
19177 singleton_buffer_edited,
19178 edited_buffer,
19179 } => {
19180 self.scrollbar_marker_state.dirty = true;
19181 self.active_indent_guides_state.dirty = true;
19182 self.refresh_active_diagnostics(cx);
19183 self.refresh_code_actions(window, cx);
19184 self.refresh_selected_text_highlights(true, window, cx);
19185 refresh_matching_bracket_highlights(self, window, cx);
19186 if self.has_active_inline_completion() {
19187 self.update_visible_inline_completion(window, cx);
19188 }
19189 if let Some(project) = self.project.as_ref() {
19190 if let Some(edited_buffer) = edited_buffer {
19191 project.update(cx, |project, cx| {
19192 self.registered_buffers
19193 .entry(edited_buffer.read(cx).remote_id())
19194 .or_insert_with(|| {
19195 project
19196 .register_buffer_with_language_servers(&edited_buffer, cx)
19197 });
19198 });
19199 }
19200 }
19201 cx.emit(EditorEvent::BufferEdited);
19202 cx.emit(SearchEvent::MatchesInvalidated);
19203
19204 if let Some(buffer) = edited_buffer {
19205 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19206 }
19207
19208 if *singleton_buffer_edited {
19209 if let Some(buffer) = edited_buffer {
19210 if buffer.read(cx).file().is_none() {
19211 cx.emit(EditorEvent::TitleChanged);
19212 }
19213 }
19214 if let Some(project) = &self.project {
19215 #[allow(clippy::mutable_key_type)]
19216 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19217 multibuffer
19218 .all_buffers()
19219 .into_iter()
19220 .filter_map(|buffer| {
19221 buffer.update(cx, |buffer, cx| {
19222 let language = buffer.language()?;
19223 let should_discard = project.update(cx, |project, cx| {
19224 project.is_local()
19225 && !project.has_language_servers_for(buffer, cx)
19226 });
19227 should_discard.not().then_some(language.clone())
19228 })
19229 })
19230 .collect::<HashSet<_>>()
19231 });
19232 if !languages_affected.is_empty() {
19233 self.refresh_inlay_hints(
19234 InlayHintRefreshReason::BufferEdited(languages_affected),
19235 cx,
19236 );
19237 }
19238 }
19239 }
19240
19241 let Some(project) = &self.project else { return };
19242 let (telemetry, is_via_ssh) = {
19243 let project = project.read(cx);
19244 let telemetry = project.client().telemetry().clone();
19245 let is_via_ssh = project.is_via_ssh();
19246 (telemetry, is_via_ssh)
19247 };
19248 refresh_linked_ranges(self, window, cx);
19249 telemetry.log_edit_event("editor", is_via_ssh);
19250 }
19251 multi_buffer::Event::ExcerptsAdded {
19252 buffer,
19253 predecessor,
19254 excerpts,
19255 } => {
19256 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19257 let buffer_id = buffer.read(cx).remote_id();
19258 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19259 if let Some(project) = &self.project {
19260 update_uncommitted_diff_for_buffer(
19261 cx.entity(),
19262 project,
19263 [buffer.clone()],
19264 self.buffer.clone(),
19265 cx,
19266 )
19267 .detach();
19268 }
19269 }
19270 self.update_lsp_data(false, Some(buffer_id), window, cx);
19271 cx.emit(EditorEvent::ExcerptsAdded {
19272 buffer: buffer.clone(),
19273 predecessor: *predecessor,
19274 excerpts: excerpts.clone(),
19275 });
19276 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19277 }
19278 multi_buffer::Event::ExcerptsRemoved {
19279 ids,
19280 removed_buffer_ids,
19281 } => {
19282 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19283 let buffer = self.buffer.read(cx);
19284 self.registered_buffers
19285 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19286 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19287 cx.emit(EditorEvent::ExcerptsRemoved {
19288 ids: ids.clone(),
19289 removed_buffer_ids: removed_buffer_ids.clone(),
19290 });
19291 }
19292 multi_buffer::Event::ExcerptsEdited {
19293 excerpt_ids,
19294 buffer_ids,
19295 } => {
19296 self.display_map.update(cx, |map, cx| {
19297 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19298 });
19299 cx.emit(EditorEvent::ExcerptsEdited {
19300 ids: excerpt_ids.clone(),
19301 });
19302 }
19303 multi_buffer::Event::ExcerptsExpanded { ids } => {
19304 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19305 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19306 }
19307 multi_buffer::Event::Reparsed(buffer_id) => {
19308 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19309 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19310
19311 cx.emit(EditorEvent::Reparsed(*buffer_id));
19312 }
19313 multi_buffer::Event::DiffHunksToggled => {
19314 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19315 }
19316 multi_buffer::Event::LanguageChanged(buffer_id) => {
19317 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19318 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19319 cx.emit(EditorEvent::Reparsed(*buffer_id));
19320 cx.notify();
19321 }
19322 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19323 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19324 multi_buffer::Event::FileHandleChanged
19325 | multi_buffer::Event::Reloaded
19326 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19327 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19328 multi_buffer::Event::DiagnosticsUpdated => {
19329 self.update_diagnostics_state(window, cx);
19330 }
19331 _ => {}
19332 };
19333 }
19334
19335 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19336 self.refresh_active_diagnostics(cx);
19337 self.refresh_inline_diagnostics(true, window, cx);
19338 self.scrollbar_marker_state.dirty = true;
19339 cx.notify();
19340 }
19341
19342 pub fn start_temporary_diff_override(&mut self) {
19343 self.load_diff_task.take();
19344 self.temporary_diff_override = true;
19345 }
19346
19347 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19348 self.temporary_diff_override = false;
19349 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19350 self.buffer.update(cx, |buffer, cx| {
19351 buffer.set_all_diff_hunks_collapsed(cx);
19352 });
19353
19354 if let Some(project) = self.project.clone() {
19355 self.load_diff_task = Some(
19356 update_uncommitted_diff_for_buffer(
19357 cx.entity(),
19358 &project,
19359 self.buffer.read(cx).all_buffers(),
19360 self.buffer.clone(),
19361 cx,
19362 )
19363 .shared(),
19364 );
19365 }
19366 }
19367
19368 fn on_display_map_changed(
19369 &mut self,
19370 _: Entity<DisplayMap>,
19371 _: &mut Window,
19372 cx: &mut Context<Self>,
19373 ) {
19374 cx.notify();
19375 }
19376
19377 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19378 let new_severity = if self.diagnostics_enabled() {
19379 EditorSettings::get_global(cx)
19380 .diagnostics_max_severity
19381 .unwrap_or(DiagnosticSeverity::Hint)
19382 } else {
19383 DiagnosticSeverity::Off
19384 };
19385 self.set_max_diagnostics_severity(new_severity, cx);
19386 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19387 self.update_edit_prediction_settings(cx);
19388 self.refresh_inline_completion(true, false, window, cx);
19389 self.refresh_inlay_hints(
19390 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19391 self.selections.newest_anchor().head(),
19392 &self.buffer.read(cx).snapshot(cx),
19393 cx,
19394 )),
19395 cx,
19396 );
19397
19398 let old_cursor_shape = self.cursor_shape;
19399
19400 {
19401 let editor_settings = EditorSettings::get_global(cx);
19402 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19403 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19404 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19405 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19406 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19407 }
19408
19409 if old_cursor_shape != self.cursor_shape {
19410 cx.emit(EditorEvent::CursorShapeChanged);
19411 }
19412
19413 let project_settings = ProjectSettings::get_global(cx);
19414 self.serialize_dirty_buffers =
19415 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19416
19417 if self.mode.is_full() {
19418 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19419 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19420 if self.show_inline_diagnostics != show_inline_diagnostics {
19421 self.show_inline_diagnostics = show_inline_diagnostics;
19422 self.refresh_inline_diagnostics(false, window, cx);
19423 }
19424
19425 if self.git_blame_inline_enabled != inline_blame_enabled {
19426 self.toggle_git_blame_inline_internal(false, window, cx);
19427 }
19428
19429 let minimap_settings = EditorSettings::get_global(cx).minimap;
19430 if self.minimap_visibility != MinimapVisibility::Disabled {
19431 if self.minimap_visibility.settings_visibility()
19432 != minimap_settings.minimap_enabled()
19433 {
19434 self.set_minimap_visibility(
19435 MinimapVisibility::for_mode(self.mode(), cx),
19436 window,
19437 cx,
19438 );
19439 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19440 minimap_entity.update(cx, |minimap_editor, cx| {
19441 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19442 })
19443 }
19444 }
19445 }
19446
19447 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19448 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19449 }) {
19450 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19451 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19452 }
19453 self.refresh_colors(false, None, window, cx);
19454 }
19455
19456 cx.notify();
19457 }
19458
19459 pub fn set_searchable(&mut self, searchable: bool) {
19460 self.searchable = searchable;
19461 }
19462
19463 pub fn searchable(&self) -> bool {
19464 self.searchable
19465 }
19466
19467 fn open_proposed_changes_editor(
19468 &mut self,
19469 _: &OpenProposedChangesEditor,
19470 window: &mut Window,
19471 cx: &mut Context<Self>,
19472 ) {
19473 let Some(workspace) = self.workspace() else {
19474 cx.propagate();
19475 return;
19476 };
19477
19478 let selections = self.selections.all::<usize>(cx);
19479 let multi_buffer = self.buffer.read(cx);
19480 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19481 let mut new_selections_by_buffer = HashMap::default();
19482 for selection in selections {
19483 for (buffer, range, _) in
19484 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19485 {
19486 let mut range = range.to_point(buffer);
19487 range.start.column = 0;
19488 range.end.column = buffer.line_len(range.end.row);
19489 new_selections_by_buffer
19490 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19491 .or_insert(Vec::new())
19492 .push(range)
19493 }
19494 }
19495
19496 let proposed_changes_buffers = new_selections_by_buffer
19497 .into_iter()
19498 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19499 .collect::<Vec<_>>();
19500 let proposed_changes_editor = cx.new(|cx| {
19501 ProposedChangesEditor::new(
19502 "Proposed changes",
19503 proposed_changes_buffers,
19504 self.project.clone(),
19505 window,
19506 cx,
19507 )
19508 });
19509
19510 window.defer(cx, move |window, cx| {
19511 workspace.update(cx, |workspace, cx| {
19512 workspace.active_pane().update(cx, |pane, cx| {
19513 pane.add_item(
19514 Box::new(proposed_changes_editor),
19515 true,
19516 true,
19517 None,
19518 window,
19519 cx,
19520 );
19521 });
19522 });
19523 });
19524 }
19525
19526 pub fn open_excerpts_in_split(
19527 &mut self,
19528 _: &OpenExcerptsSplit,
19529 window: &mut Window,
19530 cx: &mut Context<Self>,
19531 ) {
19532 self.open_excerpts_common(None, true, window, cx)
19533 }
19534
19535 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19536 self.open_excerpts_common(None, false, window, cx)
19537 }
19538
19539 fn open_excerpts_common(
19540 &mut self,
19541 jump_data: Option<JumpData>,
19542 split: bool,
19543 window: &mut Window,
19544 cx: &mut Context<Self>,
19545 ) {
19546 let Some(workspace) = self.workspace() else {
19547 cx.propagate();
19548 return;
19549 };
19550
19551 if self.buffer.read(cx).is_singleton() {
19552 cx.propagate();
19553 return;
19554 }
19555
19556 let mut new_selections_by_buffer = HashMap::default();
19557 match &jump_data {
19558 Some(JumpData::MultiBufferPoint {
19559 excerpt_id,
19560 position,
19561 anchor,
19562 line_offset_from_top,
19563 }) => {
19564 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19565 if let Some(buffer) = multi_buffer_snapshot
19566 .buffer_id_for_excerpt(*excerpt_id)
19567 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19568 {
19569 let buffer_snapshot = buffer.read(cx).snapshot();
19570 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19571 language::ToPoint::to_point(anchor, &buffer_snapshot)
19572 } else {
19573 buffer_snapshot.clip_point(*position, Bias::Left)
19574 };
19575 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19576 new_selections_by_buffer.insert(
19577 buffer,
19578 (
19579 vec![jump_to_offset..jump_to_offset],
19580 Some(*line_offset_from_top),
19581 ),
19582 );
19583 }
19584 }
19585 Some(JumpData::MultiBufferRow {
19586 row,
19587 line_offset_from_top,
19588 }) => {
19589 let point = MultiBufferPoint::new(row.0, 0);
19590 if let Some((buffer, buffer_point, _)) =
19591 self.buffer.read(cx).point_to_buffer_point(point, cx)
19592 {
19593 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19594 new_selections_by_buffer
19595 .entry(buffer)
19596 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19597 .0
19598 .push(buffer_offset..buffer_offset)
19599 }
19600 }
19601 None => {
19602 let selections = self.selections.all::<usize>(cx);
19603 let multi_buffer = self.buffer.read(cx);
19604 for selection in selections {
19605 for (snapshot, range, _, anchor) in multi_buffer
19606 .snapshot(cx)
19607 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19608 {
19609 if let Some(anchor) = anchor {
19610 // selection is in a deleted hunk
19611 let Some(buffer_id) = anchor.buffer_id else {
19612 continue;
19613 };
19614 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19615 continue;
19616 };
19617 let offset = text::ToOffset::to_offset(
19618 &anchor.text_anchor,
19619 &buffer_handle.read(cx).snapshot(),
19620 );
19621 let range = offset..offset;
19622 new_selections_by_buffer
19623 .entry(buffer_handle)
19624 .or_insert((Vec::new(), None))
19625 .0
19626 .push(range)
19627 } else {
19628 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19629 else {
19630 continue;
19631 };
19632 new_selections_by_buffer
19633 .entry(buffer_handle)
19634 .or_insert((Vec::new(), None))
19635 .0
19636 .push(range)
19637 }
19638 }
19639 }
19640 }
19641 }
19642
19643 new_selections_by_buffer
19644 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19645
19646 if new_selections_by_buffer.is_empty() {
19647 return;
19648 }
19649
19650 // We defer the pane interaction because we ourselves are a workspace item
19651 // and activating a new item causes the pane to call a method on us reentrantly,
19652 // which panics if we're on the stack.
19653 window.defer(cx, move |window, cx| {
19654 workspace.update(cx, |workspace, cx| {
19655 let pane = if split {
19656 workspace.adjacent_pane(window, cx)
19657 } else {
19658 workspace.active_pane().clone()
19659 };
19660
19661 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19662 let editor = buffer
19663 .read(cx)
19664 .file()
19665 .is_none()
19666 .then(|| {
19667 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19668 // so `workspace.open_project_item` will never find them, always opening a new editor.
19669 // Instead, we try to activate the existing editor in the pane first.
19670 let (editor, pane_item_index) =
19671 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19672 let editor = item.downcast::<Editor>()?;
19673 let singleton_buffer =
19674 editor.read(cx).buffer().read(cx).as_singleton()?;
19675 if singleton_buffer == buffer {
19676 Some((editor, i))
19677 } else {
19678 None
19679 }
19680 })?;
19681 pane.update(cx, |pane, cx| {
19682 pane.activate_item(pane_item_index, true, true, window, cx)
19683 });
19684 Some(editor)
19685 })
19686 .flatten()
19687 .unwrap_or_else(|| {
19688 workspace.open_project_item::<Self>(
19689 pane.clone(),
19690 buffer,
19691 true,
19692 true,
19693 window,
19694 cx,
19695 )
19696 });
19697
19698 editor.update(cx, |editor, cx| {
19699 let autoscroll = match scroll_offset {
19700 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19701 None => Autoscroll::newest(),
19702 };
19703 let nav_history = editor.nav_history.take();
19704 editor.change_selections(Some(autoscroll), window, cx, |s| {
19705 s.select_ranges(ranges);
19706 });
19707 editor.nav_history = nav_history;
19708 });
19709 }
19710 })
19711 });
19712 }
19713
19714 // For now, don't allow opening excerpts in buffers that aren't backed by
19715 // regular project files.
19716 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19717 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19718 }
19719
19720 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19721 let snapshot = self.buffer.read(cx).read(cx);
19722 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19723 Some(
19724 ranges
19725 .iter()
19726 .map(move |range| {
19727 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19728 })
19729 .collect(),
19730 )
19731 }
19732
19733 fn selection_replacement_ranges(
19734 &self,
19735 range: Range<OffsetUtf16>,
19736 cx: &mut App,
19737 ) -> Vec<Range<OffsetUtf16>> {
19738 let selections = self.selections.all::<OffsetUtf16>(cx);
19739 let newest_selection = selections
19740 .iter()
19741 .max_by_key(|selection| selection.id)
19742 .unwrap();
19743 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19744 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19745 let snapshot = self.buffer.read(cx).read(cx);
19746 selections
19747 .into_iter()
19748 .map(|mut selection| {
19749 selection.start.0 =
19750 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19751 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19752 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19753 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19754 })
19755 .collect()
19756 }
19757
19758 fn report_editor_event(
19759 &self,
19760 event_type: &'static str,
19761 file_extension: Option<String>,
19762 cx: &App,
19763 ) {
19764 if cfg!(any(test, feature = "test-support")) {
19765 return;
19766 }
19767
19768 let Some(project) = &self.project else { return };
19769
19770 // If None, we are in a file without an extension
19771 let file = self
19772 .buffer
19773 .read(cx)
19774 .as_singleton()
19775 .and_then(|b| b.read(cx).file());
19776 let file_extension = file_extension.or(file
19777 .as_ref()
19778 .and_then(|file| Path::new(file.file_name(cx)).extension())
19779 .and_then(|e| e.to_str())
19780 .map(|a| a.to_string()));
19781
19782 let vim_mode = vim_enabled(cx);
19783
19784 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19785 let copilot_enabled = edit_predictions_provider
19786 == language::language_settings::EditPredictionProvider::Copilot;
19787 let copilot_enabled_for_language = self
19788 .buffer
19789 .read(cx)
19790 .language_settings(cx)
19791 .show_edit_predictions;
19792
19793 let project = project.read(cx);
19794 telemetry::event!(
19795 event_type,
19796 file_extension,
19797 vim_mode,
19798 copilot_enabled,
19799 copilot_enabled_for_language,
19800 edit_predictions_provider,
19801 is_via_ssh = project.is_via_ssh(),
19802 );
19803 }
19804
19805 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19806 /// with each line being an array of {text, highlight} objects.
19807 fn copy_highlight_json(
19808 &mut self,
19809 _: &CopyHighlightJson,
19810 window: &mut Window,
19811 cx: &mut Context<Self>,
19812 ) {
19813 #[derive(Serialize)]
19814 struct Chunk<'a> {
19815 text: String,
19816 highlight: Option<&'a str>,
19817 }
19818
19819 let snapshot = self.buffer.read(cx).snapshot(cx);
19820 let range = self
19821 .selected_text_range(false, window, cx)
19822 .and_then(|selection| {
19823 if selection.range.is_empty() {
19824 None
19825 } else {
19826 Some(selection.range)
19827 }
19828 })
19829 .unwrap_or_else(|| 0..snapshot.len());
19830
19831 let chunks = snapshot.chunks(range, true);
19832 let mut lines = Vec::new();
19833 let mut line: VecDeque<Chunk> = VecDeque::new();
19834
19835 let Some(style) = self.style.as_ref() else {
19836 return;
19837 };
19838
19839 for chunk in chunks {
19840 let highlight = chunk
19841 .syntax_highlight_id
19842 .and_then(|id| id.name(&style.syntax));
19843 let mut chunk_lines = chunk.text.split('\n').peekable();
19844 while let Some(text) = chunk_lines.next() {
19845 let mut merged_with_last_token = false;
19846 if let Some(last_token) = line.back_mut() {
19847 if last_token.highlight == highlight {
19848 last_token.text.push_str(text);
19849 merged_with_last_token = true;
19850 }
19851 }
19852
19853 if !merged_with_last_token {
19854 line.push_back(Chunk {
19855 text: text.into(),
19856 highlight,
19857 });
19858 }
19859
19860 if chunk_lines.peek().is_some() {
19861 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19862 line.pop_front();
19863 }
19864 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19865 line.pop_back();
19866 }
19867
19868 lines.push(mem::take(&mut line));
19869 }
19870 }
19871 }
19872
19873 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19874 return;
19875 };
19876 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19877 }
19878
19879 pub fn open_context_menu(
19880 &mut self,
19881 _: &OpenContextMenu,
19882 window: &mut Window,
19883 cx: &mut Context<Self>,
19884 ) {
19885 self.request_autoscroll(Autoscroll::newest(), cx);
19886 let position = self.selections.newest_display(cx).start;
19887 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19888 }
19889
19890 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19891 &self.inlay_hint_cache
19892 }
19893
19894 pub fn replay_insert_event(
19895 &mut self,
19896 text: &str,
19897 relative_utf16_range: Option<Range<isize>>,
19898 window: &mut Window,
19899 cx: &mut Context<Self>,
19900 ) {
19901 if !self.input_enabled {
19902 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19903 return;
19904 }
19905 if let Some(relative_utf16_range) = relative_utf16_range {
19906 let selections = self.selections.all::<OffsetUtf16>(cx);
19907 self.change_selections(None, window, cx, |s| {
19908 let new_ranges = selections.into_iter().map(|range| {
19909 let start = OffsetUtf16(
19910 range
19911 .head()
19912 .0
19913 .saturating_add_signed(relative_utf16_range.start),
19914 );
19915 let end = OffsetUtf16(
19916 range
19917 .head()
19918 .0
19919 .saturating_add_signed(relative_utf16_range.end),
19920 );
19921 start..end
19922 });
19923 s.select_ranges(new_ranges);
19924 });
19925 }
19926
19927 self.handle_input(text, window, cx);
19928 }
19929
19930 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19931 let Some(provider) = self.semantics_provider.as_ref() else {
19932 return false;
19933 };
19934
19935 let mut supports = false;
19936 self.buffer().update(cx, |this, cx| {
19937 this.for_each_buffer(|buffer| {
19938 supports |= provider.supports_inlay_hints(buffer, cx);
19939 });
19940 });
19941
19942 supports
19943 }
19944
19945 pub fn is_focused(&self, window: &Window) -> bool {
19946 self.focus_handle.is_focused(window)
19947 }
19948
19949 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19950 cx.emit(EditorEvent::Focused);
19951
19952 if let Some(descendant) = self
19953 .last_focused_descendant
19954 .take()
19955 .and_then(|descendant| descendant.upgrade())
19956 {
19957 window.focus(&descendant);
19958 } else {
19959 if let Some(blame) = self.blame.as_ref() {
19960 blame.update(cx, GitBlame::focus)
19961 }
19962
19963 self.blink_manager.update(cx, BlinkManager::enable);
19964 self.show_cursor_names(window, cx);
19965 self.buffer.update(cx, |buffer, cx| {
19966 buffer.finalize_last_transaction(cx);
19967 if self.leader_id.is_none() {
19968 buffer.set_active_selections(
19969 &self.selections.disjoint_anchors(),
19970 self.selections.line_mode,
19971 self.cursor_shape,
19972 cx,
19973 );
19974 }
19975 });
19976 }
19977 }
19978
19979 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19980 cx.emit(EditorEvent::FocusedIn)
19981 }
19982
19983 fn handle_focus_out(
19984 &mut self,
19985 event: FocusOutEvent,
19986 _window: &mut Window,
19987 cx: &mut Context<Self>,
19988 ) {
19989 if event.blurred != self.focus_handle {
19990 self.last_focused_descendant = Some(event.blurred);
19991 }
19992 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19993 }
19994
19995 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19996 self.blink_manager.update(cx, BlinkManager::disable);
19997 self.buffer
19998 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19999
20000 if let Some(blame) = self.blame.as_ref() {
20001 blame.update(cx, GitBlame::blur)
20002 }
20003 if !self.hover_state.focused(window, cx) {
20004 hide_hover(self, cx);
20005 }
20006 if !self
20007 .context_menu
20008 .borrow()
20009 .as_ref()
20010 .is_some_and(|context_menu| context_menu.focused(window, cx))
20011 {
20012 self.hide_context_menu(window, cx);
20013 }
20014 self.discard_inline_completion(false, cx);
20015 cx.emit(EditorEvent::Blurred);
20016 cx.notify();
20017 }
20018
20019 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20020 let mut pending: String = window
20021 .pending_input_keystrokes()
20022 .into_iter()
20023 .flatten()
20024 .filter_map(|keystroke| {
20025 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20026 keystroke.key_char.clone()
20027 } else {
20028 None
20029 }
20030 })
20031 .collect();
20032
20033 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20034 pending = "".to_string();
20035 }
20036
20037 let existing_pending = self
20038 .text_highlights::<PendingInput>(cx)
20039 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20040 if existing_pending.is_none() && pending.is_empty() {
20041 return;
20042 }
20043 let transaction =
20044 self.transact(window, cx, |this, window, cx| {
20045 let selections = this.selections.all::<usize>(cx);
20046 let edits = selections
20047 .iter()
20048 .map(|selection| (selection.end..selection.end, pending.clone()));
20049 this.edit(edits, cx);
20050 this.change_selections(None, window, cx, |s| {
20051 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20052 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20053 }));
20054 });
20055 if let Some(existing_ranges) = existing_pending {
20056 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20057 this.edit(edits, cx);
20058 }
20059 });
20060
20061 let snapshot = self.snapshot(window, cx);
20062 let ranges = self
20063 .selections
20064 .all::<usize>(cx)
20065 .into_iter()
20066 .map(|selection| {
20067 snapshot.buffer_snapshot.anchor_after(selection.end)
20068 ..snapshot
20069 .buffer_snapshot
20070 .anchor_before(selection.end + pending.len())
20071 })
20072 .collect();
20073
20074 if pending.is_empty() {
20075 self.clear_highlights::<PendingInput>(cx);
20076 } else {
20077 self.highlight_text::<PendingInput>(
20078 ranges,
20079 HighlightStyle {
20080 underline: Some(UnderlineStyle {
20081 thickness: px(1.),
20082 color: None,
20083 wavy: false,
20084 }),
20085 ..Default::default()
20086 },
20087 cx,
20088 );
20089 }
20090
20091 self.ime_transaction = self.ime_transaction.or(transaction);
20092 if let Some(transaction) = self.ime_transaction {
20093 self.buffer.update(cx, |buffer, cx| {
20094 buffer.group_until_transaction(transaction, cx);
20095 });
20096 }
20097
20098 if self.text_highlights::<PendingInput>(cx).is_none() {
20099 self.ime_transaction.take();
20100 }
20101 }
20102
20103 pub fn register_action_renderer(
20104 &mut self,
20105 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20106 ) -> Subscription {
20107 let id = self.next_editor_action_id.post_inc();
20108 self.editor_actions
20109 .borrow_mut()
20110 .insert(id, Box::new(listener));
20111
20112 let editor_actions = self.editor_actions.clone();
20113 Subscription::new(move || {
20114 editor_actions.borrow_mut().remove(&id);
20115 })
20116 }
20117
20118 pub fn register_action<A: Action>(
20119 &mut self,
20120 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20121 ) -> Subscription {
20122 let id = self.next_editor_action_id.post_inc();
20123 let listener = Arc::new(listener);
20124 self.editor_actions.borrow_mut().insert(
20125 id,
20126 Box::new(move |_, window, _| {
20127 let listener = listener.clone();
20128 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20129 let action = action.downcast_ref().unwrap();
20130 if phase == DispatchPhase::Bubble {
20131 listener(action, window, cx)
20132 }
20133 })
20134 }),
20135 );
20136
20137 let editor_actions = self.editor_actions.clone();
20138 Subscription::new(move || {
20139 editor_actions.borrow_mut().remove(&id);
20140 })
20141 }
20142
20143 pub fn file_header_size(&self) -> u32 {
20144 FILE_HEADER_HEIGHT
20145 }
20146
20147 pub fn restore(
20148 &mut self,
20149 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20150 window: &mut Window,
20151 cx: &mut Context<Self>,
20152 ) {
20153 let workspace = self.workspace();
20154 let project = self.project.as_ref();
20155 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20156 let mut tasks = Vec::new();
20157 for (buffer_id, changes) in revert_changes {
20158 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20159 buffer.update(cx, |buffer, cx| {
20160 buffer.edit(
20161 changes
20162 .into_iter()
20163 .map(|(range, text)| (range, text.to_string())),
20164 None,
20165 cx,
20166 );
20167 });
20168
20169 if let Some(project) =
20170 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20171 {
20172 project.update(cx, |project, cx| {
20173 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20174 })
20175 }
20176 }
20177 }
20178 tasks
20179 });
20180 cx.spawn_in(window, async move |_, cx| {
20181 for (buffer, task) in save_tasks {
20182 let result = task.await;
20183 if result.is_err() {
20184 let Some(path) = buffer
20185 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20186 .ok()
20187 else {
20188 continue;
20189 };
20190 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20191 let Some(task) = cx
20192 .update_window_entity(&workspace, |workspace, window, cx| {
20193 workspace
20194 .open_path_preview(path, None, false, false, false, window, cx)
20195 })
20196 .ok()
20197 else {
20198 continue;
20199 };
20200 task.await.log_err();
20201 }
20202 }
20203 }
20204 })
20205 .detach();
20206 self.change_selections(None, window, cx, |selections| selections.refresh());
20207 }
20208
20209 pub fn to_pixel_point(
20210 &self,
20211 source: multi_buffer::Anchor,
20212 editor_snapshot: &EditorSnapshot,
20213 window: &mut Window,
20214 ) -> Option<gpui::Point<Pixels>> {
20215 let source_point = source.to_display_point(editor_snapshot);
20216 self.display_to_pixel_point(source_point, editor_snapshot, window)
20217 }
20218
20219 pub fn display_to_pixel_point(
20220 &self,
20221 source: DisplayPoint,
20222 editor_snapshot: &EditorSnapshot,
20223 window: &mut Window,
20224 ) -> Option<gpui::Point<Pixels>> {
20225 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20226 let text_layout_details = self.text_layout_details(window);
20227 let scroll_top = text_layout_details
20228 .scroll_anchor
20229 .scroll_position(editor_snapshot)
20230 .y;
20231
20232 if source.row().as_f32() < scroll_top.floor() {
20233 return None;
20234 }
20235 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20236 let source_y = line_height * (source.row().as_f32() - scroll_top);
20237 Some(gpui::Point::new(source_x, source_y))
20238 }
20239
20240 pub fn has_visible_completions_menu(&self) -> bool {
20241 !self.edit_prediction_preview_is_active()
20242 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20243 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20244 })
20245 }
20246
20247 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20248 if self.mode.is_minimap() {
20249 return;
20250 }
20251 self.addons
20252 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20253 }
20254
20255 pub fn unregister_addon<T: Addon>(&mut self) {
20256 self.addons.remove(&std::any::TypeId::of::<T>());
20257 }
20258
20259 pub fn addon<T: Addon>(&self) -> Option<&T> {
20260 let type_id = std::any::TypeId::of::<T>();
20261 self.addons
20262 .get(&type_id)
20263 .and_then(|item| item.to_any().downcast_ref::<T>())
20264 }
20265
20266 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20267 let type_id = std::any::TypeId::of::<T>();
20268 self.addons
20269 .get_mut(&type_id)
20270 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20271 }
20272
20273 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20274 let text_layout_details = self.text_layout_details(window);
20275 let style = &text_layout_details.editor_style;
20276 let font_id = window.text_system().resolve_font(&style.text.font());
20277 let font_size = style.text.font_size.to_pixels(window.rem_size());
20278 let line_height = style.text.line_height_in_pixels(window.rem_size());
20279 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20280
20281 gpui::Size::new(em_width, line_height)
20282 }
20283
20284 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20285 self.load_diff_task.clone()
20286 }
20287
20288 fn read_metadata_from_db(
20289 &mut self,
20290 item_id: u64,
20291 workspace_id: WorkspaceId,
20292 window: &mut Window,
20293 cx: &mut Context<Editor>,
20294 ) {
20295 if self.is_singleton(cx)
20296 && !self.mode.is_minimap()
20297 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20298 {
20299 let buffer_snapshot = OnceCell::new();
20300
20301 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20302 if !folds.is_empty() {
20303 let snapshot =
20304 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20305 self.fold_ranges(
20306 folds
20307 .into_iter()
20308 .map(|(start, end)| {
20309 snapshot.clip_offset(start, Bias::Left)
20310 ..snapshot.clip_offset(end, Bias::Right)
20311 })
20312 .collect(),
20313 false,
20314 window,
20315 cx,
20316 );
20317 }
20318 }
20319
20320 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20321 if !selections.is_empty() {
20322 let snapshot =
20323 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20324 // skip adding the initial selection to selection history
20325 self.selection_history.mode = SelectionHistoryMode::Skipping;
20326 self.change_selections(None, window, cx, |s| {
20327 s.select_ranges(selections.into_iter().map(|(start, end)| {
20328 snapshot.clip_offset(start, Bias::Left)
20329 ..snapshot.clip_offset(end, Bias::Right)
20330 }));
20331 });
20332 self.selection_history.mode = SelectionHistoryMode::Normal;
20333 }
20334 };
20335 }
20336
20337 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20338 }
20339
20340 fn update_lsp_data(
20341 &mut self,
20342 ignore_cache: bool,
20343 for_buffer: Option<BufferId>,
20344 window: &mut Window,
20345 cx: &mut Context<'_, Self>,
20346 ) {
20347 self.pull_diagnostics(for_buffer, window, cx);
20348 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20349 }
20350}
20351
20352fn vim_enabled(cx: &App) -> bool {
20353 cx.global::<SettingsStore>()
20354 .raw_user_settings()
20355 .get("vim_mode")
20356 == Some(&serde_json::Value::Bool(true))
20357}
20358
20359fn process_completion_for_edit(
20360 completion: &Completion,
20361 intent: CompletionIntent,
20362 buffer: &Entity<Buffer>,
20363 cursor_position: &text::Anchor,
20364 cx: &mut Context<Editor>,
20365) -> CompletionEdit {
20366 let buffer = buffer.read(cx);
20367 let buffer_snapshot = buffer.snapshot();
20368 let (snippet, new_text) = if completion.is_snippet() {
20369 // Workaround for typescript language server issues so that methods don't expand within
20370 // strings and functions with type expressions. The previous point is used because the query
20371 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20372 let mut snippet_source = completion.new_text.clone();
20373 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20374 previous_point.column = previous_point.column.saturating_sub(1);
20375 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20376 if scope.prefers_label_for_snippet_in_completion() {
20377 if let Some(label) = completion.label() {
20378 if matches!(
20379 completion.kind(),
20380 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20381 ) {
20382 snippet_source = label;
20383 }
20384 }
20385 }
20386 }
20387 match Snippet::parse(&snippet_source).log_err() {
20388 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20389 None => (None, completion.new_text.clone()),
20390 }
20391 } else {
20392 (None, completion.new_text.clone())
20393 };
20394
20395 let mut range_to_replace = {
20396 let replace_range = &completion.replace_range;
20397 if let CompletionSource::Lsp {
20398 insert_range: Some(insert_range),
20399 ..
20400 } = &completion.source
20401 {
20402 debug_assert_eq!(
20403 insert_range.start, replace_range.start,
20404 "insert_range and replace_range should start at the same position"
20405 );
20406 debug_assert!(
20407 insert_range
20408 .start
20409 .cmp(&cursor_position, &buffer_snapshot)
20410 .is_le(),
20411 "insert_range should start before or at cursor position"
20412 );
20413 debug_assert!(
20414 replace_range
20415 .start
20416 .cmp(&cursor_position, &buffer_snapshot)
20417 .is_le(),
20418 "replace_range should start before or at cursor position"
20419 );
20420 debug_assert!(
20421 insert_range
20422 .end
20423 .cmp(&cursor_position, &buffer_snapshot)
20424 .is_le(),
20425 "insert_range should end before or at cursor position"
20426 );
20427
20428 let should_replace = match intent {
20429 CompletionIntent::CompleteWithInsert => false,
20430 CompletionIntent::CompleteWithReplace => true,
20431 CompletionIntent::Complete | CompletionIntent::Compose => {
20432 let insert_mode =
20433 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20434 .completions
20435 .lsp_insert_mode;
20436 match insert_mode {
20437 LspInsertMode::Insert => false,
20438 LspInsertMode::Replace => true,
20439 LspInsertMode::ReplaceSubsequence => {
20440 let mut text_to_replace = buffer.chars_for_range(
20441 buffer.anchor_before(replace_range.start)
20442 ..buffer.anchor_after(replace_range.end),
20443 );
20444 let mut current_needle = text_to_replace.next();
20445 for haystack_ch in completion.label.text.chars() {
20446 if let Some(needle_ch) = current_needle {
20447 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20448 current_needle = text_to_replace.next();
20449 }
20450 }
20451 }
20452 current_needle.is_none()
20453 }
20454 LspInsertMode::ReplaceSuffix => {
20455 if replace_range
20456 .end
20457 .cmp(&cursor_position, &buffer_snapshot)
20458 .is_gt()
20459 {
20460 let range_after_cursor = *cursor_position..replace_range.end;
20461 let text_after_cursor = buffer
20462 .text_for_range(
20463 buffer.anchor_before(range_after_cursor.start)
20464 ..buffer.anchor_after(range_after_cursor.end),
20465 )
20466 .collect::<String>()
20467 .to_ascii_lowercase();
20468 completion
20469 .label
20470 .text
20471 .to_ascii_lowercase()
20472 .ends_with(&text_after_cursor)
20473 } else {
20474 true
20475 }
20476 }
20477 }
20478 }
20479 };
20480
20481 if should_replace {
20482 replace_range.clone()
20483 } else {
20484 insert_range.clone()
20485 }
20486 } else {
20487 replace_range.clone()
20488 }
20489 };
20490
20491 if range_to_replace
20492 .end
20493 .cmp(&cursor_position, &buffer_snapshot)
20494 .is_lt()
20495 {
20496 range_to_replace.end = *cursor_position;
20497 }
20498
20499 CompletionEdit {
20500 new_text,
20501 replace_range: range_to_replace.to_offset(&buffer),
20502 snippet,
20503 }
20504}
20505
20506struct CompletionEdit {
20507 new_text: String,
20508 replace_range: Range<usize>,
20509 snippet: Option<Snippet>,
20510}
20511
20512fn insert_extra_newline_brackets(
20513 buffer: &MultiBufferSnapshot,
20514 range: Range<usize>,
20515 language: &language::LanguageScope,
20516) -> bool {
20517 let leading_whitespace_len = buffer
20518 .reversed_chars_at(range.start)
20519 .take_while(|c| c.is_whitespace() && *c != '\n')
20520 .map(|c| c.len_utf8())
20521 .sum::<usize>();
20522 let trailing_whitespace_len = buffer
20523 .chars_at(range.end)
20524 .take_while(|c| c.is_whitespace() && *c != '\n')
20525 .map(|c| c.len_utf8())
20526 .sum::<usize>();
20527 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20528
20529 language.brackets().any(|(pair, enabled)| {
20530 let pair_start = pair.start.trim_end();
20531 let pair_end = pair.end.trim_start();
20532
20533 enabled
20534 && pair.newline
20535 && buffer.contains_str_at(range.end, pair_end)
20536 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20537 })
20538}
20539
20540fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20541 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20542 [(buffer, range, _)] => (*buffer, range.clone()),
20543 _ => return false,
20544 };
20545 let pair = {
20546 let mut result: Option<BracketMatch> = None;
20547
20548 for pair in buffer
20549 .all_bracket_ranges(range.clone())
20550 .filter(move |pair| {
20551 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20552 })
20553 {
20554 let len = pair.close_range.end - pair.open_range.start;
20555
20556 if let Some(existing) = &result {
20557 let existing_len = existing.close_range.end - existing.open_range.start;
20558 if len > existing_len {
20559 continue;
20560 }
20561 }
20562
20563 result = Some(pair);
20564 }
20565
20566 result
20567 };
20568 let Some(pair) = pair else {
20569 return false;
20570 };
20571 pair.newline_only
20572 && buffer
20573 .chars_for_range(pair.open_range.end..range.start)
20574 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20575 .all(|c| c.is_whitespace() && c != '\n')
20576}
20577
20578fn update_uncommitted_diff_for_buffer(
20579 editor: Entity<Editor>,
20580 project: &Entity<Project>,
20581 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20582 buffer: Entity<MultiBuffer>,
20583 cx: &mut App,
20584) -> Task<()> {
20585 let mut tasks = Vec::new();
20586 project.update(cx, |project, cx| {
20587 for buffer in buffers {
20588 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20589 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20590 }
20591 }
20592 });
20593 cx.spawn(async move |cx| {
20594 let diffs = future::join_all(tasks).await;
20595 if editor
20596 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20597 .unwrap_or(false)
20598 {
20599 return;
20600 }
20601
20602 buffer
20603 .update(cx, |buffer, cx| {
20604 for diff in diffs.into_iter().flatten() {
20605 buffer.add_diff(diff, cx);
20606 }
20607 })
20608 .ok();
20609 })
20610}
20611
20612fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20613 let tab_size = tab_size.get() as usize;
20614 let mut width = offset;
20615
20616 for ch in text.chars() {
20617 width += if ch == '\t' {
20618 tab_size - (width % tab_size)
20619 } else {
20620 1
20621 };
20622 }
20623
20624 width - offset
20625}
20626
20627#[cfg(test)]
20628mod tests {
20629 use super::*;
20630
20631 #[test]
20632 fn test_string_size_with_expanded_tabs() {
20633 let nz = |val| NonZeroU32::new(val).unwrap();
20634 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20635 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20636 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20637 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20638 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20639 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20640 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20641 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20642 }
20643}
20644
20645/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20646struct WordBreakingTokenizer<'a> {
20647 input: &'a str,
20648}
20649
20650impl<'a> WordBreakingTokenizer<'a> {
20651 fn new(input: &'a str) -> Self {
20652 Self { input }
20653 }
20654}
20655
20656fn is_char_ideographic(ch: char) -> bool {
20657 use unicode_script::Script::*;
20658 use unicode_script::UnicodeScript;
20659 matches!(ch.script(), Han | Tangut | Yi)
20660}
20661
20662fn is_grapheme_ideographic(text: &str) -> bool {
20663 text.chars().any(is_char_ideographic)
20664}
20665
20666fn is_grapheme_whitespace(text: &str) -> bool {
20667 text.chars().any(|x| x.is_whitespace())
20668}
20669
20670fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20671 text.chars().next().map_or(false, |ch| {
20672 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20673 })
20674}
20675
20676#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20677enum WordBreakToken<'a> {
20678 Word { token: &'a str, grapheme_len: usize },
20679 InlineWhitespace { token: &'a str, grapheme_len: usize },
20680 Newline,
20681}
20682
20683impl<'a> Iterator for WordBreakingTokenizer<'a> {
20684 /// Yields a span, the count of graphemes in the token, and whether it was
20685 /// whitespace. Note that it also breaks at word boundaries.
20686 type Item = WordBreakToken<'a>;
20687
20688 fn next(&mut self) -> Option<Self::Item> {
20689 use unicode_segmentation::UnicodeSegmentation;
20690 if self.input.is_empty() {
20691 return None;
20692 }
20693
20694 let mut iter = self.input.graphemes(true).peekable();
20695 let mut offset = 0;
20696 let mut grapheme_len = 0;
20697 if let Some(first_grapheme) = iter.next() {
20698 let is_newline = first_grapheme == "\n";
20699 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20700 offset += first_grapheme.len();
20701 grapheme_len += 1;
20702 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20703 if let Some(grapheme) = iter.peek().copied() {
20704 if should_stay_with_preceding_ideograph(grapheme) {
20705 offset += grapheme.len();
20706 grapheme_len += 1;
20707 }
20708 }
20709 } else {
20710 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20711 let mut next_word_bound = words.peek().copied();
20712 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20713 next_word_bound = words.next();
20714 }
20715 while let Some(grapheme) = iter.peek().copied() {
20716 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20717 break;
20718 };
20719 if is_grapheme_whitespace(grapheme) != is_whitespace
20720 || (grapheme == "\n") != is_newline
20721 {
20722 break;
20723 };
20724 offset += grapheme.len();
20725 grapheme_len += 1;
20726 iter.next();
20727 }
20728 }
20729 let token = &self.input[..offset];
20730 self.input = &self.input[offset..];
20731 if token == "\n" {
20732 Some(WordBreakToken::Newline)
20733 } else if is_whitespace {
20734 Some(WordBreakToken::InlineWhitespace {
20735 token,
20736 grapheme_len,
20737 })
20738 } else {
20739 Some(WordBreakToken::Word {
20740 token,
20741 grapheme_len,
20742 })
20743 }
20744 } else {
20745 None
20746 }
20747 }
20748}
20749
20750#[test]
20751fn test_word_breaking_tokenizer() {
20752 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20753 ("", &[]),
20754 (" ", &[whitespace(" ", 2)]),
20755 ("Ʒ", &[word("Ʒ", 1)]),
20756 ("Ǽ", &[word("Ǽ", 1)]),
20757 ("⋑", &[word("⋑", 1)]),
20758 ("⋑⋑", &[word("⋑⋑", 2)]),
20759 (
20760 "原理,进而",
20761 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20762 ),
20763 (
20764 "hello world",
20765 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20766 ),
20767 (
20768 "hello, world",
20769 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20770 ),
20771 (
20772 " hello world",
20773 &[
20774 whitespace(" ", 2),
20775 word("hello", 5),
20776 whitespace(" ", 1),
20777 word("world", 5),
20778 ],
20779 ),
20780 (
20781 "这是什么 \n 钢笔",
20782 &[
20783 word("这", 1),
20784 word("是", 1),
20785 word("什", 1),
20786 word("么", 1),
20787 whitespace(" ", 1),
20788 newline(),
20789 whitespace(" ", 1),
20790 word("钢", 1),
20791 word("笔", 1),
20792 ],
20793 ),
20794 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20795 ];
20796
20797 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20798 WordBreakToken::Word {
20799 token,
20800 grapheme_len,
20801 }
20802 }
20803
20804 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20805 WordBreakToken::InlineWhitespace {
20806 token,
20807 grapheme_len,
20808 }
20809 }
20810
20811 fn newline() -> WordBreakToken<'static> {
20812 WordBreakToken::Newline
20813 }
20814
20815 for (input, result) in tests {
20816 assert_eq!(
20817 WordBreakingTokenizer::new(input)
20818 .collect::<Vec<_>>()
20819 .as_slice(),
20820 *result,
20821 );
20822 }
20823}
20824
20825fn wrap_with_prefix(
20826 line_prefix: String,
20827 unwrapped_text: String,
20828 wrap_column: usize,
20829 tab_size: NonZeroU32,
20830 preserve_existing_whitespace: bool,
20831) -> String {
20832 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20833 let mut wrapped_text = String::new();
20834 let mut current_line = line_prefix.clone();
20835
20836 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20837 let mut current_line_len = line_prefix_len;
20838 let mut in_whitespace = false;
20839 for token in tokenizer {
20840 let have_preceding_whitespace = in_whitespace;
20841 match token {
20842 WordBreakToken::Word {
20843 token,
20844 grapheme_len,
20845 } => {
20846 in_whitespace = false;
20847 if current_line_len + grapheme_len > wrap_column
20848 && current_line_len != line_prefix_len
20849 {
20850 wrapped_text.push_str(current_line.trim_end());
20851 wrapped_text.push('\n');
20852 current_line.truncate(line_prefix.len());
20853 current_line_len = line_prefix_len;
20854 }
20855 current_line.push_str(token);
20856 current_line_len += grapheme_len;
20857 }
20858 WordBreakToken::InlineWhitespace {
20859 mut token,
20860 mut grapheme_len,
20861 } => {
20862 in_whitespace = true;
20863 if have_preceding_whitespace && !preserve_existing_whitespace {
20864 continue;
20865 }
20866 if !preserve_existing_whitespace {
20867 token = " ";
20868 grapheme_len = 1;
20869 }
20870 if current_line_len + grapheme_len > wrap_column {
20871 wrapped_text.push_str(current_line.trim_end());
20872 wrapped_text.push('\n');
20873 current_line.truncate(line_prefix.len());
20874 current_line_len = line_prefix_len;
20875 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20876 current_line.push_str(token);
20877 current_line_len += grapheme_len;
20878 }
20879 }
20880 WordBreakToken::Newline => {
20881 in_whitespace = true;
20882 if preserve_existing_whitespace {
20883 wrapped_text.push_str(current_line.trim_end());
20884 wrapped_text.push('\n');
20885 current_line.truncate(line_prefix.len());
20886 current_line_len = line_prefix_len;
20887 } else if have_preceding_whitespace {
20888 continue;
20889 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20890 {
20891 wrapped_text.push_str(current_line.trim_end());
20892 wrapped_text.push('\n');
20893 current_line.truncate(line_prefix.len());
20894 current_line_len = line_prefix_len;
20895 } else if current_line_len != line_prefix_len {
20896 current_line.push(' ');
20897 current_line_len += 1;
20898 }
20899 }
20900 }
20901 }
20902
20903 if !current_line.is_empty() {
20904 wrapped_text.push_str(¤t_line);
20905 }
20906 wrapped_text
20907}
20908
20909#[test]
20910fn test_wrap_with_prefix() {
20911 assert_eq!(
20912 wrap_with_prefix(
20913 "# ".to_string(),
20914 "abcdefg".to_string(),
20915 4,
20916 NonZeroU32::new(4).unwrap(),
20917 false,
20918 ),
20919 "# abcdefg"
20920 );
20921 assert_eq!(
20922 wrap_with_prefix(
20923 "".to_string(),
20924 "\thello world".to_string(),
20925 8,
20926 NonZeroU32::new(4).unwrap(),
20927 false,
20928 ),
20929 "hello\nworld"
20930 );
20931 assert_eq!(
20932 wrap_with_prefix(
20933 "// ".to_string(),
20934 "xx \nyy zz aa bb cc".to_string(),
20935 12,
20936 NonZeroU32::new(4).unwrap(),
20937 false,
20938 ),
20939 "// xx yy zz\n// aa bb cc"
20940 );
20941 assert_eq!(
20942 wrap_with_prefix(
20943 String::new(),
20944 "这是什么 \n 钢笔".to_string(),
20945 3,
20946 NonZeroU32::new(4).unwrap(),
20947 false,
20948 ),
20949 "这是什\n么 钢\n笔"
20950 );
20951}
20952
20953pub trait CollaborationHub {
20954 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20955 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20956 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20957}
20958
20959impl CollaborationHub for Entity<Project> {
20960 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20961 self.read(cx).collaborators()
20962 }
20963
20964 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20965 self.read(cx).user_store().read(cx).participant_indices()
20966 }
20967
20968 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20969 let this = self.read(cx);
20970 let user_ids = this.collaborators().values().map(|c| c.user_id);
20971 this.user_store().read(cx).participant_names(user_ids, cx)
20972 }
20973}
20974
20975pub trait SemanticsProvider {
20976 fn hover(
20977 &self,
20978 buffer: &Entity<Buffer>,
20979 position: text::Anchor,
20980 cx: &mut App,
20981 ) -> Option<Task<Vec<project::Hover>>>;
20982
20983 fn inline_values(
20984 &self,
20985 buffer_handle: Entity<Buffer>,
20986 range: Range<text::Anchor>,
20987 cx: &mut App,
20988 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20989
20990 fn inlay_hints(
20991 &self,
20992 buffer_handle: Entity<Buffer>,
20993 range: Range<text::Anchor>,
20994 cx: &mut App,
20995 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20996
20997 fn resolve_inlay_hint(
20998 &self,
20999 hint: InlayHint,
21000 buffer_handle: Entity<Buffer>,
21001 server_id: LanguageServerId,
21002 cx: &mut App,
21003 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21004
21005 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21006
21007 fn document_highlights(
21008 &self,
21009 buffer: &Entity<Buffer>,
21010 position: text::Anchor,
21011 cx: &mut App,
21012 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21013
21014 fn definitions(
21015 &self,
21016 buffer: &Entity<Buffer>,
21017 position: text::Anchor,
21018 kind: GotoDefinitionKind,
21019 cx: &mut App,
21020 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21021
21022 fn range_for_rename(
21023 &self,
21024 buffer: &Entity<Buffer>,
21025 position: text::Anchor,
21026 cx: &mut App,
21027 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21028
21029 fn perform_rename(
21030 &self,
21031 buffer: &Entity<Buffer>,
21032 position: text::Anchor,
21033 new_name: String,
21034 cx: &mut App,
21035 ) -> Option<Task<Result<ProjectTransaction>>>;
21036}
21037
21038pub trait CompletionProvider {
21039 fn completions(
21040 &self,
21041 excerpt_id: ExcerptId,
21042 buffer: &Entity<Buffer>,
21043 buffer_position: text::Anchor,
21044 trigger: CompletionContext,
21045 window: &mut Window,
21046 cx: &mut Context<Editor>,
21047 ) -> Task<Result<Vec<CompletionResponse>>>;
21048
21049 fn resolve_completions(
21050 &self,
21051 _buffer: Entity<Buffer>,
21052 _completion_indices: Vec<usize>,
21053 _completions: Rc<RefCell<Box<[Completion]>>>,
21054 _cx: &mut Context<Editor>,
21055 ) -> Task<Result<bool>> {
21056 Task::ready(Ok(false))
21057 }
21058
21059 fn apply_additional_edits_for_completion(
21060 &self,
21061 _buffer: Entity<Buffer>,
21062 _completions: Rc<RefCell<Box<[Completion]>>>,
21063 _completion_index: usize,
21064 _push_to_history: bool,
21065 _cx: &mut Context<Editor>,
21066 ) -> Task<Result<Option<language::Transaction>>> {
21067 Task::ready(Ok(None))
21068 }
21069
21070 fn is_completion_trigger(
21071 &self,
21072 buffer: &Entity<Buffer>,
21073 position: language::Anchor,
21074 text: &str,
21075 trigger_in_words: bool,
21076 menu_is_open: bool,
21077 cx: &mut Context<Editor>,
21078 ) -> bool;
21079
21080 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21081
21082 fn sort_completions(&self) -> bool {
21083 true
21084 }
21085
21086 fn filter_completions(&self) -> bool {
21087 true
21088 }
21089}
21090
21091pub trait CodeActionProvider {
21092 fn id(&self) -> Arc<str>;
21093
21094 fn code_actions(
21095 &self,
21096 buffer: &Entity<Buffer>,
21097 range: Range<text::Anchor>,
21098 window: &mut Window,
21099 cx: &mut App,
21100 ) -> Task<Result<Vec<CodeAction>>>;
21101
21102 fn apply_code_action(
21103 &self,
21104 buffer_handle: Entity<Buffer>,
21105 action: CodeAction,
21106 excerpt_id: ExcerptId,
21107 push_to_history: bool,
21108 window: &mut Window,
21109 cx: &mut App,
21110 ) -> Task<Result<ProjectTransaction>>;
21111}
21112
21113impl CodeActionProvider for Entity<Project> {
21114 fn id(&self) -> Arc<str> {
21115 "project".into()
21116 }
21117
21118 fn code_actions(
21119 &self,
21120 buffer: &Entity<Buffer>,
21121 range: Range<text::Anchor>,
21122 _window: &mut Window,
21123 cx: &mut App,
21124 ) -> Task<Result<Vec<CodeAction>>> {
21125 self.update(cx, |project, cx| {
21126 let code_lens = project.code_lens(buffer, range.clone(), cx);
21127 let code_actions = project.code_actions(buffer, range, None, cx);
21128 cx.background_spawn(async move {
21129 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21130 Ok(code_lens
21131 .context("code lens fetch")?
21132 .into_iter()
21133 .chain(code_actions.context("code action fetch")?)
21134 .collect())
21135 })
21136 })
21137 }
21138
21139 fn apply_code_action(
21140 &self,
21141 buffer_handle: Entity<Buffer>,
21142 action: CodeAction,
21143 _excerpt_id: ExcerptId,
21144 push_to_history: bool,
21145 _window: &mut Window,
21146 cx: &mut App,
21147 ) -> Task<Result<ProjectTransaction>> {
21148 self.update(cx, |project, cx| {
21149 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21150 })
21151 }
21152}
21153
21154fn snippet_completions(
21155 project: &Project,
21156 buffer: &Entity<Buffer>,
21157 buffer_position: text::Anchor,
21158 cx: &mut App,
21159) -> Task<Result<CompletionResponse>> {
21160 let languages = buffer.read(cx).languages_at(buffer_position);
21161 let snippet_store = project.snippets().read(cx);
21162
21163 let scopes: Vec<_> = languages
21164 .iter()
21165 .filter_map(|language| {
21166 let language_name = language.lsp_id();
21167 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21168
21169 if snippets.is_empty() {
21170 None
21171 } else {
21172 Some((language.default_scope(), snippets))
21173 }
21174 })
21175 .collect();
21176
21177 if scopes.is_empty() {
21178 return Task::ready(Ok(CompletionResponse {
21179 completions: vec![],
21180 is_incomplete: false,
21181 }));
21182 }
21183
21184 let snapshot = buffer.read(cx).text_snapshot();
21185 let chars: String = snapshot
21186 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21187 .collect();
21188 let executor = cx.background_executor().clone();
21189
21190 cx.background_spawn(async move {
21191 let mut is_incomplete = false;
21192 let mut completions: Vec<Completion> = Vec::new();
21193 for (scope, snippets) in scopes.into_iter() {
21194 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21195 let mut last_word = chars
21196 .chars()
21197 .take_while(|c| classifier.is_word(*c))
21198 .collect::<String>();
21199 last_word = last_word.chars().rev().collect();
21200
21201 if last_word.is_empty() {
21202 return Ok(CompletionResponse {
21203 completions: vec![],
21204 is_incomplete: true,
21205 });
21206 }
21207
21208 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21209 let to_lsp = |point: &text::Anchor| {
21210 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21211 point_to_lsp(end)
21212 };
21213 let lsp_end = to_lsp(&buffer_position);
21214
21215 let candidates = snippets
21216 .iter()
21217 .enumerate()
21218 .flat_map(|(ix, snippet)| {
21219 snippet
21220 .prefix
21221 .iter()
21222 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21223 })
21224 .collect::<Vec<StringMatchCandidate>>();
21225
21226 const MAX_RESULTS: usize = 100;
21227 let mut matches = fuzzy::match_strings(
21228 &candidates,
21229 &last_word,
21230 last_word.chars().any(|c| c.is_uppercase()),
21231 true,
21232 MAX_RESULTS,
21233 &Default::default(),
21234 executor.clone(),
21235 )
21236 .await;
21237
21238 if matches.len() >= MAX_RESULTS {
21239 is_incomplete = true;
21240 }
21241
21242 // Remove all candidates where the query's start does not match the start of any word in the candidate
21243 if let Some(query_start) = last_word.chars().next() {
21244 matches.retain(|string_match| {
21245 split_words(&string_match.string).any(|word| {
21246 // Check that the first codepoint of the word as lowercase matches the first
21247 // codepoint of the query as lowercase
21248 word.chars()
21249 .flat_map(|codepoint| codepoint.to_lowercase())
21250 .zip(query_start.to_lowercase())
21251 .all(|(word_cp, query_cp)| word_cp == query_cp)
21252 })
21253 });
21254 }
21255
21256 let matched_strings = matches
21257 .into_iter()
21258 .map(|m| m.string)
21259 .collect::<HashSet<_>>();
21260
21261 completions.extend(snippets.iter().filter_map(|snippet| {
21262 let matching_prefix = snippet
21263 .prefix
21264 .iter()
21265 .find(|prefix| matched_strings.contains(*prefix))?;
21266 let start = as_offset - last_word.len();
21267 let start = snapshot.anchor_before(start);
21268 let range = start..buffer_position;
21269 let lsp_start = to_lsp(&start);
21270 let lsp_range = lsp::Range {
21271 start: lsp_start,
21272 end: lsp_end,
21273 };
21274 Some(Completion {
21275 replace_range: range,
21276 new_text: snippet.body.clone(),
21277 source: CompletionSource::Lsp {
21278 insert_range: None,
21279 server_id: LanguageServerId(usize::MAX),
21280 resolved: true,
21281 lsp_completion: Box::new(lsp::CompletionItem {
21282 label: snippet.prefix.first().unwrap().clone(),
21283 kind: Some(CompletionItemKind::SNIPPET),
21284 label_details: snippet.description.as_ref().map(|description| {
21285 lsp::CompletionItemLabelDetails {
21286 detail: Some(description.clone()),
21287 description: None,
21288 }
21289 }),
21290 insert_text_format: Some(InsertTextFormat::SNIPPET),
21291 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21292 lsp::InsertReplaceEdit {
21293 new_text: snippet.body.clone(),
21294 insert: lsp_range,
21295 replace: lsp_range,
21296 },
21297 )),
21298 filter_text: Some(snippet.body.clone()),
21299 sort_text: Some(char::MAX.to_string()),
21300 ..lsp::CompletionItem::default()
21301 }),
21302 lsp_defaults: None,
21303 },
21304 label: CodeLabel {
21305 text: matching_prefix.clone(),
21306 runs: Vec::new(),
21307 filter_range: 0..matching_prefix.len(),
21308 },
21309 icon_path: None,
21310 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21311 single_line: snippet.name.clone().into(),
21312 plain_text: snippet
21313 .description
21314 .clone()
21315 .map(|description| description.into()),
21316 }),
21317 insert_text_mode: None,
21318 confirm: None,
21319 })
21320 }))
21321 }
21322
21323 Ok(CompletionResponse {
21324 completions,
21325 is_incomplete,
21326 })
21327 })
21328}
21329
21330impl CompletionProvider for Entity<Project> {
21331 fn completions(
21332 &self,
21333 _excerpt_id: ExcerptId,
21334 buffer: &Entity<Buffer>,
21335 buffer_position: text::Anchor,
21336 options: CompletionContext,
21337 _window: &mut Window,
21338 cx: &mut Context<Editor>,
21339 ) -> Task<Result<Vec<CompletionResponse>>> {
21340 self.update(cx, |project, cx| {
21341 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21342 let project_completions = project.completions(buffer, buffer_position, options, cx);
21343 cx.background_spawn(async move {
21344 let mut responses = project_completions.await?;
21345 let snippets = snippets.await?;
21346 if !snippets.completions.is_empty() {
21347 responses.push(snippets);
21348 }
21349 Ok(responses)
21350 })
21351 })
21352 }
21353
21354 fn resolve_completions(
21355 &self,
21356 buffer: Entity<Buffer>,
21357 completion_indices: Vec<usize>,
21358 completions: Rc<RefCell<Box<[Completion]>>>,
21359 cx: &mut Context<Editor>,
21360 ) -> Task<Result<bool>> {
21361 self.update(cx, |project, cx| {
21362 project.lsp_store().update(cx, |lsp_store, cx| {
21363 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21364 })
21365 })
21366 }
21367
21368 fn apply_additional_edits_for_completion(
21369 &self,
21370 buffer: Entity<Buffer>,
21371 completions: Rc<RefCell<Box<[Completion]>>>,
21372 completion_index: usize,
21373 push_to_history: bool,
21374 cx: &mut Context<Editor>,
21375 ) -> Task<Result<Option<language::Transaction>>> {
21376 self.update(cx, |project, cx| {
21377 project.lsp_store().update(cx, |lsp_store, cx| {
21378 lsp_store.apply_additional_edits_for_completion(
21379 buffer,
21380 completions,
21381 completion_index,
21382 push_to_history,
21383 cx,
21384 )
21385 })
21386 })
21387 }
21388
21389 fn is_completion_trigger(
21390 &self,
21391 buffer: &Entity<Buffer>,
21392 position: language::Anchor,
21393 text: &str,
21394 trigger_in_words: bool,
21395 menu_is_open: bool,
21396 cx: &mut Context<Editor>,
21397 ) -> bool {
21398 let mut chars = text.chars();
21399 let char = if let Some(char) = chars.next() {
21400 char
21401 } else {
21402 return false;
21403 };
21404 if chars.next().is_some() {
21405 return false;
21406 }
21407
21408 let buffer = buffer.read(cx);
21409 let snapshot = buffer.snapshot();
21410 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21411 return false;
21412 }
21413 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21414 if trigger_in_words && classifier.is_word(char) {
21415 return true;
21416 }
21417
21418 buffer.completion_triggers().contains(text)
21419 }
21420}
21421
21422impl SemanticsProvider for Entity<Project> {
21423 fn hover(
21424 &self,
21425 buffer: &Entity<Buffer>,
21426 position: text::Anchor,
21427 cx: &mut App,
21428 ) -> Option<Task<Vec<project::Hover>>> {
21429 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21430 }
21431
21432 fn document_highlights(
21433 &self,
21434 buffer: &Entity<Buffer>,
21435 position: text::Anchor,
21436 cx: &mut App,
21437 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21438 Some(self.update(cx, |project, cx| {
21439 project.document_highlights(buffer, position, cx)
21440 }))
21441 }
21442
21443 fn definitions(
21444 &self,
21445 buffer: &Entity<Buffer>,
21446 position: text::Anchor,
21447 kind: GotoDefinitionKind,
21448 cx: &mut App,
21449 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21450 Some(self.update(cx, |project, cx| match kind {
21451 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21452 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21453 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21454 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21455 }))
21456 }
21457
21458 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21459 // TODO: make this work for remote projects
21460 self.update(cx, |project, cx| {
21461 if project
21462 .active_debug_session(cx)
21463 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21464 {
21465 return true;
21466 }
21467
21468 buffer.update(cx, |buffer, cx| {
21469 project.any_language_server_supports_inlay_hints(buffer, cx)
21470 })
21471 })
21472 }
21473
21474 fn inline_values(
21475 &self,
21476 buffer_handle: Entity<Buffer>,
21477
21478 range: Range<text::Anchor>,
21479 cx: &mut App,
21480 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21481 self.update(cx, |project, cx| {
21482 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21483
21484 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21485 })
21486 }
21487
21488 fn inlay_hints(
21489 &self,
21490 buffer_handle: Entity<Buffer>,
21491 range: Range<text::Anchor>,
21492 cx: &mut App,
21493 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21494 Some(self.update(cx, |project, cx| {
21495 project.inlay_hints(buffer_handle, range, cx)
21496 }))
21497 }
21498
21499 fn resolve_inlay_hint(
21500 &self,
21501 hint: InlayHint,
21502 buffer_handle: Entity<Buffer>,
21503 server_id: LanguageServerId,
21504 cx: &mut App,
21505 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21506 Some(self.update(cx, |project, cx| {
21507 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21508 }))
21509 }
21510
21511 fn range_for_rename(
21512 &self,
21513 buffer: &Entity<Buffer>,
21514 position: text::Anchor,
21515 cx: &mut App,
21516 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21517 Some(self.update(cx, |project, cx| {
21518 let buffer = buffer.clone();
21519 let task = project.prepare_rename(buffer.clone(), position, cx);
21520 cx.spawn(async move |_, cx| {
21521 Ok(match task.await? {
21522 PrepareRenameResponse::Success(range) => Some(range),
21523 PrepareRenameResponse::InvalidPosition => None,
21524 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21525 // Fallback on using TreeSitter info to determine identifier range
21526 buffer.read_with(cx, |buffer, _| {
21527 let snapshot = buffer.snapshot();
21528 let (range, kind) = snapshot.surrounding_word(position);
21529 if kind != Some(CharKind::Word) {
21530 return None;
21531 }
21532 Some(
21533 snapshot.anchor_before(range.start)
21534 ..snapshot.anchor_after(range.end),
21535 )
21536 })?
21537 }
21538 })
21539 })
21540 }))
21541 }
21542
21543 fn perform_rename(
21544 &self,
21545 buffer: &Entity<Buffer>,
21546 position: text::Anchor,
21547 new_name: String,
21548 cx: &mut App,
21549 ) -> Option<Task<Result<ProjectTransaction>>> {
21550 Some(self.update(cx, |project, cx| {
21551 project.perform_rename(buffer.clone(), position, new_name, cx)
21552 }))
21553 }
21554}
21555
21556fn inlay_hint_settings(
21557 location: Anchor,
21558 snapshot: &MultiBufferSnapshot,
21559 cx: &mut Context<Editor>,
21560) -> InlayHintSettings {
21561 let file = snapshot.file_at(location);
21562 let language = snapshot.language_at(location).map(|l| l.name());
21563 language_settings(language, file, cx).inlay_hints
21564}
21565
21566fn consume_contiguous_rows(
21567 contiguous_row_selections: &mut Vec<Selection<Point>>,
21568 selection: &Selection<Point>,
21569 display_map: &DisplaySnapshot,
21570 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21571) -> (MultiBufferRow, MultiBufferRow) {
21572 contiguous_row_selections.push(selection.clone());
21573 let start_row = MultiBufferRow(selection.start.row);
21574 let mut end_row = ending_row(selection, display_map);
21575
21576 while let Some(next_selection) = selections.peek() {
21577 if next_selection.start.row <= end_row.0 {
21578 end_row = ending_row(next_selection, display_map);
21579 contiguous_row_selections.push(selections.next().unwrap().clone());
21580 } else {
21581 break;
21582 }
21583 }
21584 (start_row, end_row)
21585}
21586
21587fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21588 if next_selection.end.column > 0 || next_selection.is_empty() {
21589 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21590 } else {
21591 MultiBufferRow(next_selection.end.row)
21592 }
21593}
21594
21595impl EditorSnapshot {
21596 pub fn remote_selections_in_range<'a>(
21597 &'a self,
21598 range: &'a Range<Anchor>,
21599 collaboration_hub: &dyn CollaborationHub,
21600 cx: &'a App,
21601 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21602 let participant_names = collaboration_hub.user_names(cx);
21603 let participant_indices = collaboration_hub.user_participant_indices(cx);
21604 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21605 let collaborators_by_replica_id = collaborators_by_peer_id
21606 .values()
21607 .map(|collaborator| (collaborator.replica_id, collaborator))
21608 .collect::<HashMap<_, _>>();
21609 self.buffer_snapshot
21610 .selections_in_range(range, false)
21611 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21612 if replica_id == AGENT_REPLICA_ID {
21613 Some(RemoteSelection {
21614 replica_id,
21615 selection,
21616 cursor_shape,
21617 line_mode,
21618 collaborator_id: CollaboratorId::Agent,
21619 user_name: Some("Agent".into()),
21620 color: cx.theme().players().agent(),
21621 })
21622 } else {
21623 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21624 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21625 let user_name = participant_names.get(&collaborator.user_id).cloned();
21626 Some(RemoteSelection {
21627 replica_id,
21628 selection,
21629 cursor_shape,
21630 line_mode,
21631 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21632 user_name,
21633 color: if let Some(index) = participant_index {
21634 cx.theme().players().color_for_participant(index.0)
21635 } else {
21636 cx.theme().players().absent()
21637 },
21638 })
21639 }
21640 })
21641 }
21642
21643 pub fn hunks_for_ranges(
21644 &self,
21645 ranges: impl IntoIterator<Item = Range<Point>>,
21646 ) -> Vec<MultiBufferDiffHunk> {
21647 let mut hunks = Vec::new();
21648 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21649 HashMap::default();
21650 for query_range in ranges {
21651 let query_rows =
21652 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21653 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21654 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21655 ) {
21656 // Include deleted hunks that are adjacent to the query range, because
21657 // otherwise they would be missed.
21658 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21659 if hunk.status().is_deleted() {
21660 intersects_range |= hunk.row_range.start == query_rows.end;
21661 intersects_range |= hunk.row_range.end == query_rows.start;
21662 }
21663 if intersects_range {
21664 if !processed_buffer_rows
21665 .entry(hunk.buffer_id)
21666 .or_default()
21667 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21668 {
21669 continue;
21670 }
21671 hunks.push(hunk);
21672 }
21673 }
21674 }
21675
21676 hunks
21677 }
21678
21679 fn display_diff_hunks_for_rows<'a>(
21680 &'a self,
21681 display_rows: Range<DisplayRow>,
21682 folded_buffers: &'a HashSet<BufferId>,
21683 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21684 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21685 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21686
21687 self.buffer_snapshot
21688 .diff_hunks_in_range(buffer_start..buffer_end)
21689 .filter_map(|hunk| {
21690 if folded_buffers.contains(&hunk.buffer_id) {
21691 return None;
21692 }
21693
21694 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21695 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21696
21697 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21698 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21699
21700 let display_hunk = if hunk_display_start.column() != 0 {
21701 DisplayDiffHunk::Folded {
21702 display_row: hunk_display_start.row(),
21703 }
21704 } else {
21705 let mut end_row = hunk_display_end.row();
21706 if hunk_display_end.column() > 0 {
21707 end_row.0 += 1;
21708 }
21709 let is_created_file = hunk.is_created_file();
21710 DisplayDiffHunk::Unfolded {
21711 status: hunk.status(),
21712 diff_base_byte_range: hunk.diff_base_byte_range,
21713 display_row_range: hunk_display_start.row()..end_row,
21714 multi_buffer_range: Anchor::range_in_buffer(
21715 hunk.excerpt_id,
21716 hunk.buffer_id,
21717 hunk.buffer_range,
21718 ),
21719 is_created_file,
21720 }
21721 };
21722
21723 Some(display_hunk)
21724 })
21725 }
21726
21727 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21728 self.display_snapshot.buffer_snapshot.language_at(position)
21729 }
21730
21731 pub fn is_focused(&self) -> bool {
21732 self.is_focused
21733 }
21734
21735 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21736 self.placeholder_text.as_ref()
21737 }
21738
21739 pub fn scroll_position(&self) -> gpui::Point<f32> {
21740 self.scroll_anchor.scroll_position(&self.display_snapshot)
21741 }
21742
21743 fn gutter_dimensions(
21744 &self,
21745 font_id: FontId,
21746 font_size: Pixels,
21747 max_line_number_width: Pixels,
21748 cx: &App,
21749 ) -> Option<GutterDimensions> {
21750 if !self.show_gutter {
21751 return None;
21752 }
21753
21754 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21755 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21756
21757 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21758 matches!(
21759 ProjectSettings::get_global(cx).git.git_gutter,
21760 Some(GitGutterSetting::TrackedFiles)
21761 )
21762 });
21763 let gutter_settings = EditorSettings::get_global(cx).gutter;
21764 let show_line_numbers = self
21765 .show_line_numbers
21766 .unwrap_or(gutter_settings.line_numbers);
21767 let line_gutter_width = if show_line_numbers {
21768 // Avoid flicker-like gutter resizes when the line number gains another digit by
21769 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21770 let min_width_for_number_on_gutter =
21771 ch_advance * gutter_settings.min_line_number_digits as f32;
21772 max_line_number_width.max(min_width_for_number_on_gutter)
21773 } else {
21774 0.0.into()
21775 };
21776
21777 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21778 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21779
21780 let git_blame_entries_width =
21781 self.git_blame_gutter_max_author_length
21782 .map(|max_author_length| {
21783 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21784 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21785
21786 /// The number of characters to dedicate to gaps and margins.
21787 const SPACING_WIDTH: usize = 4;
21788
21789 let max_char_count = max_author_length.min(renderer.max_author_length())
21790 + ::git::SHORT_SHA_LENGTH
21791 + MAX_RELATIVE_TIMESTAMP.len()
21792 + SPACING_WIDTH;
21793
21794 ch_advance * max_char_count
21795 });
21796
21797 let is_singleton = self.buffer_snapshot.is_singleton();
21798
21799 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21800 left_padding += if !is_singleton {
21801 ch_width * 4.0
21802 } else if show_runnables || show_breakpoints {
21803 ch_width * 3.0
21804 } else if show_git_gutter && show_line_numbers {
21805 ch_width * 2.0
21806 } else if show_git_gutter || show_line_numbers {
21807 ch_width
21808 } else {
21809 px(0.)
21810 };
21811
21812 let shows_folds = is_singleton && gutter_settings.folds;
21813
21814 let right_padding = if shows_folds && show_line_numbers {
21815 ch_width * 4.0
21816 } else if shows_folds || (!is_singleton && show_line_numbers) {
21817 ch_width * 3.0
21818 } else if show_line_numbers {
21819 ch_width
21820 } else {
21821 px(0.)
21822 };
21823
21824 Some(GutterDimensions {
21825 left_padding,
21826 right_padding,
21827 width: line_gutter_width + left_padding + right_padding,
21828 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21829 git_blame_entries_width,
21830 })
21831 }
21832
21833 pub fn render_crease_toggle(
21834 &self,
21835 buffer_row: MultiBufferRow,
21836 row_contains_cursor: bool,
21837 editor: Entity<Editor>,
21838 window: &mut Window,
21839 cx: &mut App,
21840 ) -> Option<AnyElement> {
21841 let folded = self.is_line_folded(buffer_row);
21842 let mut is_foldable = false;
21843
21844 if let Some(crease) = self
21845 .crease_snapshot
21846 .query_row(buffer_row, &self.buffer_snapshot)
21847 {
21848 is_foldable = true;
21849 match crease {
21850 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21851 if let Some(render_toggle) = render_toggle {
21852 let toggle_callback =
21853 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21854 if folded {
21855 editor.update(cx, |editor, cx| {
21856 editor.fold_at(buffer_row, window, cx)
21857 });
21858 } else {
21859 editor.update(cx, |editor, cx| {
21860 editor.unfold_at(buffer_row, window, cx)
21861 });
21862 }
21863 });
21864 return Some((render_toggle)(
21865 buffer_row,
21866 folded,
21867 toggle_callback,
21868 window,
21869 cx,
21870 ));
21871 }
21872 }
21873 }
21874 }
21875
21876 is_foldable |= self.starts_indent(buffer_row);
21877
21878 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21879 Some(
21880 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21881 .toggle_state(folded)
21882 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21883 if folded {
21884 this.unfold_at(buffer_row, window, cx);
21885 } else {
21886 this.fold_at(buffer_row, window, cx);
21887 }
21888 }))
21889 .into_any_element(),
21890 )
21891 } else {
21892 None
21893 }
21894 }
21895
21896 pub fn render_crease_trailer(
21897 &self,
21898 buffer_row: MultiBufferRow,
21899 window: &mut Window,
21900 cx: &mut App,
21901 ) -> Option<AnyElement> {
21902 let folded = self.is_line_folded(buffer_row);
21903 if let Crease::Inline { render_trailer, .. } = self
21904 .crease_snapshot
21905 .query_row(buffer_row, &self.buffer_snapshot)?
21906 {
21907 let render_trailer = render_trailer.as_ref()?;
21908 Some(render_trailer(buffer_row, folded, window, cx))
21909 } else {
21910 None
21911 }
21912 }
21913}
21914
21915impl Deref for EditorSnapshot {
21916 type Target = DisplaySnapshot;
21917
21918 fn deref(&self) -> &Self::Target {
21919 &self.display_snapshot
21920 }
21921}
21922
21923#[derive(Clone, Debug, PartialEq, Eq)]
21924pub enum EditorEvent {
21925 InputIgnored {
21926 text: Arc<str>,
21927 },
21928 InputHandled {
21929 utf16_range_to_replace: Option<Range<isize>>,
21930 text: Arc<str>,
21931 },
21932 ExcerptsAdded {
21933 buffer: Entity<Buffer>,
21934 predecessor: ExcerptId,
21935 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21936 },
21937 ExcerptsRemoved {
21938 ids: Vec<ExcerptId>,
21939 removed_buffer_ids: Vec<BufferId>,
21940 },
21941 BufferFoldToggled {
21942 ids: Vec<ExcerptId>,
21943 folded: bool,
21944 },
21945 ExcerptsEdited {
21946 ids: Vec<ExcerptId>,
21947 },
21948 ExcerptsExpanded {
21949 ids: Vec<ExcerptId>,
21950 },
21951 BufferEdited,
21952 Edited {
21953 transaction_id: clock::Lamport,
21954 },
21955 Reparsed(BufferId),
21956 Focused,
21957 FocusedIn,
21958 Blurred,
21959 DirtyChanged,
21960 Saved,
21961 TitleChanged,
21962 DiffBaseChanged,
21963 SelectionsChanged {
21964 local: bool,
21965 },
21966 ScrollPositionChanged {
21967 local: bool,
21968 autoscroll: bool,
21969 },
21970 Closed,
21971 TransactionUndone {
21972 transaction_id: clock::Lamport,
21973 },
21974 TransactionBegun {
21975 transaction_id: clock::Lamport,
21976 },
21977 Reloaded,
21978 CursorShapeChanged,
21979 PushedToNavHistory {
21980 anchor: Anchor,
21981 is_deactivate: bool,
21982 },
21983}
21984
21985impl EventEmitter<EditorEvent> for Editor {}
21986
21987impl Focusable for Editor {
21988 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21989 self.focus_handle.clone()
21990 }
21991}
21992
21993impl Render for Editor {
21994 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21995 let settings = ThemeSettings::get_global(cx);
21996
21997 let mut text_style = match self.mode {
21998 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21999 color: cx.theme().colors().editor_foreground,
22000 font_family: settings.ui_font.family.clone(),
22001 font_features: settings.ui_font.features.clone(),
22002 font_fallbacks: settings.ui_font.fallbacks.clone(),
22003 font_size: rems(0.875).into(),
22004 font_weight: settings.ui_font.weight,
22005 line_height: relative(settings.buffer_line_height.value()),
22006 ..Default::default()
22007 },
22008 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22009 color: cx.theme().colors().editor_foreground,
22010 font_family: settings.buffer_font.family.clone(),
22011 font_features: settings.buffer_font.features.clone(),
22012 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22013 font_size: settings.buffer_font_size(cx).into(),
22014 font_weight: settings.buffer_font.weight,
22015 line_height: relative(settings.buffer_line_height.value()),
22016 ..Default::default()
22017 },
22018 };
22019 if let Some(text_style_refinement) = &self.text_style_refinement {
22020 text_style.refine(text_style_refinement)
22021 }
22022
22023 let background = match self.mode {
22024 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22025 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22026 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22027 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22028 };
22029
22030 EditorElement::new(
22031 &cx.entity(),
22032 EditorStyle {
22033 background,
22034 local_player: cx.theme().players().local(),
22035 text: text_style,
22036 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22037 syntax: cx.theme().syntax().clone(),
22038 status: cx.theme().status().clone(),
22039 inlay_hints_style: make_inlay_hints_style(cx),
22040 inline_completion_styles: make_suggestion_styles(cx),
22041 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22042 show_underlines: !self.mode.is_minimap(),
22043 },
22044 )
22045 }
22046}
22047
22048impl EntityInputHandler for Editor {
22049 fn text_for_range(
22050 &mut self,
22051 range_utf16: Range<usize>,
22052 adjusted_range: &mut Option<Range<usize>>,
22053 _: &mut Window,
22054 cx: &mut Context<Self>,
22055 ) -> Option<String> {
22056 let snapshot = self.buffer.read(cx).read(cx);
22057 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22058 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22059 if (start.0..end.0) != range_utf16 {
22060 adjusted_range.replace(start.0..end.0);
22061 }
22062 Some(snapshot.text_for_range(start..end).collect())
22063 }
22064
22065 fn selected_text_range(
22066 &mut self,
22067 ignore_disabled_input: bool,
22068 _: &mut Window,
22069 cx: &mut Context<Self>,
22070 ) -> Option<UTF16Selection> {
22071 // Prevent the IME menu from appearing when holding down an alphabetic key
22072 // while input is disabled.
22073 if !ignore_disabled_input && !self.input_enabled {
22074 return None;
22075 }
22076
22077 let selection = self.selections.newest::<OffsetUtf16>(cx);
22078 let range = selection.range();
22079
22080 Some(UTF16Selection {
22081 range: range.start.0..range.end.0,
22082 reversed: selection.reversed,
22083 })
22084 }
22085
22086 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22087 let snapshot = self.buffer.read(cx).read(cx);
22088 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22089 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22090 }
22091
22092 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22093 self.clear_highlights::<InputComposition>(cx);
22094 self.ime_transaction.take();
22095 }
22096
22097 fn replace_text_in_range(
22098 &mut self,
22099 range_utf16: Option<Range<usize>>,
22100 text: &str,
22101 window: &mut Window,
22102 cx: &mut Context<Self>,
22103 ) {
22104 if !self.input_enabled {
22105 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22106 return;
22107 }
22108
22109 self.transact(window, cx, |this, window, cx| {
22110 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22111 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22112 Some(this.selection_replacement_ranges(range_utf16, cx))
22113 } else {
22114 this.marked_text_ranges(cx)
22115 };
22116
22117 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22118 let newest_selection_id = this.selections.newest_anchor().id;
22119 this.selections
22120 .all::<OffsetUtf16>(cx)
22121 .iter()
22122 .zip(ranges_to_replace.iter())
22123 .find_map(|(selection, range)| {
22124 if selection.id == newest_selection_id {
22125 Some(
22126 (range.start.0 as isize - selection.head().0 as isize)
22127 ..(range.end.0 as isize - selection.head().0 as isize),
22128 )
22129 } else {
22130 None
22131 }
22132 })
22133 });
22134
22135 cx.emit(EditorEvent::InputHandled {
22136 utf16_range_to_replace: range_to_replace,
22137 text: text.into(),
22138 });
22139
22140 if let Some(new_selected_ranges) = new_selected_ranges {
22141 this.change_selections(None, window, cx, |selections| {
22142 selections.select_ranges(new_selected_ranges)
22143 });
22144 this.backspace(&Default::default(), window, cx);
22145 }
22146
22147 this.handle_input(text, window, cx);
22148 });
22149
22150 if let Some(transaction) = self.ime_transaction {
22151 self.buffer.update(cx, |buffer, cx| {
22152 buffer.group_until_transaction(transaction, cx);
22153 });
22154 }
22155
22156 self.unmark_text(window, cx);
22157 }
22158
22159 fn replace_and_mark_text_in_range(
22160 &mut self,
22161 range_utf16: Option<Range<usize>>,
22162 text: &str,
22163 new_selected_range_utf16: Option<Range<usize>>,
22164 window: &mut Window,
22165 cx: &mut Context<Self>,
22166 ) {
22167 if !self.input_enabled {
22168 return;
22169 }
22170
22171 let transaction = self.transact(window, cx, |this, window, cx| {
22172 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22173 let snapshot = this.buffer.read(cx).read(cx);
22174 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22175 for marked_range in &mut marked_ranges {
22176 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22177 marked_range.start.0 += relative_range_utf16.start;
22178 marked_range.start =
22179 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22180 marked_range.end =
22181 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22182 }
22183 }
22184 Some(marked_ranges)
22185 } else if let Some(range_utf16) = range_utf16 {
22186 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22187 Some(this.selection_replacement_ranges(range_utf16, cx))
22188 } else {
22189 None
22190 };
22191
22192 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22193 let newest_selection_id = this.selections.newest_anchor().id;
22194 this.selections
22195 .all::<OffsetUtf16>(cx)
22196 .iter()
22197 .zip(ranges_to_replace.iter())
22198 .find_map(|(selection, range)| {
22199 if selection.id == newest_selection_id {
22200 Some(
22201 (range.start.0 as isize - selection.head().0 as isize)
22202 ..(range.end.0 as isize - selection.head().0 as isize),
22203 )
22204 } else {
22205 None
22206 }
22207 })
22208 });
22209
22210 cx.emit(EditorEvent::InputHandled {
22211 utf16_range_to_replace: range_to_replace,
22212 text: text.into(),
22213 });
22214
22215 if let Some(ranges) = ranges_to_replace {
22216 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22217 }
22218
22219 let marked_ranges = {
22220 let snapshot = this.buffer.read(cx).read(cx);
22221 this.selections
22222 .disjoint_anchors()
22223 .iter()
22224 .map(|selection| {
22225 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22226 })
22227 .collect::<Vec<_>>()
22228 };
22229
22230 if text.is_empty() {
22231 this.unmark_text(window, cx);
22232 } else {
22233 this.highlight_text::<InputComposition>(
22234 marked_ranges.clone(),
22235 HighlightStyle {
22236 underline: Some(UnderlineStyle {
22237 thickness: px(1.),
22238 color: None,
22239 wavy: false,
22240 }),
22241 ..Default::default()
22242 },
22243 cx,
22244 );
22245 }
22246
22247 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22248 let use_autoclose = this.use_autoclose;
22249 let use_auto_surround = this.use_auto_surround;
22250 this.set_use_autoclose(false);
22251 this.set_use_auto_surround(false);
22252 this.handle_input(text, window, cx);
22253 this.set_use_autoclose(use_autoclose);
22254 this.set_use_auto_surround(use_auto_surround);
22255
22256 if let Some(new_selected_range) = new_selected_range_utf16 {
22257 let snapshot = this.buffer.read(cx).read(cx);
22258 let new_selected_ranges = marked_ranges
22259 .into_iter()
22260 .map(|marked_range| {
22261 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22262 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22263 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22264 snapshot.clip_offset_utf16(new_start, Bias::Left)
22265 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22266 })
22267 .collect::<Vec<_>>();
22268
22269 drop(snapshot);
22270 this.change_selections(None, window, cx, |selections| {
22271 selections.select_ranges(new_selected_ranges)
22272 });
22273 }
22274 });
22275
22276 self.ime_transaction = self.ime_transaction.or(transaction);
22277 if let Some(transaction) = self.ime_transaction {
22278 self.buffer.update(cx, |buffer, cx| {
22279 buffer.group_until_transaction(transaction, cx);
22280 });
22281 }
22282
22283 if self.text_highlights::<InputComposition>(cx).is_none() {
22284 self.ime_transaction.take();
22285 }
22286 }
22287
22288 fn bounds_for_range(
22289 &mut self,
22290 range_utf16: Range<usize>,
22291 element_bounds: gpui::Bounds<Pixels>,
22292 window: &mut Window,
22293 cx: &mut Context<Self>,
22294 ) -> Option<gpui::Bounds<Pixels>> {
22295 let text_layout_details = self.text_layout_details(window);
22296 let gpui::Size {
22297 width: em_width,
22298 height: line_height,
22299 } = self.character_size(window);
22300
22301 let snapshot = self.snapshot(window, cx);
22302 let scroll_position = snapshot.scroll_position();
22303 let scroll_left = scroll_position.x * em_width;
22304
22305 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22306 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22307 + self.gutter_dimensions.width
22308 + self.gutter_dimensions.margin;
22309 let y = line_height * (start.row().as_f32() - scroll_position.y);
22310
22311 Some(Bounds {
22312 origin: element_bounds.origin + point(x, y),
22313 size: size(em_width, line_height),
22314 })
22315 }
22316
22317 fn character_index_for_point(
22318 &mut self,
22319 point: gpui::Point<Pixels>,
22320 _window: &mut Window,
22321 _cx: &mut Context<Self>,
22322 ) -> Option<usize> {
22323 let position_map = self.last_position_map.as_ref()?;
22324 if !position_map.text_hitbox.contains(&point) {
22325 return None;
22326 }
22327 let display_point = position_map.point_for_position(point).previous_valid;
22328 let anchor = position_map
22329 .snapshot
22330 .display_point_to_anchor(display_point, Bias::Left);
22331 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22332 Some(utf16_offset.0)
22333 }
22334}
22335
22336trait SelectionExt {
22337 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22338 fn spanned_rows(
22339 &self,
22340 include_end_if_at_line_start: bool,
22341 map: &DisplaySnapshot,
22342 ) -> Range<MultiBufferRow>;
22343}
22344
22345impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22346 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22347 let start = self
22348 .start
22349 .to_point(&map.buffer_snapshot)
22350 .to_display_point(map);
22351 let end = self
22352 .end
22353 .to_point(&map.buffer_snapshot)
22354 .to_display_point(map);
22355 if self.reversed {
22356 end..start
22357 } else {
22358 start..end
22359 }
22360 }
22361
22362 fn spanned_rows(
22363 &self,
22364 include_end_if_at_line_start: bool,
22365 map: &DisplaySnapshot,
22366 ) -> Range<MultiBufferRow> {
22367 let start = self.start.to_point(&map.buffer_snapshot);
22368 let mut end = self.end.to_point(&map.buffer_snapshot);
22369 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22370 end.row -= 1;
22371 }
22372
22373 let buffer_start = map.prev_line_boundary(start).0;
22374 let buffer_end = map.next_line_boundary(end).0;
22375 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22376 }
22377}
22378
22379impl<T: InvalidationRegion> InvalidationStack<T> {
22380 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22381 where
22382 S: Clone + ToOffset,
22383 {
22384 while let Some(region) = self.last() {
22385 let all_selections_inside_invalidation_ranges =
22386 if selections.len() == region.ranges().len() {
22387 selections
22388 .iter()
22389 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22390 .all(|(selection, invalidation_range)| {
22391 let head = selection.head().to_offset(buffer);
22392 invalidation_range.start <= head && invalidation_range.end >= head
22393 })
22394 } else {
22395 false
22396 };
22397
22398 if all_selections_inside_invalidation_ranges {
22399 break;
22400 } else {
22401 self.pop();
22402 }
22403 }
22404 }
22405}
22406
22407impl<T> Default for InvalidationStack<T> {
22408 fn default() -> Self {
22409 Self(Default::default())
22410 }
22411}
22412
22413impl<T> Deref for InvalidationStack<T> {
22414 type Target = Vec<T>;
22415
22416 fn deref(&self) -> &Self::Target {
22417 &self.0
22418 }
22419}
22420
22421impl<T> DerefMut for InvalidationStack<T> {
22422 fn deref_mut(&mut self) -> &mut Self::Target {
22423 &mut self.0
22424 }
22425}
22426
22427impl InvalidationRegion for SnippetState {
22428 fn ranges(&self) -> &[Range<Anchor>] {
22429 &self.ranges[self.active_index]
22430 }
22431}
22432
22433fn inline_completion_edit_text(
22434 current_snapshot: &BufferSnapshot,
22435 edits: &[(Range<Anchor>, String)],
22436 edit_preview: &EditPreview,
22437 include_deletions: bool,
22438 cx: &App,
22439) -> HighlightedText {
22440 let edits = edits
22441 .iter()
22442 .map(|(anchor, text)| {
22443 (
22444 anchor.start.text_anchor..anchor.end.text_anchor,
22445 text.clone(),
22446 )
22447 })
22448 .collect::<Vec<_>>();
22449
22450 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22451}
22452
22453pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22454 match severity {
22455 lsp::DiagnosticSeverity::ERROR => colors.error,
22456 lsp::DiagnosticSeverity::WARNING => colors.warning,
22457 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22458 lsp::DiagnosticSeverity::HINT => colors.info,
22459 _ => colors.ignored,
22460 }
22461}
22462
22463pub fn styled_runs_for_code_label<'a>(
22464 label: &'a CodeLabel,
22465 syntax_theme: &'a theme::SyntaxTheme,
22466) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22467 let fade_out = HighlightStyle {
22468 fade_out: Some(0.35),
22469 ..Default::default()
22470 };
22471
22472 let mut prev_end = label.filter_range.end;
22473 label
22474 .runs
22475 .iter()
22476 .enumerate()
22477 .flat_map(move |(ix, (range, highlight_id))| {
22478 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22479 style
22480 } else {
22481 return Default::default();
22482 };
22483 let mut muted_style = style;
22484 muted_style.highlight(fade_out);
22485
22486 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22487 if range.start >= label.filter_range.end {
22488 if range.start > prev_end {
22489 runs.push((prev_end..range.start, fade_out));
22490 }
22491 runs.push((range.clone(), muted_style));
22492 } else if range.end <= label.filter_range.end {
22493 runs.push((range.clone(), style));
22494 } else {
22495 runs.push((range.start..label.filter_range.end, style));
22496 runs.push((label.filter_range.end..range.end, muted_style));
22497 }
22498 prev_end = cmp::max(prev_end, range.end);
22499
22500 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22501 runs.push((prev_end..label.text.len(), fade_out));
22502 }
22503
22504 runs
22505 })
22506}
22507
22508pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22509 let mut prev_index = 0;
22510 let mut prev_codepoint: Option<char> = None;
22511 text.char_indices()
22512 .chain([(text.len(), '\0')])
22513 .filter_map(move |(index, codepoint)| {
22514 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22515 let is_boundary = index == text.len()
22516 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22517 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22518 if is_boundary {
22519 let chunk = &text[prev_index..index];
22520 prev_index = index;
22521 Some(chunk)
22522 } else {
22523 None
22524 }
22525 })
22526}
22527
22528pub trait RangeToAnchorExt: Sized {
22529 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22530
22531 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22532 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22533 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22534 }
22535}
22536
22537impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22538 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22539 let start_offset = self.start.to_offset(snapshot);
22540 let end_offset = self.end.to_offset(snapshot);
22541 if start_offset == end_offset {
22542 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22543 } else {
22544 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22545 }
22546 }
22547}
22548
22549pub trait RowExt {
22550 fn as_f32(&self) -> f32;
22551
22552 fn next_row(&self) -> Self;
22553
22554 fn previous_row(&self) -> Self;
22555
22556 fn minus(&self, other: Self) -> u32;
22557}
22558
22559impl RowExt for DisplayRow {
22560 fn as_f32(&self) -> f32 {
22561 self.0 as f32
22562 }
22563
22564 fn next_row(&self) -> Self {
22565 Self(self.0 + 1)
22566 }
22567
22568 fn previous_row(&self) -> Self {
22569 Self(self.0.saturating_sub(1))
22570 }
22571
22572 fn minus(&self, other: Self) -> u32 {
22573 self.0 - other.0
22574 }
22575}
22576
22577impl RowExt for MultiBufferRow {
22578 fn as_f32(&self) -> f32 {
22579 self.0 as f32
22580 }
22581
22582 fn next_row(&self) -> Self {
22583 Self(self.0 + 1)
22584 }
22585
22586 fn previous_row(&self) -> Self {
22587 Self(self.0.saturating_sub(1))
22588 }
22589
22590 fn minus(&self, other: Self) -> u32 {
22591 self.0 - other.0
22592 }
22593}
22594
22595trait RowRangeExt {
22596 type Row;
22597
22598 fn len(&self) -> usize;
22599
22600 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22601}
22602
22603impl RowRangeExt for Range<MultiBufferRow> {
22604 type Row = MultiBufferRow;
22605
22606 fn len(&self) -> usize {
22607 (self.end.0 - self.start.0) as usize
22608 }
22609
22610 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22611 (self.start.0..self.end.0).map(MultiBufferRow)
22612 }
22613}
22614
22615impl RowRangeExt for Range<DisplayRow> {
22616 type Row = DisplayRow;
22617
22618 fn len(&self) -> usize {
22619 (self.end.0 - self.start.0) as usize
22620 }
22621
22622 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22623 (self.start.0..self.end.0).map(DisplayRow)
22624 }
22625}
22626
22627/// If select range has more than one line, we
22628/// just point the cursor to range.start.
22629fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22630 if range.start.row == range.end.row {
22631 range
22632 } else {
22633 range.start..range.start
22634 }
22635}
22636pub struct KillRing(ClipboardItem);
22637impl Global for KillRing {}
22638
22639const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22640
22641enum BreakpointPromptEditAction {
22642 Log,
22643 Condition,
22644 HitCondition,
22645}
22646
22647struct BreakpointPromptEditor {
22648 pub(crate) prompt: Entity<Editor>,
22649 editor: WeakEntity<Editor>,
22650 breakpoint_anchor: Anchor,
22651 breakpoint: Breakpoint,
22652 edit_action: BreakpointPromptEditAction,
22653 block_ids: HashSet<CustomBlockId>,
22654 editor_margins: Arc<Mutex<EditorMargins>>,
22655 _subscriptions: Vec<Subscription>,
22656}
22657
22658impl BreakpointPromptEditor {
22659 const MAX_LINES: u8 = 4;
22660
22661 fn new(
22662 editor: WeakEntity<Editor>,
22663 breakpoint_anchor: Anchor,
22664 breakpoint: Breakpoint,
22665 edit_action: BreakpointPromptEditAction,
22666 window: &mut Window,
22667 cx: &mut Context<Self>,
22668 ) -> Self {
22669 let base_text = match edit_action {
22670 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22671 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22672 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22673 }
22674 .map(|msg| msg.to_string())
22675 .unwrap_or_default();
22676
22677 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22678 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22679
22680 let prompt = cx.new(|cx| {
22681 let mut prompt = Editor::new(
22682 EditorMode::AutoHeight {
22683 min_lines: 1,
22684 max_lines: Self::MAX_LINES as usize,
22685 },
22686 buffer,
22687 None,
22688 window,
22689 cx,
22690 );
22691 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22692 prompt.set_show_cursor_when_unfocused(false, cx);
22693 prompt.set_placeholder_text(
22694 match edit_action {
22695 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22696 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22697 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22698 },
22699 cx,
22700 );
22701
22702 prompt
22703 });
22704
22705 Self {
22706 prompt,
22707 editor,
22708 breakpoint_anchor,
22709 breakpoint,
22710 edit_action,
22711 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22712 block_ids: Default::default(),
22713 _subscriptions: vec![],
22714 }
22715 }
22716
22717 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22718 self.block_ids.extend(block_ids)
22719 }
22720
22721 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22722 if let Some(editor) = self.editor.upgrade() {
22723 let message = self
22724 .prompt
22725 .read(cx)
22726 .buffer
22727 .read(cx)
22728 .as_singleton()
22729 .expect("A multi buffer in breakpoint prompt isn't possible")
22730 .read(cx)
22731 .as_rope()
22732 .to_string();
22733
22734 editor.update(cx, |editor, cx| {
22735 editor.edit_breakpoint_at_anchor(
22736 self.breakpoint_anchor,
22737 self.breakpoint.clone(),
22738 match self.edit_action {
22739 BreakpointPromptEditAction::Log => {
22740 BreakpointEditAction::EditLogMessage(message.into())
22741 }
22742 BreakpointPromptEditAction::Condition => {
22743 BreakpointEditAction::EditCondition(message.into())
22744 }
22745 BreakpointPromptEditAction::HitCondition => {
22746 BreakpointEditAction::EditHitCondition(message.into())
22747 }
22748 },
22749 cx,
22750 );
22751
22752 editor.remove_blocks(self.block_ids.clone(), None, cx);
22753 cx.focus_self(window);
22754 });
22755 }
22756 }
22757
22758 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22759 self.editor
22760 .update(cx, |editor, cx| {
22761 editor.remove_blocks(self.block_ids.clone(), None, cx);
22762 window.focus(&editor.focus_handle);
22763 })
22764 .log_err();
22765 }
22766
22767 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22768 let settings = ThemeSettings::get_global(cx);
22769 let text_style = TextStyle {
22770 color: if self.prompt.read(cx).read_only(cx) {
22771 cx.theme().colors().text_disabled
22772 } else {
22773 cx.theme().colors().text
22774 },
22775 font_family: settings.buffer_font.family.clone(),
22776 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22777 font_size: settings.buffer_font_size(cx).into(),
22778 font_weight: settings.buffer_font.weight,
22779 line_height: relative(settings.buffer_line_height.value()),
22780 ..Default::default()
22781 };
22782 EditorElement::new(
22783 &self.prompt,
22784 EditorStyle {
22785 background: cx.theme().colors().editor_background,
22786 local_player: cx.theme().players().local(),
22787 text: text_style,
22788 ..Default::default()
22789 },
22790 )
22791 }
22792}
22793
22794impl Render for BreakpointPromptEditor {
22795 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22796 let editor_margins = *self.editor_margins.lock();
22797 let gutter_dimensions = editor_margins.gutter;
22798 h_flex()
22799 .key_context("Editor")
22800 .bg(cx.theme().colors().editor_background)
22801 .border_y_1()
22802 .border_color(cx.theme().status().info_border)
22803 .size_full()
22804 .py(window.line_height() / 2.5)
22805 .on_action(cx.listener(Self::confirm))
22806 .on_action(cx.listener(Self::cancel))
22807 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22808 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22809 }
22810}
22811
22812impl Focusable for BreakpointPromptEditor {
22813 fn focus_handle(&self, cx: &App) -> FocusHandle {
22814 self.prompt.focus_handle(cx)
22815 }
22816}
22817
22818fn all_edits_insertions_or_deletions(
22819 edits: &Vec<(Range<Anchor>, String)>,
22820 snapshot: &MultiBufferSnapshot,
22821) -> bool {
22822 let mut all_insertions = true;
22823 let mut all_deletions = true;
22824
22825 for (range, new_text) in edits.iter() {
22826 let range_is_empty = range.to_offset(&snapshot).is_empty();
22827 let text_is_empty = new_text.is_empty();
22828
22829 if range_is_empty != text_is_empty {
22830 if range_is_empty {
22831 all_deletions = false;
22832 } else {
22833 all_insertions = false;
22834 }
22835 } else {
22836 return false;
22837 }
22838
22839 if !all_insertions && !all_deletions {
22840 return false;
22841 }
22842 }
22843 all_insertions || all_deletions
22844}
22845
22846struct MissingEditPredictionKeybindingTooltip;
22847
22848impl Render for MissingEditPredictionKeybindingTooltip {
22849 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22850 ui::tooltip_container(window, cx, |container, _, cx| {
22851 container
22852 .flex_shrink_0()
22853 .max_w_80()
22854 .min_h(rems_from_px(124.))
22855 .justify_between()
22856 .child(
22857 v_flex()
22858 .flex_1()
22859 .text_ui_sm(cx)
22860 .child(Label::new("Conflict with Accept Keybinding"))
22861 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22862 )
22863 .child(
22864 h_flex()
22865 .pb_1()
22866 .gap_1()
22867 .items_end()
22868 .w_full()
22869 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22870 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22871 }))
22872 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22873 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22874 })),
22875 )
22876 })
22877 }
22878}
22879
22880#[derive(Debug, Clone, Copy, PartialEq)]
22881pub struct LineHighlight {
22882 pub background: Background,
22883 pub border: Option<gpui::Hsla>,
22884 pub include_gutter: bool,
22885 pub type_id: Option<TypeId>,
22886}
22887
22888fn render_diff_hunk_controls(
22889 row: u32,
22890 status: &DiffHunkStatus,
22891 hunk_range: Range<Anchor>,
22892 is_created_file: bool,
22893 line_height: Pixels,
22894 editor: &Entity<Editor>,
22895 _window: &mut Window,
22896 cx: &mut App,
22897) -> AnyElement {
22898 h_flex()
22899 .h(line_height)
22900 .mr_1()
22901 .gap_1()
22902 .px_0p5()
22903 .pb_1()
22904 .border_x_1()
22905 .border_b_1()
22906 .border_color(cx.theme().colors().border_variant)
22907 .rounded_b_lg()
22908 .bg(cx.theme().colors().editor_background)
22909 .gap_1()
22910 .block_mouse_except_scroll()
22911 .shadow_md()
22912 .child(if status.has_secondary_hunk() {
22913 Button::new(("stage", row as u64), "Stage")
22914 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22915 .tooltip({
22916 let focus_handle = editor.focus_handle(cx);
22917 move |window, cx| {
22918 Tooltip::for_action_in(
22919 "Stage Hunk",
22920 &::git::ToggleStaged,
22921 &focus_handle,
22922 window,
22923 cx,
22924 )
22925 }
22926 })
22927 .on_click({
22928 let editor = editor.clone();
22929 move |_event, _window, cx| {
22930 editor.update(cx, |editor, cx| {
22931 editor.stage_or_unstage_diff_hunks(
22932 true,
22933 vec![hunk_range.start..hunk_range.start],
22934 cx,
22935 );
22936 });
22937 }
22938 })
22939 } else {
22940 Button::new(("unstage", row as u64), "Unstage")
22941 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22942 .tooltip({
22943 let focus_handle = editor.focus_handle(cx);
22944 move |window, cx| {
22945 Tooltip::for_action_in(
22946 "Unstage Hunk",
22947 &::git::ToggleStaged,
22948 &focus_handle,
22949 window,
22950 cx,
22951 )
22952 }
22953 })
22954 .on_click({
22955 let editor = editor.clone();
22956 move |_event, _window, cx| {
22957 editor.update(cx, |editor, cx| {
22958 editor.stage_or_unstage_diff_hunks(
22959 false,
22960 vec![hunk_range.start..hunk_range.start],
22961 cx,
22962 );
22963 });
22964 }
22965 })
22966 })
22967 .child(
22968 Button::new(("restore", row as u64), "Restore")
22969 .tooltip({
22970 let focus_handle = editor.focus_handle(cx);
22971 move |window, cx| {
22972 Tooltip::for_action_in(
22973 "Restore Hunk",
22974 &::git::Restore,
22975 &focus_handle,
22976 window,
22977 cx,
22978 )
22979 }
22980 })
22981 .on_click({
22982 let editor = editor.clone();
22983 move |_event, window, cx| {
22984 editor.update(cx, |editor, cx| {
22985 let snapshot = editor.snapshot(window, cx);
22986 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22987 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22988 });
22989 }
22990 })
22991 .disabled(is_created_file),
22992 )
22993 .when(
22994 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22995 |el| {
22996 el.child(
22997 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22998 .shape(IconButtonShape::Square)
22999 .icon_size(IconSize::Small)
23000 // .disabled(!has_multiple_hunks)
23001 .tooltip({
23002 let focus_handle = editor.focus_handle(cx);
23003 move |window, cx| {
23004 Tooltip::for_action_in(
23005 "Next Hunk",
23006 &GoToHunk,
23007 &focus_handle,
23008 window,
23009 cx,
23010 )
23011 }
23012 })
23013 .on_click({
23014 let editor = editor.clone();
23015 move |_event, window, cx| {
23016 editor.update(cx, |editor, cx| {
23017 let snapshot = editor.snapshot(window, cx);
23018 let position =
23019 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23020 editor.go_to_hunk_before_or_after_position(
23021 &snapshot,
23022 position,
23023 Direction::Next,
23024 window,
23025 cx,
23026 );
23027 editor.expand_selected_diff_hunks(cx);
23028 });
23029 }
23030 }),
23031 )
23032 .child(
23033 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23034 .shape(IconButtonShape::Square)
23035 .icon_size(IconSize::Small)
23036 // .disabled(!has_multiple_hunks)
23037 .tooltip({
23038 let focus_handle = editor.focus_handle(cx);
23039 move |window, cx| {
23040 Tooltip::for_action_in(
23041 "Previous Hunk",
23042 &GoToPreviousHunk,
23043 &focus_handle,
23044 window,
23045 cx,
23046 )
23047 }
23048 })
23049 .on_click({
23050 let editor = editor.clone();
23051 move |_event, window, cx| {
23052 editor.update(cx, |editor, cx| {
23053 let snapshot = editor.snapshot(window, cx);
23054 let point =
23055 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23056 editor.go_to_hunk_before_or_after_position(
23057 &snapshot,
23058 point,
23059 Direction::Prev,
23060 window,
23061 cx,
23062 );
23063 editor.expand_selected_diff_hunks(cx);
23064 });
23065 }
23066 }),
23067 )
23068 },
23069 )
23070 .into_any_element()
23071}