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(server_id, ..)
1800 | project::Event::LanguageServerRemoved(server_id) => {
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(false, Some(*server_id), 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, 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.excerpts_for_inlay_hints_query(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 excerpts_for_inlay_hints_query(
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 *editor.context_menu.borrow_mut() =
5945 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5946 buffer,
5947 actions: CodeActionContents::new(
5948 resolved_tasks,
5949 code_actions,
5950 debug_scenarios,
5951 task_context.unwrap_or_default(),
5952 ),
5953 selected_item: Default::default(),
5954 scroll_handle: UniformListScrollHandle::default(),
5955 deployed_from,
5956 }));
5957 cx.notify();
5958 if spawn_straight_away {
5959 if let Some(task) = editor.confirm_code_action(
5960 &ConfirmCodeAction { item_ix: Some(0) },
5961 window,
5962 cx,
5963 ) {
5964 return task;
5965 }
5966 }
5967
5968 Task::ready(Ok(()))
5969 })
5970 })
5971 .detach_and_log_err(cx);
5972 }
5973
5974 fn debug_scenarios(
5975 &mut self,
5976 resolved_tasks: &Option<ResolvedTasks>,
5977 buffer: &Entity<Buffer>,
5978 cx: &mut App,
5979 ) -> Task<Vec<task::DebugScenario>> {
5980 maybe!({
5981 let project = self.project.as_ref()?;
5982 let dap_store = project.read(cx).dap_store();
5983 let mut scenarios = vec![];
5984 let resolved_tasks = resolved_tasks.as_ref()?;
5985 let buffer = buffer.read(cx);
5986 let language = buffer.language()?;
5987 let file = buffer.file();
5988 let debug_adapter = language_settings(language.name().into(), file, cx)
5989 .debuggers
5990 .first()
5991 .map(SharedString::from)
5992 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5993
5994 dap_store.update(cx, |dap_store, cx| {
5995 for (_, task) in &resolved_tasks.templates {
5996 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5997 task.original_task().clone(),
5998 debug_adapter.clone().into(),
5999 task.display_label().to_owned().into(),
6000 cx,
6001 );
6002 scenarios.push(maybe_scenario);
6003 }
6004 });
6005 Some(cx.background_spawn(async move {
6006 let scenarios = futures::future::join_all(scenarios)
6007 .await
6008 .into_iter()
6009 .flatten()
6010 .collect::<Vec<_>>();
6011 scenarios
6012 }))
6013 })
6014 .unwrap_or_else(|| Task::ready(vec![]))
6015 }
6016
6017 fn code_actions(
6018 &mut self,
6019 buffer_row: u32,
6020 window: &mut Window,
6021 cx: &mut Context<Self>,
6022 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6023 let mut task = self.code_actions_task.take();
6024 cx.spawn_in(window, async move |editor, cx| {
6025 while let Some(prev_task) = task {
6026 prev_task.await.log_err();
6027 task = editor
6028 .update(cx, |this, _| this.code_actions_task.take())
6029 .ok()?;
6030 }
6031
6032 editor
6033 .update(cx, |editor, cx| {
6034 editor
6035 .available_code_actions
6036 .clone()
6037 .and_then(|(location, code_actions)| {
6038 let snapshot = location.buffer.read(cx).snapshot();
6039 let point_range = location.range.to_point(&snapshot);
6040 let point_range = point_range.start.row..=point_range.end.row;
6041 if point_range.contains(&buffer_row) {
6042 Some(code_actions)
6043 } else {
6044 None
6045 }
6046 })
6047 })
6048 .ok()
6049 .flatten()
6050 })
6051 }
6052
6053 pub fn confirm_code_action(
6054 &mut self,
6055 action: &ConfirmCodeAction,
6056 window: &mut Window,
6057 cx: &mut Context<Self>,
6058 ) -> Option<Task<Result<()>>> {
6059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6060
6061 let actions_menu =
6062 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6063 menu
6064 } else {
6065 return None;
6066 };
6067
6068 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6069 let action = actions_menu.actions.get(action_ix)?;
6070 let title = action.label();
6071 let buffer = actions_menu.buffer;
6072 let workspace = self.workspace()?;
6073
6074 match action {
6075 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6076 workspace.update(cx, |workspace, cx| {
6077 workspace.schedule_resolved_task(
6078 task_source_kind,
6079 resolved_task,
6080 false,
6081 window,
6082 cx,
6083 );
6084
6085 Some(Task::ready(Ok(())))
6086 })
6087 }
6088 CodeActionsItem::CodeAction {
6089 excerpt_id,
6090 action,
6091 provider,
6092 } => {
6093 let apply_code_action =
6094 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6095 let workspace = workspace.downgrade();
6096 Some(cx.spawn_in(window, async move |editor, cx| {
6097 let project_transaction = apply_code_action.await?;
6098 Self::open_project_transaction(
6099 &editor,
6100 workspace,
6101 project_transaction,
6102 title,
6103 cx,
6104 )
6105 .await
6106 }))
6107 }
6108 CodeActionsItem::DebugScenario(scenario) => {
6109 let context = actions_menu.actions.context.clone();
6110
6111 workspace.update(cx, |workspace, cx| {
6112 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6113 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6114 });
6115 Some(Task::ready(Ok(())))
6116 }
6117 }
6118 }
6119
6120 pub async fn open_project_transaction(
6121 this: &WeakEntity<Editor>,
6122 workspace: WeakEntity<Workspace>,
6123 transaction: ProjectTransaction,
6124 title: String,
6125 cx: &mut AsyncWindowContext,
6126 ) -> Result<()> {
6127 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6128 cx.update(|_, cx| {
6129 entries.sort_unstable_by_key(|(buffer, _)| {
6130 buffer.read(cx).file().map(|f| f.path().clone())
6131 });
6132 })?;
6133
6134 // If the project transaction's edits are all contained within this editor, then
6135 // avoid opening a new editor to display them.
6136
6137 if let Some((buffer, transaction)) = entries.first() {
6138 if entries.len() == 1 {
6139 let excerpt = this.update(cx, |editor, cx| {
6140 editor
6141 .buffer()
6142 .read(cx)
6143 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6144 })?;
6145 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6146 if excerpted_buffer == *buffer {
6147 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6148 let excerpt_range = excerpt_range.to_offset(buffer);
6149 buffer
6150 .edited_ranges_for_transaction::<usize>(transaction)
6151 .all(|range| {
6152 excerpt_range.start <= range.start
6153 && excerpt_range.end >= range.end
6154 })
6155 })?;
6156
6157 if all_edits_within_excerpt {
6158 return Ok(());
6159 }
6160 }
6161 }
6162 }
6163 } else {
6164 return Ok(());
6165 }
6166
6167 let mut ranges_to_highlight = Vec::new();
6168 let excerpt_buffer = cx.new(|cx| {
6169 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6170 for (buffer_handle, transaction) in &entries {
6171 let edited_ranges = buffer_handle
6172 .read(cx)
6173 .edited_ranges_for_transaction::<Point>(transaction)
6174 .collect::<Vec<_>>();
6175 let (ranges, _) = multibuffer.set_excerpts_for_path(
6176 PathKey::for_buffer(buffer_handle, cx),
6177 buffer_handle.clone(),
6178 edited_ranges,
6179 DEFAULT_MULTIBUFFER_CONTEXT,
6180 cx,
6181 );
6182
6183 ranges_to_highlight.extend(ranges);
6184 }
6185 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6186 multibuffer
6187 })?;
6188
6189 workspace.update_in(cx, |workspace, window, cx| {
6190 let project = workspace.project().clone();
6191 let editor =
6192 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6193 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6194 editor.update(cx, |editor, cx| {
6195 editor.highlight_background::<Self>(
6196 &ranges_to_highlight,
6197 |theme| theme.colors().editor_highlighted_line_background,
6198 cx,
6199 );
6200 });
6201 })?;
6202
6203 Ok(())
6204 }
6205
6206 pub fn clear_code_action_providers(&mut self) {
6207 self.code_action_providers.clear();
6208 self.available_code_actions.take();
6209 }
6210
6211 pub fn add_code_action_provider(
6212 &mut self,
6213 provider: Rc<dyn CodeActionProvider>,
6214 window: &mut Window,
6215 cx: &mut Context<Self>,
6216 ) {
6217 if self
6218 .code_action_providers
6219 .iter()
6220 .any(|existing_provider| existing_provider.id() == provider.id())
6221 {
6222 return;
6223 }
6224
6225 self.code_action_providers.push(provider);
6226 self.refresh_code_actions(window, cx);
6227 }
6228
6229 pub fn remove_code_action_provider(
6230 &mut self,
6231 id: Arc<str>,
6232 window: &mut Window,
6233 cx: &mut Context<Self>,
6234 ) {
6235 self.code_action_providers
6236 .retain(|provider| provider.id() != id);
6237 self.refresh_code_actions(window, cx);
6238 }
6239
6240 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6241 !self.code_action_providers.is_empty()
6242 && EditorSettings::get_global(cx).toolbar.code_actions
6243 }
6244
6245 pub fn has_available_code_actions(&self) -> bool {
6246 self.available_code_actions
6247 .as_ref()
6248 .is_some_and(|(_, actions)| !actions.is_empty())
6249 }
6250
6251 fn render_inline_code_actions(
6252 &self,
6253 icon_size: ui::IconSize,
6254 display_row: DisplayRow,
6255 is_active: bool,
6256 cx: &mut Context<Self>,
6257 ) -> AnyElement {
6258 let show_tooltip = !self.context_menu_visible();
6259 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6260 .icon_size(icon_size)
6261 .shape(ui::IconButtonShape::Square)
6262 .style(ButtonStyle::Transparent)
6263 .icon_color(ui::Color::Hidden)
6264 .toggle_state(is_active)
6265 .when(show_tooltip, |this| {
6266 this.tooltip({
6267 let focus_handle = self.focus_handle.clone();
6268 move |window, cx| {
6269 Tooltip::for_action_in(
6270 "Toggle Code Actions",
6271 &ToggleCodeActions {
6272 deployed_from: None,
6273 quick_launch: false,
6274 },
6275 &focus_handle,
6276 window,
6277 cx,
6278 )
6279 }
6280 })
6281 })
6282 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6283 window.focus(&editor.focus_handle(cx));
6284 editor.toggle_code_actions(
6285 &crate::actions::ToggleCodeActions {
6286 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6287 display_row,
6288 )),
6289 quick_launch: false,
6290 },
6291 window,
6292 cx,
6293 );
6294 }))
6295 .into_any_element()
6296 }
6297
6298 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6299 &self.context_menu
6300 }
6301
6302 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6303 let newest_selection = self.selections.newest_anchor().clone();
6304 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6305 let buffer = self.buffer.read(cx);
6306 if newest_selection.head().diff_base_anchor.is_some() {
6307 return None;
6308 }
6309 let (start_buffer, start) =
6310 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6311 let (end_buffer, end) =
6312 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6313 if start_buffer != end_buffer {
6314 return None;
6315 }
6316
6317 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6318 cx.background_executor()
6319 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6320 .await;
6321
6322 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6323 let providers = this.code_action_providers.clone();
6324 let tasks = this
6325 .code_action_providers
6326 .iter()
6327 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6328 .collect::<Vec<_>>();
6329 (providers, tasks)
6330 })?;
6331
6332 let mut actions = Vec::new();
6333 for (provider, provider_actions) in
6334 providers.into_iter().zip(future::join_all(tasks).await)
6335 {
6336 if let Some(provider_actions) = provider_actions.log_err() {
6337 actions.extend(provider_actions.into_iter().map(|action| {
6338 AvailableCodeAction {
6339 excerpt_id: newest_selection.start.excerpt_id,
6340 action,
6341 provider: provider.clone(),
6342 }
6343 }));
6344 }
6345 }
6346
6347 this.update(cx, |this, cx| {
6348 this.available_code_actions = if actions.is_empty() {
6349 None
6350 } else {
6351 Some((
6352 Location {
6353 buffer: start_buffer,
6354 range: start..end,
6355 },
6356 actions.into(),
6357 ))
6358 };
6359 cx.notify();
6360 })
6361 }));
6362 None
6363 }
6364
6365 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6366 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6367 self.show_git_blame_inline = false;
6368
6369 self.show_git_blame_inline_delay_task =
6370 Some(cx.spawn_in(window, async move |this, cx| {
6371 cx.background_executor().timer(delay).await;
6372
6373 this.update(cx, |this, cx| {
6374 this.show_git_blame_inline = true;
6375 cx.notify();
6376 })
6377 .log_err();
6378 }));
6379 }
6380 }
6381
6382 fn show_blame_popover(
6383 &mut self,
6384 blame_entry: &BlameEntry,
6385 position: gpui::Point<Pixels>,
6386 cx: &mut Context<Self>,
6387 ) {
6388 if let Some(state) = &mut self.inline_blame_popover {
6389 state.hide_task.take();
6390 } else {
6391 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6392 let blame_entry = blame_entry.clone();
6393 let show_task = cx.spawn(async move |editor, cx| {
6394 cx.background_executor()
6395 .timer(std::time::Duration::from_millis(delay))
6396 .await;
6397 editor
6398 .update(cx, |editor, cx| {
6399 editor.inline_blame_popover_show_task.take();
6400 let Some(blame) = editor.blame.as_ref() else {
6401 return;
6402 };
6403 let blame = blame.read(cx);
6404 let details = blame.details_for_entry(&blame_entry);
6405 let markdown = cx.new(|cx| {
6406 Markdown::new(
6407 details
6408 .as_ref()
6409 .map(|message| message.message.clone())
6410 .unwrap_or_default(),
6411 None,
6412 None,
6413 cx,
6414 )
6415 });
6416 editor.inline_blame_popover = Some(InlineBlamePopover {
6417 position,
6418 hide_task: None,
6419 popover_bounds: None,
6420 popover_state: InlineBlamePopoverState {
6421 scroll_handle: ScrollHandle::new(),
6422 commit_message: details,
6423 markdown,
6424 },
6425 });
6426 cx.notify();
6427 })
6428 .ok();
6429 });
6430 self.inline_blame_popover_show_task = Some(show_task);
6431 }
6432 }
6433
6434 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6435 self.inline_blame_popover_show_task.take();
6436 if let Some(state) = &mut self.inline_blame_popover {
6437 let hide_task = cx.spawn(async move |editor, cx| {
6438 cx.background_executor()
6439 .timer(std::time::Duration::from_millis(100))
6440 .await;
6441 editor
6442 .update(cx, |editor, cx| {
6443 editor.inline_blame_popover.take();
6444 cx.notify();
6445 })
6446 .ok();
6447 });
6448 state.hide_task = Some(hide_task);
6449 }
6450 }
6451
6452 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6453 if self.pending_rename.is_some() {
6454 return None;
6455 }
6456
6457 let provider = self.semantics_provider.clone()?;
6458 let buffer = self.buffer.read(cx);
6459 let newest_selection = self.selections.newest_anchor().clone();
6460 let cursor_position = newest_selection.head();
6461 let (cursor_buffer, cursor_buffer_position) =
6462 buffer.text_anchor_for_position(cursor_position, cx)?;
6463 let (tail_buffer, tail_buffer_position) =
6464 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6465 if cursor_buffer != tail_buffer {
6466 return None;
6467 }
6468
6469 let snapshot = cursor_buffer.read(cx).snapshot();
6470 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6471 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6472 if start_word_range != end_word_range {
6473 self.document_highlights_task.take();
6474 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6475 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6476 return None;
6477 }
6478
6479 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6480 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6481 cx.background_executor()
6482 .timer(Duration::from_millis(debounce))
6483 .await;
6484
6485 let highlights = if let Some(highlights) = cx
6486 .update(|cx| {
6487 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6488 })
6489 .ok()
6490 .flatten()
6491 {
6492 highlights.await.log_err()
6493 } else {
6494 None
6495 };
6496
6497 if let Some(highlights) = highlights {
6498 this.update(cx, |this, cx| {
6499 if this.pending_rename.is_some() {
6500 return;
6501 }
6502
6503 let buffer_id = cursor_position.buffer_id;
6504 let buffer = this.buffer.read(cx);
6505 if !buffer
6506 .text_anchor_for_position(cursor_position, cx)
6507 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6508 {
6509 return;
6510 }
6511
6512 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6513 let mut write_ranges = Vec::new();
6514 let mut read_ranges = Vec::new();
6515 for highlight in highlights {
6516 for (excerpt_id, excerpt_range) in
6517 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6518 {
6519 let start = highlight
6520 .range
6521 .start
6522 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6523 let end = highlight
6524 .range
6525 .end
6526 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6527 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6528 continue;
6529 }
6530
6531 let range = Anchor {
6532 buffer_id,
6533 excerpt_id,
6534 text_anchor: start,
6535 diff_base_anchor: None,
6536 }..Anchor {
6537 buffer_id,
6538 excerpt_id,
6539 text_anchor: end,
6540 diff_base_anchor: None,
6541 };
6542 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6543 write_ranges.push(range);
6544 } else {
6545 read_ranges.push(range);
6546 }
6547 }
6548 }
6549
6550 this.highlight_background::<DocumentHighlightRead>(
6551 &read_ranges,
6552 |theme| theme.colors().editor_document_highlight_read_background,
6553 cx,
6554 );
6555 this.highlight_background::<DocumentHighlightWrite>(
6556 &write_ranges,
6557 |theme| theme.colors().editor_document_highlight_write_background,
6558 cx,
6559 );
6560 cx.notify();
6561 })
6562 .log_err();
6563 }
6564 }));
6565 None
6566 }
6567
6568 fn prepare_highlight_query_from_selection(
6569 &mut self,
6570 cx: &mut Context<Editor>,
6571 ) -> Option<(String, Range<Anchor>)> {
6572 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6573 return None;
6574 }
6575 if !EditorSettings::get_global(cx).selection_highlight {
6576 return None;
6577 }
6578 if self.selections.count() != 1 || self.selections.line_mode {
6579 return None;
6580 }
6581 let selection = self.selections.newest::<Point>(cx);
6582 if selection.is_empty() || selection.start.row != selection.end.row {
6583 return None;
6584 }
6585 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6586 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6587 let query = multi_buffer_snapshot
6588 .text_for_range(selection_anchor_range.clone())
6589 .collect::<String>();
6590 if query.trim().is_empty() {
6591 return None;
6592 }
6593 Some((query, selection_anchor_range))
6594 }
6595
6596 fn update_selection_occurrence_highlights(
6597 &mut self,
6598 query_text: String,
6599 query_range: Range<Anchor>,
6600 multi_buffer_range_to_query: Range<Point>,
6601 use_debounce: bool,
6602 window: &mut Window,
6603 cx: &mut Context<Editor>,
6604 ) -> Task<()> {
6605 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6606 cx.spawn_in(window, async move |editor, cx| {
6607 if use_debounce {
6608 cx.background_executor()
6609 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6610 .await;
6611 }
6612 let match_task = cx.background_spawn(async move {
6613 let buffer_ranges = multi_buffer_snapshot
6614 .range_to_buffer_ranges(multi_buffer_range_to_query)
6615 .into_iter()
6616 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6617 let mut match_ranges = Vec::new();
6618 let Ok(regex) = project::search::SearchQuery::text(
6619 query_text.clone(),
6620 false,
6621 false,
6622 false,
6623 Default::default(),
6624 Default::default(),
6625 false,
6626 None,
6627 ) else {
6628 return Vec::default();
6629 };
6630 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6631 match_ranges.extend(
6632 regex
6633 .search(&buffer_snapshot, Some(search_range.clone()))
6634 .await
6635 .into_iter()
6636 .filter_map(|match_range| {
6637 let match_start = buffer_snapshot
6638 .anchor_after(search_range.start + match_range.start);
6639 let match_end = buffer_snapshot
6640 .anchor_before(search_range.start + match_range.end);
6641 let match_anchor_range = Anchor::range_in_buffer(
6642 excerpt_id,
6643 buffer_snapshot.remote_id(),
6644 match_start..match_end,
6645 );
6646 (match_anchor_range != query_range).then_some(match_anchor_range)
6647 }),
6648 );
6649 }
6650 match_ranges
6651 });
6652 let match_ranges = match_task.await;
6653 editor
6654 .update_in(cx, |editor, _, cx| {
6655 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6656 if !match_ranges.is_empty() {
6657 editor.highlight_background::<SelectedTextHighlight>(
6658 &match_ranges,
6659 |theme| theme.colors().editor_document_highlight_bracket_background,
6660 cx,
6661 )
6662 }
6663 })
6664 .log_err();
6665 })
6666 }
6667
6668 fn refresh_selected_text_highlights(
6669 &mut self,
6670 on_buffer_edit: bool,
6671 window: &mut Window,
6672 cx: &mut Context<Editor>,
6673 ) {
6674 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6675 else {
6676 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6677 self.quick_selection_highlight_task.take();
6678 self.debounced_selection_highlight_task.take();
6679 return;
6680 };
6681 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6682 if on_buffer_edit
6683 || self
6684 .quick_selection_highlight_task
6685 .as_ref()
6686 .map_or(true, |(prev_anchor_range, _)| {
6687 prev_anchor_range != &query_range
6688 })
6689 {
6690 let multi_buffer_visible_start = self
6691 .scroll_manager
6692 .anchor()
6693 .anchor
6694 .to_point(&multi_buffer_snapshot);
6695 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6696 multi_buffer_visible_start
6697 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6698 Bias::Left,
6699 );
6700 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6701 self.quick_selection_highlight_task = Some((
6702 query_range.clone(),
6703 self.update_selection_occurrence_highlights(
6704 query_text.clone(),
6705 query_range.clone(),
6706 multi_buffer_visible_range,
6707 false,
6708 window,
6709 cx,
6710 ),
6711 ));
6712 }
6713 if on_buffer_edit
6714 || self
6715 .debounced_selection_highlight_task
6716 .as_ref()
6717 .map_or(true, |(prev_anchor_range, _)| {
6718 prev_anchor_range != &query_range
6719 })
6720 {
6721 let multi_buffer_start = multi_buffer_snapshot
6722 .anchor_before(0)
6723 .to_point(&multi_buffer_snapshot);
6724 let multi_buffer_end = multi_buffer_snapshot
6725 .anchor_after(multi_buffer_snapshot.len())
6726 .to_point(&multi_buffer_snapshot);
6727 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6728 self.debounced_selection_highlight_task = Some((
6729 query_range.clone(),
6730 self.update_selection_occurrence_highlights(
6731 query_text,
6732 query_range,
6733 multi_buffer_full_range,
6734 true,
6735 window,
6736 cx,
6737 ),
6738 ));
6739 }
6740 }
6741
6742 pub fn refresh_inline_completion(
6743 &mut self,
6744 debounce: bool,
6745 user_requested: bool,
6746 window: &mut Window,
6747 cx: &mut Context<Self>,
6748 ) -> Option<()> {
6749 let provider = self.edit_prediction_provider()?;
6750 let cursor = self.selections.newest_anchor().head();
6751 let (buffer, cursor_buffer_position) =
6752 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6753
6754 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6755 self.discard_inline_completion(false, cx);
6756 return None;
6757 }
6758
6759 if !user_requested
6760 && (!self.should_show_edit_predictions()
6761 || !self.is_focused(window)
6762 || buffer.read(cx).is_empty())
6763 {
6764 self.discard_inline_completion(false, cx);
6765 return None;
6766 }
6767
6768 self.update_visible_inline_completion(window, cx);
6769 provider.refresh(
6770 self.project.clone(),
6771 buffer,
6772 cursor_buffer_position,
6773 debounce,
6774 cx,
6775 );
6776 Some(())
6777 }
6778
6779 fn show_edit_predictions_in_menu(&self) -> bool {
6780 match self.edit_prediction_settings {
6781 EditPredictionSettings::Disabled => false,
6782 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6783 }
6784 }
6785
6786 pub fn edit_predictions_enabled(&self) -> bool {
6787 match self.edit_prediction_settings {
6788 EditPredictionSettings::Disabled => false,
6789 EditPredictionSettings::Enabled { .. } => true,
6790 }
6791 }
6792
6793 fn edit_prediction_requires_modifier(&self) -> bool {
6794 match self.edit_prediction_settings {
6795 EditPredictionSettings::Disabled => false,
6796 EditPredictionSettings::Enabled {
6797 preview_requires_modifier,
6798 ..
6799 } => preview_requires_modifier,
6800 }
6801 }
6802
6803 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6804 if self.edit_prediction_provider.is_none() {
6805 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6806 } else {
6807 let selection = self.selections.newest_anchor();
6808 let cursor = selection.head();
6809
6810 if let Some((buffer, cursor_buffer_position)) =
6811 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6812 {
6813 self.edit_prediction_settings =
6814 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6815 }
6816 }
6817 }
6818
6819 fn edit_prediction_settings_at_position(
6820 &self,
6821 buffer: &Entity<Buffer>,
6822 buffer_position: language::Anchor,
6823 cx: &App,
6824 ) -> EditPredictionSettings {
6825 if !self.mode.is_full()
6826 || !self.show_inline_completions_override.unwrap_or(true)
6827 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6828 {
6829 return EditPredictionSettings::Disabled;
6830 }
6831
6832 let buffer = buffer.read(cx);
6833
6834 let file = buffer.file();
6835
6836 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6837 return EditPredictionSettings::Disabled;
6838 };
6839
6840 let by_provider = matches!(
6841 self.menu_inline_completions_policy,
6842 MenuInlineCompletionsPolicy::ByProvider
6843 );
6844
6845 let show_in_menu = by_provider
6846 && self
6847 .edit_prediction_provider
6848 .as_ref()
6849 .map_or(false, |provider| {
6850 provider.provider.show_completions_in_menu()
6851 });
6852
6853 let preview_requires_modifier =
6854 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6855
6856 EditPredictionSettings::Enabled {
6857 show_in_menu,
6858 preview_requires_modifier,
6859 }
6860 }
6861
6862 fn should_show_edit_predictions(&self) -> bool {
6863 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6864 }
6865
6866 pub fn edit_prediction_preview_is_active(&self) -> bool {
6867 matches!(
6868 self.edit_prediction_preview,
6869 EditPredictionPreview::Active { .. }
6870 )
6871 }
6872
6873 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6874 let cursor = self.selections.newest_anchor().head();
6875 if let Some((buffer, cursor_position)) =
6876 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6877 {
6878 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6879 } else {
6880 false
6881 }
6882 }
6883
6884 pub fn supports_minimap(&self, cx: &App) -> bool {
6885 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6886 }
6887
6888 fn edit_predictions_enabled_in_buffer(
6889 &self,
6890 buffer: &Entity<Buffer>,
6891 buffer_position: language::Anchor,
6892 cx: &App,
6893 ) -> bool {
6894 maybe!({
6895 if self.read_only(cx) {
6896 return Some(false);
6897 }
6898 let provider = self.edit_prediction_provider()?;
6899 if !provider.is_enabled(&buffer, buffer_position, cx) {
6900 return Some(false);
6901 }
6902 let buffer = buffer.read(cx);
6903 let Some(file) = buffer.file() else {
6904 return Some(true);
6905 };
6906 let settings = all_language_settings(Some(file), cx);
6907 Some(settings.edit_predictions_enabled_for_file(file, cx))
6908 })
6909 .unwrap_or(false)
6910 }
6911
6912 fn cycle_inline_completion(
6913 &mut self,
6914 direction: Direction,
6915 window: &mut Window,
6916 cx: &mut Context<Self>,
6917 ) -> Option<()> {
6918 let provider = self.edit_prediction_provider()?;
6919 let cursor = self.selections.newest_anchor().head();
6920 let (buffer, cursor_buffer_position) =
6921 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6922 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6923 return None;
6924 }
6925
6926 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6927 self.update_visible_inline_completion(window, cx);
6928
6929 Some(())
6930 }
6931
6932 pub fn show_inline_completion(
6933 &mut self,
6934 _: &ShowEditPrediction,
6935 window: &mut Window,
6936 cx: &mut Context<Self>,
6937 ) {
6938 if !self.has_active_inline_completion() {
6939 self.refresh_inline_completion(false, true, window, cx);
6940 return;
6941 }
6942
6943 self.update_visible_inline_completion(window, cx);
6944 }
6945
6946 pub fn display_cursor_names(
6947 &mut self,
6948 _: &DisplayCursorNames,
6949 window: &mut Window,
6950 cx: &mut Context<Self>,
6951 ) {
6952 self.show_cursor_names(window, cx);
6953 }
6954
6955 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6956 self.show_cursor_names = true;
6957 cx.notify();
6958 cx.spawn_in(window, async move |this, cx| {
6959 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6960 this.update(cx, |this, cx| {
6961 this.show_cursor_names = false;
6962 cx.notify()
6963 })
6964 .ok()
6965 })
6966 .detach();
6967 }
6968
6969 pub fn next_edit_prediction(
6970 &mut self,
6971 _: &NextEditPrediction,
6972 window: &mut Window,
6973 cx: &mut Context<Self>,
6974 ) {
6975 if self.has_active_inline_completion() {
6976 self.cycle_inline_completion(Direction::Next, window, cx);
6977 } else {
6978 let is_copilot_disabled = self
6979 .refresh_inline_completion(false, true, window, cx)
6980 .is_none();
6981 if is_copilot_disabled {
6982 cx.propagate();
6983 }
6984 }
6985 }
6986
6987 pub fn previous_edit_prediction(
6988 &mut self,
6989 _: &PreviousEditPrediction,
6990 window: &mut Window,
6991 cx: &mut Context<Self>,
6992 ) {
6993 if self.has_active_inline_completion() {
6994 self.cycle_inline_completion(Direction::Prev, window, cx);
6995 } else {
6996 let is_copilot_disabled = self
6997 .refresh_inline_completion(false, true, window, cx)
6998 .is_none();
6999 if is_copilot_disabled {
7000 cx.propagate();
7001 }
7002 }
7003 }
7004
7005 pub fn accept_edit_prediction(
7006 &mut self,
7007 _: &AcceptEditPrediction,
7008 window: &mut Window,
7009 cx: &mut Context<Self>,
7010 ) {
7011 if self.show_edit_predictions_in_menu() {
7012 self.hide_context_menu(window, cx);
7013 }
7014
7015 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7016 return;
7017 };
7018
7019 self.report_inline_completion_event(
7020 active_inline_completion.completion_id.clone(),
7021 true,
7022 cx,
7023 );
7024
7025 match &active_inline_completion.completion {
7026 InlineCompletion::Move { target, .. } => {
7027 let target = *target;
7028
7029 if let Some(position_map) = &self.last_position_map {
7030 if position_map
7031 .visible_row_range
7032 .contains(&target.to_display_point(&position_map.snapshot).row())
7033 || !self.edit_prediction_requires_modifier()
7034 {
7035 self.unfold_ranges(&[target..target], true, false, cx);
7036 // Note that this is also done in vim's handler of the Tab action.
7037 self.change_selections(
7038 Some(Autoscroll::newest()),
7039 window,
7040 cx,
7041 |selections| {
7042 selections.select_anchor_ranges([target..target]);
7043 },
7044 );
7045 self.clear_row_highlights::<EditPredictionPreview>();
7046
7047 self.edit_prediction_preview
7048 .set_previous_scroll_position(None);
7049 } else {
7050 self.edit_prediction_preview
7051 .set_previous_scroll_position(Some(
7052 position_map.snapshot.scroll_anchor,
7053 ));
7054
7055 self.highlight_rows::<EditPredictionPreview>(
7056 target..target,
7057 cx.theme().colors().editor_highlighted_line_background,
7058 RowHighlightOptions {
7059 autoscroll: true,
7060 ..Default::default()
7061 },
7062 cx,
7063 );
7064 self.request_autoscroll(Autoscroll::fit(), cx);
7065 }
7066 }
7067 }
7068 InlineCompletion::Edit { edits, .. } => {
7069 if let Some(provider) = self.edit_prediction_provider() {
7070 provider.accept(cx);
7071 }
7072
7073 // Store the transaction ID and selections before applying the edit
7074 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7075
7076 let snapshot = self.buffer.read(cx).snapshot(cx);
7077 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7078
7079 self.buffer.update(cx, |buffer, cx| {
7080 buffer.edit(edits.iter().cloned(), None, cx)
7081 });
7082
7083 self.change_selections(None, window, cx, |s| {
7084 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7085 });
7086
7087 let selections = self.selections.disjoint_anchors();
7088 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7089 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7090 if has_new_transaction {
7091 self.selection_history
7092 .insert_transaction(transaction_id_now, selections);
7093 }
7094 }
7095
7096 self.update_visible_inline_completion(window, cx);
7097 if self.active_inline_completion.is_none() {
7098 self.refresh_inline_completion(true, true, window, cx);
7099 }
7100
7101 cx.notify();
7102 }
7103 }
7104
7105 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7106 }
7107
7108 pub fn accept_partial_inline_completion(
7109 &mut self,
7110 _: &AcceptPartialEditPrediction,
7111 window: &mut Window,
7112 cx: &mut Context<Self>,
7113 ) {
7114 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7115 return;
7116 };
7117 if self.selections.count() != 1 {
7118 return;
7119 }
7120
7121 self.report_inline_completion_event(
7122 active_inline_completion.completion_id.clone(),
7123 true,
7124 cx,
7125 );
7126
7127 match &active_inline_completion.completion {
7128 InlineCompletion::Move { target, .. } => {
7129 let target = *target;
7130 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7131 selections.select_anchor_ranges([target..target]);
7132 });
7133 }
7134 InlineCompletion::Edit { edits, .. } => {
7135 // Find an insertion that starts at the cursor position.
7136 let snapshot = self.buffer.read(cx).snapshot(cx);
7137 let cursor_offset = self.selections.newest::<usize>(cx).head();
7138 let insertion = edits.iter().find_map(|(range, text)| {
7139 let range = range.to_offset(&snapshot);
7140 if range.is_empty() && range.start == cursor_offset {
7141 Some(text)
7142 } else {
7143 None
7144 }
7145 });
7146
7147 if let Some(text) = insertion {
7148 let mut partial_completion = text
7149 .chars()
7150 .by_ref()
7151 .take_while(|c| c.is_alphabetic())
7152 .collect::<String>();
7153 if partial_completion.is_empty() {
7154 partial_completion = text
7155 .chars()
7156 .by_ref()
7157 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7158 .collect::<String>();
7159 }
7160
7161 cx.emit(EditorEvent::InputHandled {
7162 utf16_range_to_replace: None,
7163 text: partial_completion.clone().into(),
7164 });
7165
7166 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7167
7168 self.refresh_inline_completion(true, true, window, cx);
7169 cx.notify();
7170 } else {
7171 self.accept_edit_prediction(&Default::default(), window, cx);
7172 }
7173 }
7174 }
7175 }
7176
7177 fn discard_inline_completion(
7178 &mut self,
7179 should_report_inline_completion_event: bool,
7180 cx: &mut Context<Self>,
7181 ) -> bool {
7182 if should_report_inline_completion_event {
7183 let completion_id = self
7184 .active_inline_completion
7185 .as_ref()
7186 .and_then(|active_completion| active_completion.completion_id.clone());
7187
7188 self.report_inline_completion_event(completion_id, false, cx);
7189 }
7190
7191 if let Some(provider) = self.edit_prediction_provider() {
7192 provider.discard(cx);
7193 }
7194
7195 self.take_active_inline_completion(cx)
7196 }
7197
7198 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7199 let Some(provider) = self.edit_prediction_provider() else {
7200 return;
7201 };
7202
7203 let Some((_, buffer, _)) = self
7204 .buffer
7205 .read(cx)
7206 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7207 else {
7208 return;
7209 };
7210
7211 let extension = buffer
7212 .read(cx)
7213 .file()
7214 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7215
7216 let event_type = match accepted {
7217 true => "Edit Prediction Accepted",
7218 false => "Edit Prediction Discarded",
7219 };
7220 telemetry::event!(
7221 event_type,
7222 provider = provider.name(),
7223 prediction_id = id,
7224 suggestion_accepted = accepted,
7225 file_extension = extension,
7226 );
7227 }
7228
7229 pub fn has_active_inline_completion(&self) -> bool {
7230 self.active_inline_completion.is_some()
7231 }
7232
7233 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7234 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7235 return false;
7236 };
7237
7238 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7239 self.clear_highlights::<InlineCompletionHighlight>(cx);
7240 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7241 true
7242 }
7243
7244 /// Returns true when we're displaying the edit prediction popover below the cursor
7245 /// like we are not previewing and the LSP autocomplete menu is visible
7246 /// or we are in `when_holding_modifier` mode.
7247 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7248 if self.edit_prediction_preview_is_active()
7249 || !self.show_edit_predictions_in_menu()
7250 || !self.edit_predictions_enabled()
7251 {
7252 return false;
7253 }
7254
7255 if self.has_visible_completions_menu() {
7256 return true;
7257 }
7258
7259 has_completion && self.edit_prediction_requires_modifier()
7260 }
7261
7262 fn handle_modifiers_changed(
7263 &mut self,
7264 modifiers: Modifiers,
7265 position_map: &PositionMap,
7266 window: &mut Window,
7267 cx: &mut Context<Self>,
7268 ) {
7269 if self.show_edit_predictions_in_menu() {
7270 self.update_edit_prediction_preview(&modifiers, window, cx);
7271 }
7272
7273 self.update_selection_mode(&modifiers, position_map, window, cx);
7274
7275 let mouse_position = window.mouse_position();
7276 if !position_map.text_hitbox.is_hovered(window) {
7277 return;
7278 }
7279
7280 self.update_hovered_link(
7281 position_map.point_for_position(mouse_position),
7282 &position_map.snapshot,
7283 modifiers,
7284 window,
7285 cx,
7286 )
7287 }
7288
7289 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7290 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7291 if invert {
7292 match multi_cursor_setting {
7293 MultiCursorModifier::Alt => modifiers.alt,
7294 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7295 }
7296 } else {
7297 match multi_cursor_setting {
7298 MultiCursorModifier::Alt => modifiers.secondary(),
7299 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7300 }
7301 }
7302 }
7303
7304 fn columnar_selection_mode(
7305 modifiers: &Modifiers,
7306 cx: &mut Context<Self>,
7307 ) -> Option<ColumnarMode> {
7308 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7309 if Self::multi_cursor_modifier(false, modifiers, cx) {
7310 Some(ColumnarMode::FromMouse)
7311 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7312 Some(ColumnarMode::FromSelection)
7313 } else {
7314 None
7315 }
7316 } else {
7317 None
7318 }
7319 }
7320
7321 fn update_selection_mode(
7322 &mut self,
7323 modifiers: &Modifiers,
7324 position_map: &PositionMap,
7325 window: &mut Window,
7326 cx: &mut Context<Self>,
7327 ) {
7328 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7329 return;
7330 };
7331 if self.selections.pending.is_none() {
7332 return;
7333 }
7334
7335 let mouse_position = window.mouse_position();
7336 let point_for_position = position_map.point_for_position(mouse_position);
7337 let position = point_for_position.previous_valid;
7338
7339 self.select(
7340 SelectPhase::BeginColumnar {
7341 position,
7342 reset: false,
7343 mode,
7344 goal_column: point_for_position.exact_unclipped.column(),
7345 },
7346 window,
7347 cx,
7348 );
7349 }
7350
7351 fn update_edit_prediction_preview(
7352 &mut self,
7353 modifiers: &Modifiers,
7354 window: &mut Window,
7355 cx: &mut Context<Self>,
7356 ) {
7357 let mut modifiers_held = false;
7358 if let Some(accept_keystroke) = self
7359 .accept_edit_prediction_keybind(false, window, cx)
7360 .keystroke()
7361 {
7362 modifiers_held = modifiers_held
7363 || (&accept_keystroke.modifiers == modifiers
7364 && accept_keystroke.modifiers.modified());
7365 };
7366 if let Some(accept_partial_keystroke) = self
7367 .accept_edit_prediction_keybind(true, window, cx)
7368 .keystroke()
7369 {
7370 modifiers_held = modifiers_held
7371 || (&accept_partial_keystroke.modifiers == modifiers
7372 && accept_partial_keystroke.modifiers.modified());
7373 }
7374
7375 if modifiers_held {
7376 if matches!(
7377 self.edit_prediction_preview,
7378 EditPredictionPreview::Inactive { .. }
7379 ) {
7380 self.edit_prediction_preview = EditPredictionPreview::Active {
7381 previous_scroll_position: None,
7382 since: Instant::now(),
7383 };
7384
7385 self.update_visible_inline_completion(window, cx);
7386 cx.notify();
7387 }
7388 } else if let EditPredictionPreview::Active {
7389 previous_scroll_position,
7390 since,
7391 } = self.edit_prediction_preview
7392 {
7393 if let (Some(previous_scroll_position), Some(position_map)) =
7394 (previous_scroll_position, self.last_position_map.as_ref())
7395 {
7396 self.set_scroll_position(
7397 previous_scroll_position
7398 .scroll_position(&position_map.snapshot.display_snapshot),
7399 window,
7400 cx,
7401 );
7402 }
7403
7404 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7405 released_too_fast: since.elapsed() < Duration::from_millis(200),
7406 };
7407 self.clear_row_highlights::<EditPredictionPreview>();
7408 self.update_visible_inline_completion(window, cx);
7409 cx.notify();
7410 }
7411 }
7412
7413 fn update_visible_inline_completion(
7414 &mut self,
7415 _window: &mut Window,
7416 cx: &mut Context<Self>,
7417 ) -> Option<()> {
7418 let selection = self.selections.newest_anchor();
7419 let cursor = selection.head();
7420 let multibuffer = self.buffer.read(cx).snapshot(cx);
7421 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7422 let excerpt_id = cursor.excerpt_id;
7423
7424 let show_in_menu = self.show_edit_predictions_in_menu();
7425 let completions_menu_has_precedence = !show_in_menu
7426 && (self.context_menu.borrow().is_some()
7427 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7428
7429 if completions_menu_has_precedence
7430 || !offset_selection.is_empty()
7431 || self
7432 .active_inline_completion
7433 .as_ref()
7434 .map_or(false, |completion| {
7435 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7436 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7437 !invalidation_range.contains(&offset_selection.head())
7438 })
7439 {
7440 self.discard_inline_completion(false, cx);
7441 return None;
7442 }
7443
7444 self.take_active_inline_completion(cx);
7445 let Some(provider) = self.edit_prediction_provider() else {
7446 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7447 return None;
7448 };
7449
7450 let (buffer, cursor_buffer_position) =
7451 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7452
7453 self.edit_prediction_settings =
7454 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7455
7456 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7457
7458 if self.edit_prediction_indent_conflict {
7459 let cursor_point = cursor.to_point(&multibuffer);
7460
7461 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7462
7463 if let Some((_, indent)) = indents.iter().next() {
7464 if indent.len == cursor_point.column {
7465 self.edit_prediction_indent_conflict = false;
7466 }
7467 }
7468 }
7469
7470 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7471 let edits = inline_completion
7472 .edits
7473 .into_iter()
7474 .flat_map(|(range, new_text)| {
7475 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7476 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7477 Some((start..end, new_text))
7478 })
7479 .collect::<Vec<_>>();
7480 if edits.is_empty() {
7481 return None;
7482 }
7483
7484 let first_edit_start = edits.first().unwrap().0.start;
7485 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7486 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7487
7488 let last_edit_end = edits.last().unwrap().0.end;
7489 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7490 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7491
7492 let cursor_row = cursor.to_point(&multibuffer).row;
7493
7494 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7495
7496 let mut inlay_ids = Vec::new();
7497 let invalidation_row_range;
7498 let move_invalidation_row_range = if cursor_row < edit_start_row {
7499 Some(cursor_row..edit_end_row)
7500 } else if cursor_row > edit_end_row {
7501 Some(edit_start_row..cursor_row)
7502 } else {
7503 None
7504 };
7505 let is_move =
7506 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7507 let completion = if is_move {
7508 invalidation_row_range =
7509 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7510 let target = first_edit_start;
7511 InlineCompletion::Move { target, snapshot }
7512 } else {
7513 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7514 && !self.inline_completions_hidden_for_vim_mode;
7515
7516 if show_completions_in_buffer {
7517 if edits
7518 .iter()
7519 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7520 {
7521 let mut inlays = Vec::new();
7522 for (range, new_text) in &edits {
7523 let inlay = Inlay::inline_completion(
7524 post_inc(&mut self.next_inlay_id),
7525 range.start,
7526 new_text.as_str(),
7527 );
7528 inlay_ids.push(inlay.id);
7529 inlays.push(inlay);
7530 }
7531
7532 self.splice_inlays(&[], inlays, cx);
7533 } else {
7534 let background_color = cx.theme().status().deleted_background;
7535 self.highlight_text::<InlineCompletionHighlight>(
7536 edits.iter().map(|(range, _)| range.clone()).collect(),
7537 HighlightStyle {
7538 background_color: Some(background_color),
7539 ..Default::default()
7540 },
7541 cx,
7542 );
7543 }
7544 }
7545
7546 invalidation_row_range = edit_start_row..edit_end_row;
7547
7548 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7549 if provider.show_tab_accept_marker() {
7550 EditDisplayMode::TabAccept
7551 } else {
7552 EditDisplayMode::Inline
7553 }
7554 } else {
7555 EditDisplayMode::DiffPopover
7556 };
7557
7558 InlineCompletion::Edit {
7559 edits,
7560 edit_preview: inline_completion.edit_preview,
7561 display_mode,
7562 snapshot,
7563 }
7564 };
7565
7566 let invalidation_range = multibuffer
7567 .anchor_before(Point::new(invalidation_row_range.start, 0))
7568 ..multibuffer.anchor_after(Point::new(
7569 invalidation_row_range.end,
7570 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7571 ));
7572
7573 self.stale_inline_completion_in_menu = None;
7574 self.active_inline_completion = Some(InlineCompletionState {
7575 inlay_ids,
7576 completion,
7577 completion_id: inline_completion.id,
7578 invalidation_range,
7579 });
7580
7581 cx.notify();
7582
7583 Some(())
7584 }
7585
7586 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7587 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7588 }
7589
7590 fn clear_tasks(&mut self) {
7591 self.tasks.clear()
7592 }
7593
7594 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7595 if self.tasks.insert(key, value).is_some() {
7596 // This case should hopefully be rare, but just in case...
7597 log::error!(
7598 "multiple different run targets found on a single line, only the last target will be rendered"
7599 )
7600 }
7601 }
7602
7603 /// Get all display points of breakpoints that will be rendered within editor
7604 ///
7605 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7606 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7607 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7608 fn active_breakpoints(
7609 &self,
7610 range: Range<DisplayRow>,
7611 window: &mut Window,
7612 cx: &mut Context<Self>,
7613 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7614 let mut breakpoint_display_points = HashMap::default();
7615
7616 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7617 return breakpoint_display_points;
7618 };
7619
7620 let snapshot = self.snapshot(window, cx);
7621
7622 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7623 let Some(project) = self.project.as_ref() else {
7624 return breakpoint_display_points;
7625 };
7626
7627 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7628 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7629
7630 for (buffer_snapshot, range, excerpt_id) in
7631 multi_buffer_snapshot.range_to_buffer_ranges(range)
7632 {
7633 let Some(buffer) = project
7634 .read(cx)
7635 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7636 else {
7637 continue;
7638 };
7639 let breakpoints = breakpoint_store.read(cx).breakpoints(
7640 &buffer,
7641 Some(
7642 buffer_snapshot.anchor_before(range.start)
7643 ..buffer_snapshot.anchor_after(range.end),
7644 ),
7645 buffer_snapshot,
7646 cx,
7647 );
7648 for (breakpoint, state) in breakpoints {
7649 let multi_buffer_anchor =
7650 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7651 let position = multi_buffer_anchor
7652 .to_point(&multi_buffer_snapshot)
7653 .to_display_point(&snapshot);
7654
7655 breakpoint_display_points.insert(
7656 position.row(),
7657 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7658 );
7659 }
7660 }
7661
7662 breakpoint_display_points
7663 }
7664
7665 fn breakpoint_context_menu(
7666 &self,
7667 anchor: Anchor,
7668 window: &mut Window,
7669 cx: &mut Context<Self>,
7670 ) -> Entity<ui::ContextMenu> {
7671 let weak_editor = cx.weak_entity();
7672 let focus_handle = self.focus_handle(cx);
7673
7674 let row = self
7675 .buffer
7676 .read(cx)
7677 .snapshot(cx)
7678 .summary_for_anchor::<Point>(&anchor)
7679 .row;
7680
7681 let breakpoint = self
7682 .breakpoint_at_row(row, window, cx)
7683 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7684
7685 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7686 "Edit Log Breakpoint"
7687 } else {
7688 "Set Log Breakpoint"
7689 };
7690
7691 let condition_breakpoint_msg = if breakpoint
7692 .as_ref()
7693 .is_some_and(|bp| bp.1.condition.is_some())
7694 {
7695 "Edit Condition Breakpoint"
7696 } else {
7697 "Set Condition Breakpoint"
7698 };
7699
7700 let hit_condition_breakpoint_msg = if breakpoint
7701 .as_ref()
7702 .is_some_and(|bp| bp.1.hit_condition.is_some())
7703 {
7704 "Edit Hit Condition Breakpoint"
7705 } else {
7706 "Set Hit Condition Breakpoint"
7707 };
7708
7709 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7710 "Unset Breakpoint"
7711 } else {
7712 "Set Breakpoint"
7713 };
7714
7715 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7716
7717 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7718 BreakpointState::Enabled => Some("Disable"),
7719 BreakpointState::Disabled => Some("Enable"),
7720 });
7721
7722 let (anchor, breakpoint) =
7723 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7724
7725 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7726 menu.on_blur_subscription(Subscription::new(|| {}))
7727 .context(focus_handle)
7728 .when(run_to_cursor, |this| {
7729 let weak_editor = weak_editor.clone();
7730 this.entry("Run to cursor", None, move |window, cx| {
7731 weak_editor
7732 .update(cx, |editor, cx| {
7733 editor.change_selections(None, window, cx, |s| {
7734 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7735 });
7736 })
7737 .ok();
7738
7739 window.dispatch_action(Box::new(RunToCursor), cx);
7740 })
7741 .separator()
7742 })
7743 .when_some(toggle_state_msg, |this, msg| {
7744 this.entry(msg, None, {
7745 let weak_editor = weak_editor.clone();
7746 let breakpoint = breakpoint.clone();
7747 move |_window, cx| {
7748 weak_editor
7749 .update(cx, |this, cx| {
7750 this.edit_breakpoint_at_anchor(
7751 anchor,
7752 breakpoint.as_ref().clone(),
7753 BreakpointEditAction::InvertState,
7754 cx,
7755 );
7756 })
7757 .log_err();
7758 }
7759 })
7760 })
7761 .entry(set_breakpoint_msg, None, {
7762 let weak_editor = weak_editor.clone();
7763 let breakpoint = breakpoint.clone();
7764 move |_window, cx| {
7765 weak_editor
7766 .update(cx, |this, cx| {
7767 this.edit_breakpoint_at_anchor(
7768 anchor,
7769 breakpoint.as_ref().clone(),
7770 BreakpointEditAction::Toggle,
7771 cx,
7772 );
7773 })
7774 .log_err();
7775 }
7776 })
7777 .entry(log_breakpoint_msg, None, {
7778 let breakpoint = breakpoint.clone();
7779 let weak_editor = weak_editor.clone();
7780 move |window, cx| {
7781 weak_editor
7782 .update(cx, |this, cx| {
7783 this.add_edit_breakpoint_block(
7784 anchor,
7785 breakpoint.as_ref(),
7786 BreakpointPromptEditAction::Log,
7787 window,
7788 cx,
7789 );
7790 })
7791 .log_err();
7792 }
7793 })
7794 .entry(condition_breakpoint_msg, None, {
7795 let breakpoint = breakpoint.clone();
7796 let weak_editor = weak_editor.clone();
7797 move |window, cx| {
7798 weak_editor
7799 .update(cx, |this, cx| {
7800 this.add_edit_breakpoint_block(
7801 anchor,
7802 breakpoint.as_ref(),
7803 BreakpointPromptEditAction::Condition,
7804 window,
7805 cx,
7806 );
7807 })
7808 .log_err();
7809 }
7810 })
7811 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7812 weak_editor
7813 .update(cx, |this, cx| {
7814 this.add_edit_breakpoint_block(
7815 anchor,
7816 breakpoint.as_ref(),
7817 BreakpointPromptEditAction::HitCondition,
7818 window,
7819 cx,
7820 );
7821 })
7822 .log_err();
7823 })
7824 })
7825 }
7826
7827 fn render_breakpoint(
7828 &self,
7829 position: Anchor,
7830 row: DisplayRow,
7831 breakpoint: &Breakpoint,
7832 state: Option<BreakpointSessionState>,
7833 cx: &mut Context<Self>,
7834 ) -> IconButton {
7835 let is_rejected = state.is_some_and(|s| !s.verified);
7836 // Is it a breakpoint that shows up when hovering over gutter?
7837 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7838 (false, false),
7839 |PhantomBreakpointIndicator {
7840 is_active,
7841 display_row,
7842 collides_with_existing_breakpoint,
7843 }| {
7844 (
7845 is_active && display_row == row,
7846 collides_with_existing_breakpoint,
7847 )
7848 },
7849 );
7850
7851 let (color, icon) = {
7852 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7853 (false, false) => ui::IconName::DebugBreakpoint,
7854 (true, false) => ui::IconName::DebugLogBreakpoint,
7855 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7856 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7857 };
7858
7859 let color = if is_phantom {
7860 Color::Hint
7861 } else if is_rejected {
7862 Color::Disabled
7863 } else {
7864 Color::Debugger
7865 };
7866
7867 (color, icon)
7868 };
7869
7870 let breakpoint = Arc::from(breakpoint.clone());
7871
7872 let alt_as_text = gpui::Keystroke {
7873 modifiers: Modifiers::secondary_key(),
7874 ..Default::default()
7875 };
7876 let primary_action_text = if breakpoint.is_disabled() {
7877 "Enable breakpoint"
7878 } else if is_phantom && !collides_with_existing {
7879 "Set breakpoint"
7880 } else {
7881 "Unset breakpoint"
7882 };
7883 let focus_handle = self.focus_handle.clone();
7884
7885 let meta = if is_rejected {
7886 SharedString::from("No executable code is associated with this line.")
7887 } else if collides_with_existing && !breakpoint.is_disabled() {
7888 SharedString::from(format!(
7889 "{alt_as_text}-click to disable,\nright-click for more options."
7890 ))
7891 } else {
7892 SharedString::from("Right-click for more options.")
7893 };
7894 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7895 .icon_size(IconSize::XSmall)
7896 .size(ui::ButtonSize::None)
7897 .when(is_rejected, |this| {
7898 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7899 })
7900 .icon_color(color)
7901 .style(ButtonStyle::Transparent)
7902 .on_click(cx.listener({
7903 let breakpoint = breakpoint.clone();
7904
7905 move |editor, event: &ClickEvent, window, cx| {
7906 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7907 BreakpointEditAction::InvertState
7908 } else {
7909 BreakpointEditAction::Toggle
7910 };
7911
7912 window.focus(&editor.focus_handle(cx));
7913 editor.edit_breakpoint_at_anchor(
7914 position,
7915 breakpoint.as_ref().clone(),
7916 edit_action,
7917 cx,
7918 );
7919 }
7920 }))
7921 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7922 editor.set_breakpoint_context_menu(
7923 row,
7924 Some(position),
7925 event.down.position,
7926 window,
7927 cx,
7928 );
7929 }))
7930 .tooltip(move |window, cx| {
7931 Tooltip::with_meta_in(
7932 primary_action_text,
7933 Some(&ToggleBreakpoint),
7934 meta.clone(),
7935 &focus_handle,
7936 window,
7937 cx,
7938 )
7939 })
7940 }
7941
7942 fn build_tasks_context(
7943 project: &Entity<Project>,
7944 buffer: &Entity<Buffer>,
7945 buffer_row: u32,
7946 tasks: &Arc<RunnableTasks>,
7947 cx: &mut Context<Self>,
7948 ) -> Task<Option<task::TaskContext>> {
7949 let position = Point::new(buffer_row, tasks.column);
7950 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7951 let location = Location {
7952 buffer: buffer.clone(),
7953 range: range_start..range_start,
7954 };
7955 // Fill in the environmental variables from the tree-sitter captures
7956 let mut captured_task_variables = TaskVariables::default();
7957 for (capture_name, value) in tasks.extra_variables.clone() {
7958 captured_task_variables.insert(
7959 task::VariableName::Custom(capture_name.into()),
7960 value.clone(),
7961 );
7962 }
7963 project.update(cx, |project, cx| {
7964 project.task_store().update(cx, |task_store, cx| {
7965 task_store.task_context_for_location(captured_task_variables, location, cx)
7966 })
7967 })
7968 }
7969
7970 pub fn spawn_nearest_task(
7971 &mut self,
7972 action: &SpawnNearestTask,
7973 window: &mut Window,
7974 cx: &mut Context<Self>,
7975 ) {
7976 let Some((workspace, _)) = self.workspace.clone() else {
7977 return;
7978 };
7979 let Some(project) = self.project.clone() else {
7980 return;
7981 };
7982
7983 // Try to find a closest, enclosing node using tree-sitter that has a
7984 // task
7985 let Some((buffer, buffer_row, tasks)) = self
7986 .find_enclosing_node_task(cx)
7987 // Or find the task that's closest in row-distance.
7988 .or_else(|| self.find_closest_task(cx))
7989 else {
7990 return;
7991 };
7992
7993 let reveal_strategy = action.reveal;
7994 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7995 cx.spawn_in(window, async move |_, cx| {
7996 let context = task_context.await?;
7997 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7998
7999 let resolved = &mut resolved_task.resolved;
8000 resolved.reveal = reveal_strategy;
8001
8002 workspace
8003 .update_in(cx, |workspace, window, cx| {
8004 workspace.schedule_resolved_task(
8005 task_source_kind,
8006 resolved_task,
8007 false,
8008 window,
8009 cx,
8010 );
8011 })
8012 .ok()
8013 })
8014 .detach();
8015 }
8016
8017 fn find_closest_task(
8018 &mut self,
8019 cx: &mut Context<Self>,
8020 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8021 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8022
8023 let ((buffer_id, row), tasks) = self
8024 .tasks
8025 .iter()
8026 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8027
8028 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8029 let tasks = Arc::new(tasks.to_owned());
8030 Some((buffer, *row, tasks))
8031 }
8032
8033 fn find_enclosing_node_task(
8034 &mut self,
8035 cx: &mut Context<Self>,
8036 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8037 let snapshot = self.buffer.read(cx).snapshot(cx);
8038 let offset = self.selections.newest::<usize>(cx).head();
8039 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8040 let buffer_id = excerpt.buffer().remote_id();
8041
8042 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8043 let mut cursor = layer.node().walk();
8044
8045 while cursor.goto_first_child_for_byte(offset).is_some() {
8046 if cursor.node().end_byte() == offset {
8047 cursor.goto_next_sibling();
8048 }
8049 }
8050
8051 // Ascend to the smallest ancestor that contains the range and has a task.
8052 loop {
8053 let node = cursor.node();
8054 let node_range = node.byte_range();
8055 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8056
8057 // Check if this node contains our offset
8058 if node_range.start <= offset && node_range.end >= offset {
8059 // If it contains offset, check for task
8060 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8061 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8062 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8063 }
8064 }
8065
8066 if !cursor.goto_parent() {
8067 break;
8068 }
8069 }
8070 None
8071 }
8072
8073 fn render_run_indicator(
8074 &self,
8075 _style: &EditorStyle,
8076 is_active: bool,
8077 row: DisplayRow,
8078 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8079 cx: &mut Context<Self>,
8080 ) -> IconButton {
8081 let color = Color::Muted;
8082 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8083
8084 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8085 .shape(ui::IconButtonShape::Square)
8086 .icon_size(IconSize::XSmall)
8087 .icon_color(color)
8088 .toggle_state(is_active)
8089 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8090 let quick_launch = e.down.button == MouseButton::Left;
8091 window.focus(&editor.focus_handle(cx));
8092 editor.toggle_code_actions(
8093 &ToggleCodeActions {
8094 deployed_from: Some(CodeActionSource::RunMenu(row)),
8095 quick_launch,
8096 },
8097 window,
8098 cx,
8099 );
8100 }))
8101 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8102 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8103 }))
8104 }
8105
8106 pub fn context_menu_visible(&self) -> bool {
8107 !self.edit_prediction_preview_is_active()
8108 && self
8109 .context_menu
8110 .borrow()
8111 .as_ref()
8112 .map_or(false, |menu| menu.visible())
8113 }
8114
8115 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8116 self.context_menu
8117 .borrow()
8118 .as_ref()
8119 .map(|menu| menu.origin())
8120 }
8121
8122 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8123 self.context_menu_options = Some(options);
8124 }
8125
8126 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8127 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8128
8129 fn render_edit_prediction_popover(
8130 &mut self,
8131 text_bounds: &Bounds<Pixels>,
8132 content_origin: gpui::Point<Pixels>,
8133 right_margin: Pixels,
8134 editor_snapshot: &EditorSnapshot,
8135 visible_row_range: Range<DisplayRow>,
8136 scroll_top: f32,
8137 scroll_bottom: f32,
8138 line_layouts: &[LineWithInvisibles],
8139 line_height: Pixels,
8140 scroll_pixel_position: gpui::Point<Pixels>,
8141 newest_selection_head: Option<DisplayPoint>,
8142 editor_width: Pixels,
8143 style: &EditorStyle,
8144 window: &mut Window,
8145 cx: &mut App,
8146 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8147 if self.mode().is_minimap() {
8148 return None;
8149 }
8150 let active_inline_completion = self.active_inline_completion.as_ref()?;
8151
8152 if self.edit_prediction_visible_in_cursor_popover(true) {
8153 return None;
8154 }
8155
8156 match &active_inline_completion.completion {
8157 InlineCompletion::Move { target, .. } => {
8158 let target_display_point = target.to_display_point(editor_snapshot);
8159
8160 if self.edit_prediction_requires_modifier() {
8161 if !self.edit_prediction_preview_is_active() {
8162 return None;
8163 }
8164
8165 self.render_edit_prediction_modifier_jump_popover(
8166 text_bounds,
8167 content_origin,
8168 visible_row_range,
8169 line_layouts,
8170 line_height,
8171 scroll_pixel_position,
8172 newest_selection_head,
8173 target_display_point,
8174 window,
8175 cx,
8176 )
8177 } else {
8178 self.render_edit_prediction_eager_jump_popover(
8179 text_bounds,
8180 content_origin,
8181 editor_snapshot,
8182 visible_row_range,
8183 scroll_top,
8184 scroll_bottom,
8185 line_height,
8186 scroll_pixel_position,
8187 target_display_point,
8188 editor_width,
8189 window,
8190 cx,
8191 )
8192 }
8193 }
8194 InlineCompletion::Edit {
8195 display_mode: EditDisplayMode::Inline,
8196 ..
8197 } => None,
8198 InlineCompletion::Edit {
8199 display_mode: EditDisplayMode::TabAccept,
8200 edits,
8201 ..
8202 } => {
8203 let range = &edits.first()?.0;
8204 let target_display_point = range.end.to_display_point(editor_snapshot);
8205
8206 self.render_edit_prediction_end_of_line_popover(
8207 "Accept",
8208 editor_snapshot,
8209 visible_row_range,
8210 target_display_point,
8211 line_height,
8212 scroll_pixel_position,
8213 content_origin,
8214 editor_width,
8215 window,
8216 cx,
8217 )
8218 }
8219 InlineCompletion::Edit {
8220 edits,
8221 edit_preview,
8222 display_mode: EditDisplayMode::DiffPopover,
8223 snapshot,
8224 } => self.render_edit_prediction_diff_popover(
8225 text_bounds,
8226 content_origin,
8227 right_margin,
8228 editor_snapshot,
8229 visible_row_range,
8230 line_layouts,
8231 line_height,
8232 scroll_pixel_position,
8233 newest_selection_head,
8234 editor_width,
8235 style,
8236 edits,
8237 edit_preview,
8238 snapshot,
8239 window,
8240 cx,
8241 ),
8242 }
8243 }
8244
8245 fn render_edit_prediction_modifier_jump_popover(
8246 &mut self,
8247 text_bounds: &Bounds<Pixels>,
8248 content_origin: gpui::Point<Pixels>,
8249 visible_row_range: Range<DisplayRow>,
8250 line_layouts: &[LineWithInvisibles],
8251 line_height: Pixels,
8252 scroll_pixel_position: gpui::Point<Pixels>,
8253 newest_selection_head: Option<DisplayPoint>,
8254 target_display_point: DisplayPoint,
8255 window: &mut Window,
8256 cx: &mut App,
8257 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8258 let scrolled_content_origin =
8259 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8260
8261 const SCROLL_PADDING_Y: Pixels = px(12.);
8262
8263 if target_display_point.row() < visible_row_range.start {
8264 return self.render_edit_prediction_scroll_popover(
8265 |_| SCROLL_PADDING_Y,
8266 IconName::ArrowUp,
8267 visible_row_range,
8268 line_layouts,
8269 newest_selection_head,
8270 scrolled_content_origin,
8271 window,
8272 cx,
8273 );
8274 } else if target_display_point.row() >= visible_row_range.end {
8275 return self.render_edit_prediction_scroll_popover(
8276 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8277 IconName::ArrowDown,
8278 visible_row_range,
8279 line_layouts,
8280 newest_selection_head,
8281 scrolled_content_origin,
8282 window,
8283 cx,
8284 );
8285 }
8286
8287 const POLE_WIDTH: Pixels = px(2.);
8288
8289 let line_layout =
8290 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8291 let target_column = target_display_point.column() as usize;
8292
8293 let target_x = line_layout.x_for_index(target_column);
8294 let target_y =
8295 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8296
8297 let flag_on_right = target_x < text_bounds.size.width / 2.;
8298
8299 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8300 border_color.l += 0.001;
8301
8302 let mut element = v_flex()
8303 .items_end()
8304 .when(flag_on_right, |el| el.items_start())
8305 .child(if flag_on_right {
8306 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8307 .rounded_bl(px(0.))
8308 .rounded_tl(px(0.))
8309 .border_l_2()
8310 .border_color(border_color)
8311 } else {
8312 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8313 .rounded_br(px(0.))
8314 .rounded_tr(px(0.))
8315 .border_r_2()
8316 .border_color(border_color)
8317 })
8318 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8319 .into_any();
8320
8321 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8322
8323 let mut origin = scrolled_content_origin + point(target_x, target_y)
8324 - point(
8325 if flag_on_right {
8326 POLE_WIDTH
8327 } else {
8328 size.width - POLE_WIDTH
8329 },
8330 size.height - line_height,
8331 );
8332
8333 origin.x = origin.x.max(content_origin.x);
8334
8335 element.prepaint_at(origin, window, cx);
8336
8337 Some((element, origin))
8338 }
8339
8340 fn render_edit_prediction_scroll_popover(
8341 &mut self,
8342 to_y: impl Fn(Size<Pixels>) -> Pixels,
8343 scroll_icon: IconName,
8344 visible_row_range: Range<DisplayRow>,
8345 line_layouts: &[LineWithInvisibles],
8346 newest_selection_head: Option<DisplayPoint>,
8347 scrolled_content_origin: gpui::Point<Pixels>,
8348 window: &mut Window,
8349 cx: &mut App,
8350 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8351 let mut element = self
8352 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8353 .into_any();
8354
8355 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8356
8357 let cursor = newest_selection_head?;
8358 let cursor_row_layout =
8359 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8360 let cursor_column = cursor.column() as usize;
8361
8362 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8363
8364 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8365
8366 element.prepaint_at(origin, window, cx);
8367 Some((element, origin))
8368 }
8369
8370 fn render_edit_prediction_eager_jump_popover(
8371 &mut self,
8372 text_bounds: &Bounds<Pixels>,
8373 content_origin: gpui::Point<Pixels>,
8374 editor_snapshot: &EditorSnapshot,
8375 visible_row_range: Range<DisplayRow>,
8376 scroll_top: f32,
8377 scroll_bottom: f32,
8378 line_height: Pixels,
8379 scroll_pixel_position: gpui::Point<Pixels>,
8380 target_display_point: DisplayPoint,
8381 editor_width: Pixels,
8382 window: &mut Window,
8383 cx: &mut App,
8384 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8385 if target_display_point.row().as_f32() < scroll_top {
8386 let mut element = self
8387 .render_edit_prediction_line_popover(
8388 "Jump to Edit",
8389 Some(IconName::ArrowUp),
8390 window,
8391 cx,
8392 )?
8393 .into_any();
8394
8395 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8396 let offset = point(
8397 (text_bounds.size.width - size.width) / 2.,
8398 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8399 );
8400
8401 let origin = text_bounds.origin + offset;
8402 element.prepaint_at(origin, window, cx);
8403 Some((element, origin))
8404 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8405 let mut element = self
8406 .render_edit_prediction_line_popover(
8407 "Jump to Edit",
8408 Some(IconName::ArrowDown),
8409 window,
8410 cx,
8411 )?
8412 .into_any();
8413
8414 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8415 let offset = point(
8416 (text_bounds.size.width - size.width) / 2.,
8417 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8418 );
8419
8420 let origin = text_bounds.origin + offset;
8421 element.prepaint_at(origin, window, cx);
8422 Some((element, origin))
8423 } else {
8424 self.render_edit_prediction_end_of_line_popover(
8425 "Jump to Edit",
8426 editor_snapshot,
8427 visible_row_range,
8428 target_display_point,
8429 line_height,
8430 scroll_pixel_position,
8431 content_origin,
8432 editor_width,
8433 window,
8434 cx,
8435 )
8436 }
8437 }
8438
8439 fn render_edit_prediction_end_of_line_popover(
8440 self: &mut Editor,
8441 label: &'static str,
8442 editor_snapshot: &EditorSnapshot,
8443 visible_row_range: Range<DisplayRow>,
8444 target_display_point: DisplayPoint,
8445 line_height: Pixels,
8446 scroll_pixel_position: gpui::Point<Pixels>,
8447 content_origin: gpui::Point<Pixels>,
8448 editor_width: Pixels,
8449 window: &mut Window,
8450 cx: &mut App,
8451 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8452 let target_line_end = DisplayPoint::new(
8453 target_display_point.row(),
8454 editor_snapshot.line_len(target_display_point.row()),
8455 );
8456
8457 let mut element = self
8458 .render_edit_prediction_line_popover(label, None, window, cx)?
8459 .into_any();
8460
8461 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8462
8463 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8464
8465 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8466 let mut origin = start_point
8467 + line_origin
8468 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8469 origin.x = origin.x.max(content_origin.x);
8470
8471 let max_x = content_origin.x + editor_width - size.width;
8472
8473 if origin.x > max_x {
8474 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8475
8476 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8477 origin.y += offset;
8478 IconName::ArrowUp
8479 } else {
8480 origin.y -= offset;
8481 IconName::ArrowDown
8482 };
8483
8484 element = self
8485 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8486 .into_any();
8487
8488 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8489
8490 origin.x = content_origin.x + editor_width - size.width - px(2.);
8491 }
8492
8493 element.prepaint_at(origin, window, cx);
8494 Some((element, origin))
8495 }
8496
8497 fn render_edit_prediction_diff_popover(
8498 self: &Editor,
8499 text_bounds: &Bounds<Pixels>,
8500 content_origin: gpui::Point<Pixels>,
8501 right_margin: Pixels,
8502 editor_snapshot: &EditorSnapshot,
8503 visible_row_range: Range<DisplayRow>,
8504 line_layouts: &[LineWithInvisibles],
8505 line_height: Pixels,
8506 scroll_pixel_position: gpui::Point<Pixels>,
8507 newest_selection_head: Option<DisplayPoint>,
8508 editor_width: Pixels,
8509 style: &EditorStyle,
8510 edits: &Vec<(Range<Anchor>, String)>,
8511 edit_preview: &Option<language::EditPreview>,
8512 snapshot: &language::BufferSnapshot,
8513 window: &mut Window,
8514 cx: &mut App,
8515 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8516 let edit_start = edits
8517 .first()
8518 .unwrap()
8519 .0
8520 .start
8521 .to_display_point(editor_snapshot);
8522 let edit_end = edits
8523 .last()
8524 .unwrap()
8525 .0
8526 .end
8527 .to_display_point(editor_snapshot);
8528
8529 let is_visible = visible_row_range.contains(&edit_start.row())
8530 || visible_row_range.contains(&edit_end.row());
8531 if !is_visible {
8532 return None;
8533 }
8534
8535 let highlighted_edits =
8536 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8537
8538 let styled_text = highlighted_edits.to_styled_text(&style.text);
8539 let line_count = highlighted_edits.text.lines().count();
8540
8541 const BORDER_WIDTH: Pixels = px(1.);
8542
8543 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8544 let has_keybind = keybind.is_some();
8545
8546 let mut element = h_flex()
8547 .items_start()
8548 .child(
8549 h_flex()
8550 .bg(cx.theme().colors().editor_background)
8551 .border(BORDER_WIDTH)
8552 .shadow_sm()
8553 .border_color(cx.theme().colors().border)
8554 .rounded_l_lg()
8555 .when(line_count > 1, |el| el.rounded_br_lg())
8556 .pr_1()
8557 .child(styled_text),
8558 )
8559 .child(
8560 h_flex()
8561 .h(line_height + BORDER_WIDTH * 2.)
8562 .px_1p5()
8563 .gap_1()
8564 // Workaround: For some reason, there's a gap if we don't do this
8565 .ml(-BORDER_WIDTH)
8566 .shadow(vec![gpui::BoxShadow {
8567 color: gpui::black().opacity(0.05),
8568 offset: point(px(1.), px(1.)),
8569 blur_radius: px(2.),
8570 spread_radius: px(0.),
8571 }])
8572 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8573 .border(BORDER_WIDTH)
8574 .border_color(cx.theme().colors().border)
8575 .rounded_r_lg()
8576 .id("edit_prediction_diff_popover_keybind")
8577 .when(!has_keybind, |el| {
8578 let status_colors = cx.theme().status();
8579
8580 el.bg(status_colors.error_background)
8581 .border_color(status_colors.error.opacity(0.6))
8582 .child(Icon::new(IconName::Info).color(Color::Error))
8583 .cursor_default()
8584 .hoverable_tooltip(move |_window, cx| {
8585 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8586 })
8587 })
8588 .children(keybind),
8589 )
8590 .into_any();
8591
8592 let longest_row =
8593 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8594 let longest_line_width = if visible_row_range.contains(&longest_row) {
8595 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8596 } else {
8597 layout_line(
8598 longest_row,
8599 editor_snapshot,
8600 style,
8601 editor_width,
8602 |_| false,
8603 window,
8604 cx,
8605 )
8606 .width
8607 };
8608
8609 let viewport_bounds =
8610 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8611 right: -right_margin,
8612 ..Default::default()
8613 });
8614
8615 let x_after_longest =
8616 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8617 - scroll_pixel_position.x;
8618
8619 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8620
8621 // Fully visible if it can be displayed within the window (allow overlapping other
8622 // panes). However, this is only allowed if the popover starts within text_bounds.
8623 let can_position_to_the_right = x_after_longest < text_bounds.right()
8624 && x_after_longest + element_bounds.width < viewport_bounds.right();
8625
8626 let mut origin = if can_position_to_the_right {
8627 point(
8628 x_after_longest,
8629 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8630 - scroll_pixel_position.y,
8631 )
8632 } else {
8633 let cursor_row = newest_selection_head.map(|head| head.row());
8634 let above_edit = edit_start
8635 .row()
8636 .0
8637 .checked_sub(line_count as u32)
8638 .map(DisplayRow);
8639 let below_edit = Some(edit_end.row() + 1);
8640 let above_cursor =
8641 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8642 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8643
8644 // Place the edit popover adjacent to the edit if there is a location
8645 // available that is onscreen and does not obscure the cursor. Otherwise,
8646 // place it adjacent to the cursor.
8647 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8648 .into_iter()
8649 .flatten()
8650 .find(|&start_row| {
8651 let end_row = start_row + line_count as u32;
8652 visible_row_range.contains(&start_row)
8653 && visible_row_range.contains(&end_row)
8654 && cursor_row.map_or(true, |cursor_row| {
8655 !((start_row..end_row).contains(&cursor_row))
8656 })
8657 })?;
8658
8659 content_origin
8660 + point(
8661 -scroll_pixel_position.x,
8662 row_target.as_f32() * line_height - scroll_pixel_position.y,
8663 )
8664 };
8665
8666 origin.x -= BORDER_WIDTH;
8667
8668 window.defer_draw(element, origin, 1);
8669
8670 // Do not return an element, since it will already be drawn due to defer_draw.
8671 None
8672 }
8673
8674 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8675 px(30.)
8676 }
8677
8678 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8679 if self.read_only(cx) {
8680 cx.theme().players().read_only()
8681 } else {
8682 self.style.as_ref().unwrap().local_player
8683 }
8684 }
8685
8686 fn render_edit_prediction_accept_keybind(
8687 &self,
8688 window: &mut Window,
8689 cx: &App,
8690 ) -> Option<AnyElement> {
8691 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8692 let accept_keystroke = accept_binding.keystroke()?;
8693
8694 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8695
8696 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8697 Color::Accent
8698 } else {
8699 Color::Muted
8700 };
8701
8702 h_flex()
8703 .px_0p5()
8704 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8705 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8706 .text_size(TextSize::XSmall.rems(cx))
8707 .child(h_flex().children(ui::render_modifiers(
8708 &accept_keystroke.modifiers,
8709 PlatformStyle::platform(),
8710 Some(modifiers_color),
8711 Some(IconSize::XSmall.rems().into()),
8712 true,
8713 )))
8714 .when(is_platform_style_mac, |parent| {
8715 parent.child(accept_keystroke.key.clone())
8716 })
8717 .when(!is_platform_style_mac, |parent| {
8718 parent.child(
8719 Key::new(
8720 util::capitalize(&accept_keystroke.key),
8721 Some(Color::Default),
8722 )
8723 .size(Some(IconSize::XSmall.rems().into())),
8724 )
8725 })
8726 .into_any()
8727 .into()
8728 }
8729
8730 fn render_edit_prediction_line_popover(
8731 &self,
8732 label: impl Into<SharedString>,
8733 icon: Option<IconName>,
8734 window: &mut Window,
8735 cx: &App,
8736 ) -> Option<Stateful<Div>> {
8737 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8738
8739 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8740 let has_keybind = keybind.is_some();
8741
8742 let result = h_flex()
8743 .id("ep-line-popover")
8744 .py_0p5()
8745 .pl_1()
8746 .pr(padding_right)
8747 .gap_1()
8748 .rounded_md()
8749 .border_1()
8750 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8751 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8752 .shadow_sm()
8753 .when(!has_keybind, |el| {
8754 let status_colors = cx.theme().status();
8755
8756 el.bg(status_colors.error_background)
8757 .border_color(status_colors.error.opacity(0.6))
8758 .pl_2()
8759 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8760 .cursor_default()
8761 .hoverable_tooltip(move |_window, cx| {
8762 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8763 })
8764 })
8765 .children(keybind)
8766 .child(
8767 Label::new(label)
8768 .size(LabelSize::Small)
8769 .when(!has_keybind, |el| {
8770 el.color(cx.theme().status().error.into()).strikethrough()
8771 }),
8772 )
8773 .when(!has_keybind, |el| {
8774 el.child(
8775 h_flex().ml_1().child(
8776 Icon::new(IconName::Info)
8777 .size(IconSize::Small)
8778 .color(cx.theme().status().error.into()),
8779 ),
8780 )
8781 })
8782 .when_some(icon, |element, icon| {
8783 element.child(
8784 div()
8785 .mt(px(1.5))
8786 .child(Icon::new(icon).size(IconSize::Small)),
8787 )
8788 });
8789
8790 Some(result)
8791 }
8792
8793 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8794 let accent_color = cx.theme().colors().text_accent;
8795 let editor_bg_color = cx.theme().colors().editor_background;
8796 editor_bg_color.blend(accent_color.opacity(0.1))
8797 }
8798
8799 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8800 let accent_color = cx.theme().colors().text_accent;
8801 let editor_bg_color = cx.theme().colors().editor_background;
8802 editor_bg_color.blend(accent_color.opacity(0.6))
8803 }
8804
8805 fn render_edit_prediction_cursor_popover(
8806 &self,
8807 min_width: Pixels,
8808 max_width: Pixels,
8809 cursor_point: Point,
8810 style: &EditorStyle,
8811 accept_keystroke: Option<&gpui::Keystroke>,
8812 _window: &Window,
8813 cx: &mut Context<Editor>,
8814 ) -> Option<AnyElement> {
8815 let provider = self.edit_prediction_provider.as_ref()?;
8816
8817 if provider.provider.needs_terms_acceptance(cx) {
8818 return Some(
8819 h_flex()
8820 .min_w(min_width)
8821 .flex_1()
8822 .px_2()
8823 .py_1()
8824 .gap_3()
8825 .elevation_2(cx)
8826 .hover(|style| style.bg(cx.theme().colors().element_hover))
8827 .id("accept-terms")
8828 .cursor_pointer()
8829 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8830 .on_click(cx.listener(|this, _event, window, cx| {
8831 cx.stop_propagation();
8832 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8833 window.dispatch_action(
8834 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8835 cx,
8836 );
8837 }))
8838 .child(
8839 h_flex()
8840 .flex_1()
8841 .gap_2()
8842 .child(Icon::new(IconName::ZedPredict))
8843 .child(Label::new("Accept Terms of Service"))
8844 .child(div().w_full())
8845 .child(
8846 Icon::new(IconName::ArrowUpRight)
8847 .color(Color::Muted)
8848 .size(IconSize::Small),
8849 )
8850 .into_any_element(),
8851 )
8852 .into_any(),
8853 );
8854 }
8855
8856 let is_refreshing = provider.provider.is_refreshing(cx);
8857
8858 fn pending_completion_container() -> Div {
8859 h_flex()
8860 .h_full()
8861 .flex_1()
8862 .gap_2()
8863 .child(Icon::new(IconName::ZedPredict))
8864 }
8865
8866 let completion = match &self.active_inline_completion {
8867 Some(prediction) => {
8868 if !self.has_visible_completions_menu() {
8869 const RADIUS: Pixels = px(6.);
8870 const BORDER_WIDTH: Pixels = px(1.);
8871
8872 return Some(
8873 h_flex()
8874 .elevation_2(cx)
8875 .border(BORDER_WIDTH)
8876 .border_color(cx.theme().colors().border)
8877 .when(accept_keystroke.is_none(), |el| {
8878 el.border_color(cx.theme().status().error)
8879 })
8880 .rounded(RADIUS)
8881 .rounded_tl(px(0.))
8882 .overflow_hidden()
8883 .child(div().px_1p5().child(match &prediction.completion {
8884 InlineCompletion::Move { target, snapshot } => {
8885 use text::ToPoint as _;
8886 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8887 {
8888 Icon::new(IconName::ZedPredictDown)
8889 } else {
8890 Icon::new(IconName::ZedPredictUp)
8891 }
8892 }
8893 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8894 }))
8895 .child(
8896 h_flex()
8897 .gap_1()
8898 .py_1()
8899 .px_2()
8900 .rounded_r(RADIUS - BORDER_WIDTH)
8901 .border_l_1()
8902 .border_color(cx.theme().colors().border)
8903 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8904 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8905 el.child(
8906 Label::new("Hold")
8907 .size(LabelSize::Small)
8908 .when(accept_keystroke.is_none(), |el| {
8909 el.strikethrough()
8910 })
8911 .line_height_style(LineHeightStyle::UiLabel),
8912 )
8913 })
8914 .id("edit_prediction_cursor_popover_keybind")
8915 .when(accept_keystroke.is_none(), |el| {
8916 let status_colors = cx.theme().status();
8917
8918 el.bg(status_colors.error_background)
8919 .border_color(status_colors.error.opacity(0.6))
8920 .child(Icon::new(IconName::Info).color(Color::Error))
8921 .cursor_default()
8922 .hoverable_tooltip(move |_window, cx| {
8923 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8924 .into()
8925 })
8926 })
8927 .when_some(
8928 accept_keystroke.as_ref(),
8929 |el, accept_keystroke| {
8930 el.child(h_flex().children(ui::render_modifiers(
8931 &accept_keystroke.modifiers,
8932 PlatformStyle::platform(),
8933 Some(Color::Default),
8934 Some(IconSize::XSmall.rems().into()),
8935 false,
8936 )))
8937 },
8938 ),
8939 )
8940 .into_any(),
8941 );
8942 }
8943
8944 self.render_edit_prediction_cursor_popover_preview(
8945 prediction,
8946 cursor_point,
8947 style,
8948 cx,
8949 )?
8950 }
8951
8952 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8953 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8954 stale_completion,
8955 cursor_point,
8956 style,
8957 cx,
8958 )?,
8959
8960 None => {
8961 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8962 }
8963 },
8964
8965 None => pending_completion_container().child(Label::new("No Prediction")),
8966 };
8967
8968 let completion = if is_refreshing {
8969 completion
8970 .with_animation(
8971 "loading-completion",
8972 Animation::new(Duration::from_secs(2))
8973 .repeat()
8974 .with_easing(pulsating_between(0.4, 0.8)),
8975 |label, delta| label.opacity(delta),
8976 )
8977 .into_any_element()
8978 } else {
8979 completion.into_any_element()
8980 };
8981
8982 let has_completion = self.active_inline_completion.is_some();
8983
8984 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8985 Some(
8986 h_flex()
8987 .min_w(min_width)
8988 .max_w(max_width)
8989 .flex_1()
8990 .elevation_2(cx)
8991 .border_color(cx.theme().colors().border)
8992 .child(
8993 div()
8994 .flex_1()
8995 .py_1()
8996 .px_2()
8997 .overflow_hidden()
8998 .child(completion),
8999 )
9000 .when_some(accept_keystroke, |el, accept_keystroke| {
9001 if !accept_keystroke.modifiers.modified() {
9002 return el;
9003 }
9004
9005 el.child(
9006 h_flex()
9007 .h_full()
9008 .border_l_1()
9009 .rounded_r_lg()
9010 .border_color(cx.theme().colors().border)
9011 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9012 .gap_1()
9013 .py_1()
9014 .px_2()
9015 .child(
9016 h_flex()
9017 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9018 .when(is_platform_style_mac, |parent| parent.gap_1())
9019 .child(h_flex().children(ui::render_modifiers(
9020 &accept_keystroke.modifiers,
9021 PlatformStyle::platform(),
9022 Some(if !has_completion {
9023 Color::Muted
9024 } else {
9025 Color::Default
9026 }),
9027 None,
9028 false,
9029 ))),
9030 )
9031 .child(Label::new("Preview").into_any_element())
9032 .opacity(if has_completion { 1.0 } else { 0.4 }),
9033 )
9034 })
9035 .into_any(),
9036 )
9037 }
9038
9039 fn render_edit_prediction_cursor_popover_preview(
9040 &self,
9041 completion: &InlineCompletionState,
9042 cursor_point: Point,
9043 style: &EditorStyle,
9044 cx: &mut Context<Editor>,
9045 ) -> Option<Div> {
9046 use text::ToPoint as _;
9047
9048 fn render_relative_row_jump(
9049 prefix: impl Into<String>,
9050 current_row: u32,
9051 target_row: u32,
9052 ) -> Div {
9053 let (row_diff, arrow) = if target_row < current_row {
9054 (current_row - target_row, IconName::ArrowUp)
9055 } else {
9056 (target_row - current_row, IconName::ArrowDown)
9057 };
9058
9059 h_flex()
9060 .child(
9061 Label::new(format!("{}{}", prefix.into(), row_diff))
9062 .color(Color::Muted)
9063 .size(LabelSize::Small),
9064 )
9065 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9066 }
9067
9068 match &completion.completion {
9069 InlineCompletion::Move {
9070 target, snapshot, ..
9071 } => Some(
9072 h_flex()
9073 .px_2()
9074 .gap_2()
9075 .flex_1()
9076 .child(
9077 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9078 Icon::new(IconName::ZedPredictDown)
9079 } else {
9080 Icon::new(IconName::ZedPredictUp)
9081 },
9082 )
9083 .child(Label::new("Jump to Edit")),
9084 ),
9085
9086 InlineCompletion::Edit {
9087 edits,
9088 edit_preview,
9089 snapshot,
9090 display_mode: _,
9091 } => {
9092 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9093
9094 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9095 &snapshot,
9096 &edits,
9097 edit_preview.as_ref()?,
9098 true,
9099 cx,
9100 )
9101 .first_line_preview();
9102
9103 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9104 .with_default_highlights(&style.text, highlighted_edits.highlights);
9105
9106 let preview = h_flex()
9107 .gap_1()
9108 .min_w_16()
9109 .child(styled_text)
9110 .when(has_more_lines, |parent| parent.child("…"));
9111
9112 let left = if first_edit_row != cursor_point.row {
9113 render_relative_row_jump("", cursor_point.row, first_edit_row)
9114 .into_any_element()
9115 } else {
9116 Icon::new(IconName::ZedPredict).into_any_element()
9117 };
9118
9119 Some(
9120 h_flex()
9121 .h_full()
9122 .flex_1()
9123 .gap_2()
9124 .pr_1()
9125 .overflow_x_hidden()
9126 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9127 .child(left)
9128 .child(preview),
9129 )
9130 }
9131 }
9132 }
9133
9134 pub fn render_context_menu(
9135 &self,
9136 style: &EditorStyle,
9137 max_height_in_lines: u32,
9138 window: &mut Window,
9139 cx: &mut Context<Editor>,
9140 ) -> Option<AnyElement> {
9141 let menu = self.context_menu.borrow();
9142 let menu = menu.as_ref()?;
9143 if !menu.visible() {
9144 return None;
9145 };
9146 Some(menu.render(style, max_height_in_lines, window, cx))
9147 }
9148
9149 fn render_context_menu_aside(
9150 &mut self,
9151 max_size: Size<Pixels>,
9152 window: &mut Window,
9153 cx: &mut Context<Editor>,
9154 ) -> Option<AnyElement> {
9155 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9156 if menu.visible() {
9157 menu.render_aside(max_size, window, cx)
9158 } else {
9159 None
9160 }
9161 })
9162 }
9163
9164 fn hide_context_menu(
9165 &mut self,
9166 window: &mut Window,
9167 cx: &mut Context<Self>,
9168 ) -> Option<CodeContextMenu> {
9169 cx.notify();
9170 self.completion_tasks.clear();
9171 let context_menu = self.context_menu.borrow_mut().take();
9172 self.stale_inline_completion_in_menu.take();
9173 self.update_visible_inline_completion(window, cx);
9174 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9175 if let Some(completion_provider) = &self.completion_provider {
9176 completion_provider.selection_changed(None, window, cx);
9177 }
9178 }
9179 context_menu
9180 }
9181
9182 fn show_snippet_choices(
9183 &mut self,
9184 choices: &Vec<String>,
9185 selection: Range<Anchor>,
9186 cx: &mut Context<Self>,
9187 ) {
9188 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9189 (Some(a), Some(b)) if a == b => a,
9190 _ => {
9191 log::error!("expected anchor range to have matching buffer IDs");
9192 return;
9193 }
9194 };
9195 let multi_buffer = self.buffer().read(cx);
9196 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9197 return;
9198 };
9199
9200 let id = post_inc(&mut self.next_completion_id);
9201 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9202 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9203 CompletionsMenu::new_snippet_choices(
9204 id,
9205 true,
9206 choices,
9207 selection,
9208 buffer,
9209 snippet_sort_order,
9210 ),
9211 ));
9212 }
9213
9214 pub fn insert_snippet(
9215 &mut self,
9216 insertion_ranges: &[Range<usize>],
9217 snippet: Snippet,
9218 window: &mut Window,
9219 cx: &mut Context<Self>,
9220 ) -> Result<()> {
9221 struct Tabstop<T> {
9222 is_end_tabstop: bool,
9223 ranges: Vec<Range<T>>,
9224 choices: Option<Vec<String>>,
9225 }
9226
9227 let tabstops = self.buffer.update(cx, |buffer, cx| {
9228 let snippet_text: Arc<str> = snippet.text.clone().into();
9229 let edits = insertion_ranges
9230 .iter()
9231 .cloned()
9232 .map(|range| (range, snippet_text.clone()));
9233 let autoindent_mode = AutoindentMode::Block {
9234 original_indent_columns: Vec::new(),
9235 };
9236 buffer.edit(edits, Some(autoindent_mode), cx);
9237
9238 let snapshot = &*buffer.read(cx);
9239 let snippet = &snippet;
9240 snippet
9241 .tabstops
9242 .iter()
9243 .map(|tabstop| {
9244 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9245 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9246 });
9247 let mut tabstop_ranges = tabstop
9248 .ranges
9249 .iter()
9250 .flat_map(|tabstop_range| {
9251 let mut delta = 0_isize;
9252 insertion_ranges.iter().map(move |insertion_range| {
9253 let insertion_start = insertion_range.start as isize + delta;
9254 delta +=
9255 snippet.text.len() as isize - insertion_range.len() as isize;
9256
9257 let start = ((insertion_start + tabstop_range.start) as usize)
9258 .min(snapshot.len());
9259 let end = ((insertion_start + tabstop_range.end) as usize)
9260 .min(snapshot.len());
9261 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9262 })
9263 })
9264 .collect::<Vec<_>>();
9265 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9266
9267 Tabstop {
9268 is_end_tabstop,
9269 ranges: tabstop_ranges,
9270 choices: tabstop.choices.clone(),
9271 }
9272 })
9273 .collect::<Vec<_>>()
9274 });
9275 if let Some(tabstop) = tabstops.first() {
9276 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9277 // Reverse order so that the first range is the newest created selection.
9278 // Completions will use it and autoscroll will prioritize it.
9279 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9280 });
9281
9282 if let Some(choices) = &tabstop.choices {
9283 if let Some(selection) = tabstop.ranges.first() {
9284 self.show_snippet_choices(choices, selection.clone(), cx)
9285 }
9286 }
9287
9288 // If we're already at the last tabstop and it's at the end of the snippet,
9289 // we're done, we don't need to keep the state around.
9290 if !tabstop.is_end_tabstop {
9291 let choices = tabstops
9292 .iter()
9293 .map(|tabstop| tabstop.choices.clone())
9294 .collect();
9295
9296 let ranges = tabstops
9297 .into_iter()
9298 .map(|tabstop| tabstop.ranges)
9299 .collect::<Vec<_>>();
9300
9301 self.snippet_stack.push(SnippetState {
9302 active_index: 0,
9303 ranges,
9304 choices,
9305 });
9306 }
9307
9308 // Check whether the just-entered snippet ends with an auto-closable bracket.
9309 if self.autoclose_regions.is_empty() {
9310 let snapshot = self.buffer.read(cx).snapshot(cx);
9311 for selection in &mut self.selections.all::<Point>(cx) {
9312 let selection_head = selection.head();
9313 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9314 continue;
9315 };
9316
9317 let mut bracket_pair = None;
9318 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9319 let prev_chars = snapshot
9320 .reversed_chars_at(selection_head)
9321 .collect::<String>();
9322 for (pair, enabled) in scope.brackets() {
9323 if enabled
9324 && pair.close
9325 && prev_chars.starts_with(pair.start.as_str())
9326 && next_chars.starts_with(pair.end.as_str())
9327 {
9328 bracket_pair = Some(pair.clone());
9329 break;
9330 }
9331 }
9332 if let Some(pair) = bracket_pair {
9333 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9334 let autoclose_enabled =
9335 self.use_autoclose && snapshot_settings.use_autoclose;
9336 if autoclose_enabled {
9337 let start = snapshot.anchor_after(selection_head);
9338 let end = snapshot.anchor_after(selection_head);
9339 self.autoclose_regions.push(AutocloseRegion {
9340 selection_id: selection.id,
9341 range: start..end,
9342 pair,
9343 });
9344 }
9345 }
9346 }
9347 }
9348 }
9349 Ok(())
9350 }
9351
9352 pub fn move_to_next_snippet_tabstop(
9353 &mut self,
9354 window: &mut Window,
9355 cx: &mut Context<Self>,
9356 ) -> bool {
9357 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9358 }
9359
9360 pub fn move_to_prev_snippet_tabstop(
9361 &mut self,
9362 window: &mut Window,
9363 cx: &mut Context<Self>,
9364 ) -> bool {
9365 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9366 }
9367
9368 pub fn move_to_snippet_tabstop(
9369 &mut self,
9370 bias: Bias,
9371 window: &mut Window,
9372 cx: &mut Context<Self>,
9373 ) -> bool {
9374 if let Some(mut snippet) = self.snippet_stack.pop() {
9375 match bias {
9376 Bias::Left => {
9377 if snippet.active_index > 0 {
9378 snippet.active_index -= 1;
9379 } else {
9380 self.snippet_stack.push(snippet);
9381 return false;
9382 }
9383 }
9384 Bias::Right => {
9385 if snippet.active_index + 1 < snippet.ranges.len() {
9386 snippet.active_index += 1;
9387 } else {
9388 self.snippet_stack.push(snippet);
9389 return false;
9390 }
9391 }
9392 }
9393 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9394 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9395 // Reverse order so that the first range is the newest created selection.
9396 // Completions will use it and autoscroll will prioritize it.
9397 s.select_ranges(current_ranges.iter().rev().cloned())
9398 });
9399
9400 if let Some(choices) = &snippet.choices[snippet.active_index] {
9401 if let Some(selection) = current_ranges.first() {
9402 self.show_snippet_choices(&choices, selection.clone(), cx);
9403 }
9404 }
9405
9406 // If snippet state is not at the last tabstop, push it back on the stack
9407 if snippet.active_index + 1 < snippet.ranges.len() {
9408 self.snippet_stack.push(snippet);
9409 }
9410 return true;
9411 }
9412 }
9413
9414 false
9415 }
9416
9417 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9418 self.transact(window, cx, |this, window, cx| {
9419 this.select_all(&SelectAll, window, cx);
9420 this.insert("", window, cx);
9421 });
9422 }
9423
9424 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9426 self.transact(window, cx, |this, window, cx| {
9427 this.select_autoclose_pair(window, cx);
9428 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9429 if !this.linked_edit_ranges.is_empty() {
9430 let selections = this.selections.all::<MultiBufferPoint>(cx);
9431 let snapshot = this.buffer.read(cx).snapshot(cx);
9432
9433 for selection in selections.iter() {
9434 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9435 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9436 if selection_start.buffer_id != selection_end.buffer_id {
9437 continue;
9438 }
9439 if let Some(ranges) =
9440 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9441 {
9442 for (buffer, entries) in ranges {
9443 linked_ranges.entry(buffer).or_default().extend(entries);
9444 }
9445 }
9446 }
9447 }
9448
9449 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9450 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9451 for selection in &mut selections {
9452 if selection.is_empty() {
9453 let old_head = selection.head();
9454 let mut new_head =
9455 movement::left(&display_map, old_head.to_display_point(&display_map))
9456 .to_point(&display_map);
9457 if let Some((buffer, line_buffer_range)) = display_map
9458 .buffer_snapshot
9459 .buffer_line_for_row(MultiBufferRow(old_head.row))
9460 {
9461 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9462 let indent_len = match indent_size.kind {
9463 IndentKind::Space => {
9464 buffer.settings_at(line_buffer_range.start, cx).tab_size
9465 }
9466 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9467 };
9468 if old_head.column <= indent_size.len && old_head.column > 0 {
9469 let indent_len = indent_len.get();
9470 new_head = cmp::min(
9471 new_head,
9472 MultiBufferPoint::new(
9473 old_head.row,
9474 ((old_head.column - 1) / indent_len) * indent_len,
9475 ),
9476 );
9477 }
9478 }
9479
9480 selection.set_head(new_head, SelectionGoal::None);
9481 }
9482 }
9483
9484 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9485 s.select(selections)
9486 });
9487 this.insert("", window, cx);
9488 let empty_str: Arc<str> = Arc::from("");
9489 for (buffer, edits) in linked_ranges {
9490 let snapshot = buffer.read(cx).snapshot();
9491 use text::ToPoint as TP;
9492
9493 let edits = edits
9494 .into_iter()
9495 .map(|range| {
9496 let end_point = TP::to_point(&range.end, &snapshot);
9497 let mut start_point = TP::to_point(&range.start, &snapshot);
9498
9499 if end_point == start_point {
9500 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9501 .saturating_sub(1);
9502 start_point =
9503 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9504 };
9505
9506 (start_point..end_point, empty_str.clone())
9507 })
9508 .sorted_by_key(|(range, _)| range.start)
9509 .collect::<Vec<_>>();
9510 buffer.update(cx, |this, cx| {
9511 this.edit(edits, None, cx);
9512 })
9513 }
9514 this.refresh_inline_completion(true, false, window, cx);
9515 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9516 });
9517 }
9518
9519 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9521 self.transact(window, cx, |this, window, cx| {
9522 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9523 s.move_with(|map, selection| {
9524 if selection.is_empty() {
9525 let cursor = movement::right(map, selection.head());
9526 selection.end = cursor;
9527 selection.reversed = true;
9528 selection.goal = SelectionGoal::None;
9529 }
9530 })
9531 });
9532 this.insert("", window, cx);
9533 this.refresh_inline_completion(true, false, window, cx);
9534 });
9535 }
9536
9537 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9538 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9539 if self.move_to_prev_snippet_tabstop(window, cx) {
9540 return;
9541 }
9542 self.outdent(&Outdent, window, cx);
9543 }
9544
9545 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9546 if self.move_to_next_snippet_tabstop(window, cx) {
9547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9548 return;
9549 }
9550 if self.read_only(cx) {
9551 return;
9552 }
9553 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9554 let mut selections = self.selections.all_adjusted(cx);
9555 let buffer = self.buffer.read(cx);
9556 let snapshot = buffer.snapshot(cx);
9557 let rows_iter = selections.iter().map(|s| s.head().row);
9558 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9559
9560 let has_some_cursor_in_whitespace = selections
9561 .iter()
9562 .filter(|selection| selection.is_empty())
9563 .any(|selection| {
9564 let cursor = selection.head();
9565 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9566 cursor.column < current_indent.len
9567 });
9568
9569 let mut edits = Vec::new();
9570 let mut prev_edited_row = 0;
9571 let mut row_delta = 0;
9572 for selection in &mut selections {
9573 if selection.start.row != prev_edited_row {
9574 row_delta = 0;
9575 }
9576 prev_edited_row = selection.end.row;
9577
9578 // If the selection is non-empty, then increase the indentation of the selected lines.
9579 if !selection.is_empty() {
9580 row_delta =
9581 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9582 continue;
9583 }
9584
9585 let cursor = selection.head();
9586 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9587 if let Some(suggested_indent) =
9588 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9589 {
9590 // Don't do anything if already at suggested indent
9591 // and there is any other cursor which is not
9592 if has_some_cursor_in_whitespace
9593 && cursor.column == current_indent.len
9594 && current_indent.len == suggested_indent.len
9595 {
9596 continue;
9597 }
9598
9599 // Adjust line and move cursor to suggested indent
9600 // if cursor is not at suggested indent
9601 if cursor.column < suggested_indent.len
9602 && cursor.column <= current_indent.len
9603 && current_indent.len <= suggested_indent.len
9604 {
9605 selection.start = Point::new(cursor.row, suggested_indent.len);
9606 selection.end = selection.start;
9607 if row_delta == 0 {
9608 edits.extend(Buffer::edit_for_indent_size_adjustment(
9609 cursor.row,
9610 current_indent,
9611 suggested_indent,
9612 ));
9613 row_delta = suggested_indent.len - current_indent.len;
9614 }
9615 continue;
9616 }
9617
9618 // If current indent is more than suggested indent
9619 // only move cursor to current indent and skip indent
9620 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9621 selection.start = Point::new(cursor.row, current_indent.len);
9622 selection.end = selection.start;
9623 continue;
9624 }
9625 }
9626
9627 // Otherwise, insert a hard or soft tab.
9628 let settings = buffer.language_settings_at(cursor, cx);
9629 let tab_size = if settings.hard_tabs {
9630 IndentSize::tab()
9631 } else {
9632 let tab_size = settings.tab_size.get();
9633 let indent_remainder = snapshot
9634 .text_for_range(Point::new(cursor.row, 0)..cursor)
9635 .flat_map(str::chars)
9636 .fold(row_delta % tab_size, |counter: u32, c| {
9637 if c == '\t' {
9638 0
9639 } else {
9640 (counter + 1) % tab_size
9641 }
9642 });
9643
9644 let chars_to_next_tab_stop = tab_size - indent_remainder;
9645 IndentSize::spaces(chars_to_next_tab_stop)
9646 };
9647 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9648 selection.end = selection.start;
9649 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9650 row_delta += tab_size.len;
9651 }
9652
9653 self.transact(window, cx, |this, window, cx| {
9654 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9655 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9656 s.select(selections)
9657 });
9658 this.refresh_inline_completion(true, false, window, cx);
9659 });
9660 }
9661
9662 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9663 if self.read_only(cx) {
9664 return;
9665 }
9666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9667 let mut selections = self.selections.all::<Point>(cx);
9668 let mut prev_edited_row = 0;
9669 let mut row_delta = 0;
9670 let mut edits = Vec::new();
9671 let buffer = self.buffer.read(cx);
9672 let snapshot = buffer.snapshot(cx);
9673 for selection in &mut selections {
9674 if selection.start.row != prev_edited_row {
9675 row_delta = 0;
9676 }
9677 prev_edited_row = selection.end.row;
9678
9679 row_delta =
9680 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9681 }
9682
9683 self.transact(window, cx, |this, window, cx| {
9684 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9685 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9686 s.select(selections)
9687 });
9688 });
9689 }
9690
9691 fn indent_selection(
9692 buffer: &MultiBuffer,
9693 snapshot: &MultiBufferSnapshot,
9694 selection: &mut Selection<Point>,
9695 edits: &mut Vec<(Range<Point>, String)>,
9696 delta_for_start_row: u32,
9697 cx: &App,
9698 ) -> u32 {
9699 let settings = buffer.language_settings_at(selection.start, cx);
9700 let tab_size = settings.tab_size.get();
9701 let indent_kind = if settings.hard_tabs {
9702 IndentKind::Tab
9703 } else {
9704 IndentKind::Space
9705 };
9706 let mut start_row = selection.start.row;
9707 let mut end_row = selection.end.row + 1;
9708
9709 // If a selection ends at the beginning of a line, don't indent
9710 // that last line.
9711 if selection.end.column == 0 && selection.end.row > selection.start.row {
9712 end_row -= 1;
9713 }
9714
9715 // Avoid re-indenting a row that has already been indented by a
9716 // previous selection, but still update this selection's column
9717 // to reflect that indentation.
9718 if delta_for_start_row > 0 {
9719 start_row += 1;
9720 selection.start.column += delta_for_start_row;
9721 if selection.end.row == selection.start.row {
9722 selection.end.column += delta_for_start_row;
9723 }
9724 }
9725
9726 let mut delta_for_end_row = 0;
9727 let has_multiple_rows = start_row + 1 != end_row;
9728 for row in start_row..end_row {
9729 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9730 let indent_delta = match (current_indent.kind, indent_kind) {
9731 (IndentKind::Space, IndentKind::Space) => {
9732 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9733 IndentSize::spaces(columns_to_next_tab_stop)
9734 }
9735 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9736 (_, IndentKind::Tab) => IndentSize::tab(),
9737 };
9738
9739 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9740 0
9741 } else {
9742 selection.start.column
9743 };
9744 let row_start = Point::new(row, start);
9745 edits.push((
9746 row_start..row_start,
9747 indent_delta.chars().collect::<String>(),
9748 ));
9749
9750 // Update this selection's endpoints to reflect the indentation.
9751 if row == selection.start.row {
9752 selection.start.column += indent_delta.len;
9753 }
9754 if row == selection.end.row {
9755 selection.end.column += indent_delta.len;
9756 delta_for_end_row = indent_delta.len;
9757 }
9758 }
9759
9760 if selection.start.row == selection.end.row {
9761 delta_for_start_row + delta_for_end_row
9762 } else {
9763 delta_for_end_row
9764 }
9765 }
9766
9767 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9768 if self.read_only(cx) {
9769 return;
9770 }
9771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9772 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9773 let selections = self.selections.all::<Point>(cx);
9774 let mut deletion_ranges = Vec::new();
9775 let mut last_outdent = None;
9776 {
9777 let buffer = self.buffer.read(cx);
9778 let snapshot = buffer.snapshot(cx);
9779 for selection in &selections {
9780 let settings = buffer.language_settings_at(selection.start, cx);
9781 let tab_size = settings.tab_size.get();
9782 let mut rows = selection.spanned_rows(false, &display_map);
9783
9784 // Avoid re-outdenting a row that has already been outdented by a
9785 // previous selection.
9786 if let Some(last_row) = last_outdent {
9787 if last_row == rows.start {
9788 rows.start = rows.start.next_row();
9789 }
9790 }
9791 let has_multiple_rows = rows.len() > 1;
9792 for row in rows.iter_rows() {
9793 let indent_size = snapshot.indent_size_for_line(row);
9794 if indent_size.len > 0 {
9795 let deletion_len = match indent_size.kind {
9796 IndentKind::Space => {
9797 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9798 if columns_to_prev_tab_stop == 0 {
9799 tab_size
9800 } else {
9801 columns_to_prev_tab_stop
9802 }
9803 }
9804 IndentKind::Tab => 1,
9805 };
9806 let start = if has_multiple_rows
9807 || deletion_len > selection.start.column
9808 || indent_size.len < selection.start.column
9809 {
9810 0
9811 } else {
9812 selection.start.column - deletion_len
9813 };
9814 deletion_ranges.push(
9815 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9816 );
9817 last_outdent = Some(row);
9818 }
9819 }
9820 }
9821 }
9822
9823 self.transact(window, cx, |this, window, cx| {
9824 this.buffer.update(cx, |buffer, cx| {
9825 let empty_str: Arc<str> = Arc::default();
9826 buffer.edit(
9827 deletion_ranges
9828 .into_iter()
9829 .map(|range| (range, empty_str.clone())),
9830 None,
9831 cx,
9832 );
9833 });
9834 let selections = this.selections.all::<usize>(cx);
9835 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9836 s.select(selections)
9837 });
9838 });
9839 }
9840
9841 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9842 if self.read_only(cx) {
9843 return;
9844 }
9845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9846 let selections = self
9847 .selections
9848 .all::<usize>(cx)
9849 .into_iter()
9850 .map(|s| s.range());
9851
9852 self.transact(window, cx, |this, window, cx| {
9853 this.buffer.update(cx, |buffer, cx| {
9854 buffer.autoindent_ranges(selections, cx);
9855 });
9856 let selections = this.selections.all::<usize>(cx);
9857 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9858 s.select(selections)
9859 });
9860 });
9861 }
9862
9863 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9865 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9866 let selections = self.selections.all::<Point>(cx);
9867
9868 let mut new_cursors = Vec::new();
9869 let mut edit_ranges = Vec::new();
9870 let mut selections = selections.iter().peekable();
9871 while let Some(selection) = selections.next() {
9872 let mut rows = selection.spanned_rows(false, &display_map);
9873 let goal_display_column = selection.head().to_display_point(&display_map).column();
9874
9875 // Accumulate contiguous regions of rows that we want to delete.
9876 while let Some(next_selection) = selections.peek() {
9877 let next_rows = next_selection.spanned_rows(false, &display_map);
9878 if next_rows.start <= rows.end {
9879 rows.end = next_rows.end;
9880 selections.next().unwrap();
9881 } else {
9882 break;
9883 }
9884 }
9885
9886 let buffer = &display_map.buffer_snapshot;
9887 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9888 let edit_end;
9889 let cursor_buffer_row;
9890 if buffer.max_point().row >= rows.end.0 {
9891 // If there's a line after the range, delete the \n from the end of the row range
9892 // and position the cursor on the next line.
9893 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9894 cursor_buffer_row = rows.end;
9895 } else {
9896 // If there isn't a line after the range, delete the \n from the line before the
9897 // start of the row range and position the cursor there.
9898 edit_start = edit_start.saturating_sub(1);
9899 edit_end = buffer.len();
9900 cursor_buffer_row = rows.start.previous_row();
9901 }
9902
9903 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9904 *cursor.column_mut() =
9905 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9906
9907 new_cursors.push((
9908 selection.id,
9909 buffer.anchor_after(cursor.to_point(&display_map)),
9910 ));
9911 edit_ranges.push(edit_start..edit_end);
9912 }
9913
9914 self.transact(window, cx, |this, window, cx| {
9915 let buffer = this.buffer.update(cx, |buffer, cx| {
9916 let empty_str: Arc<str> = Arc::default();
9917 buffer.edit(
9918 edit_ranges
9919 .into_iter()
9920 .map(|range| (range, empty_str.clone())),
9921 None,
9922 cx,
9923 );
9924 buffer.snapshot(cx)
9925 });
9926 let new_selections = new_cursors
9927 .into_iter()
9928 .map(|(id, cursor)| {
9929 let cursor = cursor.to_point(&buffer);
9930 Selection {
9931 id,
9932 start: cursor,
9933 end: cursor,
9934 reversed: false,
9935 goal: SelectionGoal::None,
9936 }
9937 })
9938 .collect();
9939
9940 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9941 s.select(new_selections);
9942 });
9943 });
9944 }
9945
9946 pub fn join_lines_impl(
9947 &mut self,
9948 insert_whitespace: bool,
9949 window: &mut Window,
9950 cx: &mut Context<Self>,
9951 ) {
9952 if self.read_only(cx) {
9953 return;
9954 }
9955 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9956 for selection in self.selections.all::<Point>(cx) {
9957 let start = MultiBufferRow(selection.start.row);
9958 // Treat single line selections as if they include the next line. Otherwise this action
9959 // would do nothing for single line selections individual cursors.
9960 let end = if selection.start.row == selection.end.row {
9961 MultiBufferRow(selection.start.row + 1)
9962 } else {
9963 MultiBufferRow(selection.end.row)
9964 };
9965
9966 if let Some(last_row_range) = row_ranges.last_mut() {
9967 if start <= last_row_range.end {
9968 last_row_range.end = end;
9969 continue;
9970 }
9971 }
9972 row_ranges.push(start..end);
9973 }
9974
9975 let snapshot = self.buffer.read(cx).snapshot(cx);
9976 let mut cursor_positions = Vec::new();
9977 for row_range in &row_ranges {
9978 let anchor = snapshot.anchor_before(Point::new(
9979 row_range.end.previous_row().0,
9980 snapshot.line_len(row_range.end.previous_row()),
9981 ));
9982 cursor_positions.push(anchor..anchor);
9983 }
9984
9985 self.transact(window, cx, |this, window, cx| {
9986 for row_range in row_ranges.into_iter().rev() {
9987 for row in row_range.iter_rows().rev() {
9988 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9989 let next_line_row = row.next_row();
9990 let indent = snapshot.indent_size_for_line(next_line_row);
9991 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9992
9993 let replace =
9994 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9995 " "
9996 } else {
9997 ""
9998 };
9999
10000 this.buffer.update(cx, |buffer, cx| {
10001 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10002 });
10003 }
10004 }
10005
10006 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10007 s.select_anchor_ranges(cursor_positions)
10008 });
10009 });
10010 }
10011
10012 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10013 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10014 self.join_lines_impl(true, window, cx);
10015 }
10016
10017 pub fn sort_lines_case_sensitive(
10018 &mut self,
10019 _: &SortLinesCaseSensitive,
10020 window: &mut Window,
10021 cx: &mut Context<Self>,
10022 ) {
10023 self.manipulate_lines(window, cx, |lines| lines.sort())
10024 }
10025
10026 pub fn sort_lines_case_insensitive(
10027 &mut self,
10028 _: &SortLinesCaseInsensitive,
10029 window: &mut Window,
10030 cx: &mut Context<Self>,
10031 ) {
10032 self.manipulate_lines(window, cx, |lines| {
10033 lines.sort_by_key(|line| line.to_lowercase())
10034 })
10035 }
10036
10037 pub fn unique_lines_case_insensitive(
10038 &mut self,
10039 _: &UniqueLinesCaseInsensitive,
10040 window: &mut Window,
10041 cx: &mut Context<Self>,
10042 ) {
10043 self.manipulate_lines(window, cx, |lines| {
10044 let mut seen = HashSet::default();
10045 lines.retain(|line| seen.insert(line.to_lowercase()));
10046 })
10047 }
10048
10049 pub fn unique_lines_case_sensitive(
10050 &mut self,
10051 _: &UniqueLinesCaseSensitive,
10052 window: &mut Window,
10053 cx: &mut Context<Self>,
10054 ) {
10055 self.manipulate_lines(window, cx, |lines| {
10056 let mut seen = HashSet::default();
10057 lines.retain(|line| seen.insert(*line));
10058 })
10059 }
10060
10061 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10062 let Some(project) = self.project.clone() else {
10063 return;
10064 };
10065 self.reload(project, window, cx)
10066 .detach_and_notify_err(window, cx);
10067 }
10068
10069 pub fn restore_file(
10070 &mut self,
10071 _: &::git::RestoreFile,
10072 window: &mut Window,
10073 cx: &mut Context<Self>,
10074 ) {
10075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10076 let mut buffer_ids = HashSet::default();
10077 let snapshot = self.buffer().read(cx).snapshot(cx);
10078 for selection in self.selections.all::<usize>(cx) {
10079 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10080 }
10081
10082 let buffer = self.buffer().read(cx);
10083 let ranges = buffer_ids
10084 .into_iter()
10085 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10086 .collect::<Vec<_>>();
10087
10088 self.restore_hunks_in_ranges(ranges, window, cx);
10089 }
10090
10091 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10092 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10093 let selections = self
10094 .selections
10095 .all(cx)
10096 .into_iter()
10097 .map(|s| s.range())
10098 .collect();
10099 self.restore_hunks_in_ranges(selections, window, cx);
10100 }
10101
10102 pub fn restore_hunks_in_ranges(
10103 &mut self,
10104 ranges: Vec<Range<Point>>,
10105 window: &mut Window,
10106 cx: &mut Context<Editor>,
10107 ) {
10108 let mut revert_changes = HashMap::default();
10109 let chunk_by = self
10110 .snapshot(window, cx)
10111 .hunks_for_ranges(ranges)
10112 .into_iter()
10113 .chunk_by(|hunk| hunk.buffer_id);
10114 for (buffer_id, hunks) in &chunk_by {
10115 let hunks = hunks.collect::<Vec<_>>();
10116 for hunk in &hunks {
10117 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10118 }
10119 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10120 }
10121 drop(chunk_by);
10122 if !revert_changes.is_empty() {
10123 self.transact(window, cx, |editor, window, cx| {
10124 editor.restore(revert_changes, window, cx);
10125 });
10126 }
10127 }
10128
10129 pub fn open_active_item_in_terminal(
10130 &mut self,
10131 _: &OpenInTerminal,
10132 window: &mut Window,
10133 cx: &mut Context<Self>,
10134 ) {
10135 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10136 let project_path = buffer.read(cx).project_path(cx)?;
10137 let project = self.project.as_ref()?.read(cx);
10138 let entry = project.entry_for_path(&project_path, cx)?;
10139 let parent = match &entry.canonical_path {
10140 Some(canonical_path) => canonical_path.to_path_buf(),
10141 None => project.absolute_path(&project_path, cx)?,
10142 }
10143 .parent()?
10144 .to_path_buf();
10145 Some(parent)
10146 }) {
10147 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10148 }
10149 }
10150
10151 fn set_breakpoint_context_menu(
10152 &mut self,
10153 display_row: DisplayRow,
10154 position: Option<Anchor>,
10155 clicked_point: gpui::Point<Pixels>,
10156 window: &mut Window,
10157 cx: &mut Context<Self>,
10158 ) {
10159 let source = self
10160 .buffer
10161 .read(cx)
10162 .snapshot(cx)
10163 .anchor_before(Point::new(display_row.0, 0u32));
10164
10165 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10166
10167 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10168 self,
10169 source,
10170 clicked_point,
10171 context_menu,
10172 window,
10173 cx,
10174 );
10175 }
10176
10177 fn add_edit_breakpoint_block(
10178 &mut self,
10179 anchor: Anchor,
10180 breakpoint: &Breakpoint,
10181 edit_action: BreakpointPromptEditAction,
10182 window: &mut Window,
10183 cx: &mut Context<Self>,
10184 ) {
10185 let weak_editor = cx.weak_entity();
10186 let bp_prompt = cx.new(|cx| {
10187 BreakpointPromptEditor::new(
10188 weak_editor,
10189 anchor,
10190 breakpoint.clone(),
10191 edit_action,
10192 window,
10193 cx,
10194 )
10195 });
10196
10197 let height = bp_prompt.update(cx, |this, cx| {
10198 this.prompt
10199 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10200 });
10201 let cloned_prompt = bp_prompt.clone();
10202 let blocks = vec![BlockProperties {
10203 style: BlockStyle::Sticky,
10204 placement: BlockPlacement::Above(anchor),
10205 height: Some(height),
10206 render: Arc::new(move |cx| {
10207 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10208 cloned_prompt.clone().into_any_element()
10209 }),
10210 priority: 0,
10211 render_in_minimap: true,
10212 }];
10213
10214 let focus_handle = bp_prompt.focus_handle(cx);
10215 window.focus(&focus_handle);
10216
10217 let block_ids = self.insert_blocks(blocks, None, cx);
10218 bp_prompt.update(cx, |prompt, _| {
10219 prompt.add_block_ids(block_ids);
10220 });
10221 }
10222
10223 pub(crate) fn breakpoint_at_row(
10224 &self,
10225 row: u32,
10226 window: &mut Window,
10227 cx: &mut Context<Self>,
10228 ) -> Option<(Anchor, Breakpoint)> {
10229 let snapshot = self.snapshot(window, cx);
10230 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10231
10232 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10233 }
10234
10235 pub(crate) fn breakpoint_at_anchor(
10236 &self,
10237 breakpoint_position: Anchor,
10238 snapshot: &EditorSnapshot,
10239 cx: &mut Context<Self>,
10240 ) -> Option<(Anchor, Breakpoint)> {
10241 let project = self.project.clone()?;
10242
10243 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10244 snapshot
10245 .buffer_snapshot
10246 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10247 })?;
10248
10249 let enclosing_excerpt = breakpoint_position.excerpt_id;
10250 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10251 let buffer_snapshot = buffer.read(cx).snapshot();
10252
10253 let row = buffer_snapshot
10254 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10255 .row;
10256
10257 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10258 let anchor_end = snapshot
10259 .buffer_snapshot
10260 .anchor_after(Point::new(row, line_len));
10261
10262 let bp = self
10263 .breakpoint_store
10264 .as_ref()?
10265 .read_with(cx, |breakpoint_store, cx| {
10266 breakpoint_store
10267 .breakpoints(
10268 &buffer,
10269 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10270 &buffer_snapshot,
10271 cx,
10272 )
10273 .next()
10274 .and_then(|(bp, _)| {
10275 let breakpoint_row = buffer_snapshot
10276 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10277 .row;
10278
10279 if breakpoint_row == row {
10280 snapshot
10281 .buffer_snapshot
10282 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10283 .map(|position| (position, bp.bp.clone()))
10284 } else {
10285 None
10286 }
10287 })
10288 });
10289 bp
10290 }
10291
10292 pub fn edit_log_breakpoint(
10293 &mut self,
10294 _: &EditLogBreakpoint,
10295 window: &mut Window,
10296 cx: &mut Context<Self>,
10297 ) {
10298 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10299 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10300 message: None,
10301 state: BreakpointState::Enabled,
10302 condition: None,
10303 hit_condition: None,
10304 });
10305
10306 self.add_edit_breakpoint_block(
10307 anchor,
10308 &breakpoint,
10309 BreakpointPromptEditAction::Log,
10310 window,
10311 cx,
10312 );
10313 }
10314 }
10315
10316 fn breakpoints_at_cursors(
10317 &self,
10318 window: &mut Window,
10319 cx: &mut Context<Self>,
10320 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10321 let snapshot = self.snapshot(window, cx);
10322 let cursors = self
10323 .selections
10324 .disjoint_anchors()
10325 .into_iter()
10326 .map(|selection| {
10327 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10328
10329 let breakpoint_position = self
10330 .breakpoint_at_row(cursor_position.row, window, cx)
10331 .map(|bp| bp.0)
10332 .unwrap_or_else(|| {
10333 snapshot
10334 .display_snapshot
10335 .buffer_snapshot
10336 .anchor_after(Point::new(cursor_position.row, 0))
10337 });
10338
10339 let breakpoint = self
10340 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10341 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10342
10343 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10344 })
10345 // 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.
10346 .collect::<HashMap<Anchor, _>>();
10347
10348 cursors.into_iter().collect()
10349 }
10350
10351 pub fn enable_breakpoint(
10352 &mut self,
10353 _: &crate::actions::EnableBreakpoint,
10354 window: &mut Window,
10355 cx: &mut Context<Self>,
10356 ) {
10357 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10358 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10359 continue;
10360 };
10361 self.edit_breakpoint_at_anchor(
10362 anchor,
10363 breakpoint,
10364 BreakpointEditAction::InvertState,
10365 cx,
10366 );
10367 }
10368 }
10369
10370 pub fn disable_breakpoint(
10371 &mut self,
10372 _: &crate::actions::DisableBreakpoint,
10373 window: &mut Window,
10374 cx: &mut Context<Self>,
10375 ) {
10376 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10377 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10378 continue;
10379 };
10380 self.edit_breakpoint_at_anchor(
10381 anchor,
10382 breakpoint,
10383 BreakpointEditAction::InvertState,
10384 cx,
10385 );
10386 }
10387 }
10388
10389 pub fn toggle_breakpoint(
10390 &mut self,
10391 _: &crate::actions::ToggleBreakpoint,
10392 window: &mut Window,
10393 cx: &mut Context<Self>,
10394 ) {
10395 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10396 if let Some(breakpoint) = breakpoint {
10397 self.edit_breakpoint_at_anchor(
10398 anchor,
10399 breakpoint,
10400 BreakpointEditAction::Toggle,
10401 cx,
10402 );
10403 } else {
10404 self.edit_breakpoint_at_anchor(
10405 anchor,
10406 Breakpoint::new_standard(),
10407 BreakpointEditAction::Toggle,
10408 cx,
10409 );
10410 }
10411 }
10412 }
10413
10414 pub fn edit_breakpoint_at_anchor(
10415 &mut self,
10416 breakpoint_position: Anchor,
10417 breakpoint: Breakpoint,
10418 edit_action: BreakpointEditAction,
10419 cx: &mut Context<Self>,
10420 ) {
10421 let Some(breakpoint_store) = &self.breakpoint_store else {
10422 return;
10423 };
10424
10425 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10426 if breakpoint_position == Anchor::min() {
10427 self.buffer()
10428 .read(cx)
10429 .excerpt_buffer_ids()
10430 .into_iter()
10431 .next()
10432 } else {
10433 None
10434 }
10435 }) else {
10436 return;
10437 };
10438
10439 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10440 return;
10441 };
10442
10443 breakpoint_store.update(cx, |breakpoint_store, cx| {
10444 breakpoint_store.toggle_breakpoint(
10445 buffer,
10446 BreakpointWithPosition {
10447 position: breakpoint_position.text_anchor,
10448 bp: breakpoint,
10449 },
10450 edit_action,
10451 cx,
10452 );
10453 });
10454
10455 cx.notify();
10456 }
10457
10458 #[cfg(any(test, feature = "test-support"))]
10459 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10460 self.breakpoint_store.clone()
10461 }
10462
10463 pub fn prepare_restore_change(
10464 &self,
10465 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10466 hunk: &MultiBufferDiffHunk,
10467 cx: &mut App,
10468 ) -> Option<()> {
10469 if hunk.is_created_file() {
10470 return None;
10471 }
10472 let buffer = self.buffer.read(cx);
10473 let diff = buffer.diff_for(hunk.buffer_id)?;
10474 let buffer = buffer.buffer(hunk.buffer_id)?;
10475 let buffer = buffer.read(cx);
10476 let original_text = diff
10477 .read(cx)
10478 .base_text()
10479 .as_rope()
10480 .slice(hunk.diff_base_byte_range.clone());
10481 let buffer_snapshot = buffer.snapshot();
10482 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10483 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10484 probe
10485 .0
10486 .start
10487 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10488 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10489 }) {
10490 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10491 Some(())
10492 } else {
10493 None
10494 }
10495 }
10496
10497 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10498 self.manipulate_lines(window, cx, |lines| lines.reverse())
10499 }
10500
10501 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10502 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10503 }
10504
10505 fn manipulate_lines<Fn>(
10506 &mut self,
10507 window: &mut Window,
10508 cx: &mut Context<Self>,
10509 mut callback: Fn,
10510 ) where
10511 Fn: FnMut(&mut Vec<&str>),
10512 {
10513 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10514
10515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10516 let buffer = self.buffer.read(cx).snapshot(cx);
10517
10518 let mut edits = Vec::new();
10519
10520 let selections = self.selections.all::<Point>(cx);
10521 let mut selections = selections.iter().peekable();
10522 let mut contiguous_row_selections = Vec::new();
10523 let mut new_selections = Vec::new();
10524 let mut added_lines = 0;
10525 let mut removed_lines = 0;
10526
10527 while let Some(selection) = selections.next() {
10528 let (start_row, end_row) = consume_contiguous_rows(
10529 &mut contiguous_row_selections,
10530 selection,
10531 &display_map,
10532 &mut selections,
10533 );
10534
10535 let start_point = Point::new(start_row.0, 0);
10536 let end_point = Point::new(
10537 end_row.previous_row().0,
10538 buffer.line_len(end_row.previous_row()),
10539 );
10540 let text = buffer
10541 .text_for_range(start_point..end_point)
10542 .collect::<String>();
10543
10544 let mut lines = text.split('\n').collect_vec();
10545
10546 let lines_before = lines.len();
10547 callback(&mut lines);
10548 let lines_after = lines.len();
10549
10550 edits.push((start_point..end_point, lines.join("\n")));
10551
10552 // Selections must change based on added and removed line count
10553 let start_row =
10554 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10555 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10556 new_selections.push(Selection {
10557 id: selection.id,
10558 start: start_row,
10559 end: end_row,
10560 goal: SelectionGoal::None,
10561 reversed: selection.reversed,
10562 });
10563
10564 if lines_after > lines_before {
10565 added_lines += lines_after - lines_before;
10566 } else if lines_before > lines_after {
10567 removed_lines += lines_before - lines_after;
10568 }
10569 }
10570
10571 self.transact(window, cx, |this, window, cx| {
10572 let buffer = this.buffer.update(cx, |buffer, cx| {
10573 buffer.edit(edits, None, cx);
10574 buffer.snapshot(cx)
10575 });
10576
10577 // Recalculate offsets on newly edited buffer
10578 let new_selections = new_selections
10579 .iter()
10580 .map(|s| {
10581 let start_point = Point::new(s.start.0, 0);
10582 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10583 Selection {
10584 id: s.id,
10585 start: buffer.point_to_offset(start_point),
10586 end: buffer.point_to_offset(end_point),
10587 goal: s.goal,
10588 reversed: s.reversed,
10589 }
10590 })
10591 .collect();
10592
10593 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10594 s.select(new_selections);
10595 });
10596
10597 this.request_autoscroll(Autoscroll::fit(), cx);
10598 });
10599 }
10600
10601 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10602 self.manipulate_text(window, cx, |text| {
10603 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10604 if has_upper_case_characters {
10605 text.to_lowercase()
10606 } else {
10607 text.to_uppercase()
10608 }
10609 })
10610 }
10611
10612 pub fn convert_to_upper_case(
10613 &mut self,
10614 _: &ConvertToUpperCase,
10615 window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 self.manipulate_text(window, cx, |text| text.to_uppercase())
10619 }
10620
10621 pub fn convert_to_lower_case(
10622 &mut self,
10623 _: &ConvertToLowerCase,
10624 window: &mut Window,
10625 cx: &mut Context<Self>,
10626 ) {
10627 self.manipulate_text(window, cx, |text| text.to_lowercase())
10628 }
10629
10630 pub fn convert_to_title_case(
10631 &mut self,
10632 _: &ConvertToTitleCase,
10633 window: &mut Window,
10634 cx: &mut Context<Self>,
10635 ) {
10636 self.manipulate_text(window, cx, |text| {
10637 text.split('\n')
10638 .map(|line| line.to_case(Case::Title))
10639 .join("\n")
10640 })
10641 }
10642
10643 pub fn convert_to_snake_case(
10644 &mut self,
10645 _: &ConvertToSnakeCase,
10646 window: &mut Window,
10647 cx: &mut Context<Self>,
10648 ) {
10649 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10650 }
10651
10652 pub fn convert_to_kebab_case(
10653 &mut self,
10654 _: &ConvertToKebabCase,
10655 window: &mut Window,
10656 cx: &mut Context<Self>,
10657 ) {
10658 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10659 }
10660
10661 pub fn convert_to_upper_camel_case(
10662 &mut self,
10663 _: &ConvertToUpperCamelCase,
10664 window: &mut Window,
10665 cx: &mut Context<Self>,
10666 ) {
10667 self.manipulate_text(window, cx, |text| {
10668 text.split('\n')
10669 .map(|line| line.to_case(Case::UpperCamel))
10670 .join("\n")
10671 })
10672 }
10673
10674 pub fn convert_to_lower_camel_case(
10675 &mut self,
10676 _: &ConvertToLowerCamelCase,
10677 window: &mut Window,
10678 cx: &mut Context<Self>,
10679 ) {
10680 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10681 }
10682
10683 pub fn convert_to_opposite_case(
10684 &mut self,
10685 _: &ConvertToOppositeCase,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 self.manipulate_text(window, cx, |text| {
10690 text.chars()
10691 .fold(String::with_capacity(text.len()), |mut t, c| {
10692 if c.is_uppercase() {
10693 t.extend(c.to_lowercase());
10694 } else {
10695 t.extend(c.to_uppercase());
10696 }
10697 t
10698 })
10699 })
10700 }
10701
10702 pub fn convert_to_rot13(
10703 &mut self,
10704 _: &ConvertToRot13,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 ) {
10708 self.manipulate_text(window, cx, |text| {
10709 text.chars()
10710 .map(|c| match c {
10711 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10712 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10713 _ => c,
10714 })
10715 .collect()
10716 })
10717 }
10718
10719 pub fn convert_to_rot47(
10720 &mut self,
10721 _: &ConvertToRot47,
10722 window: &mut Window,
10723 cx: &mut Context<Self>,
10724 ) {
10725 self.manipulate_text(window, cx, |text| {
10726 text.chars()
10727 .map(|c| {
10728 let code_point = c as u32;
10729 if code_point >= 33 && code_point <= 126 {
10730 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10731 }
10732 c
10733 })
10734 .collect()
10735 })
10736 }
10737
10738 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10739 where
10740 Fn: FnMut(&str) -> String,
10741 {
10742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10743 let buffer = self.buffer.read(cx).snapshot(cx);
10744
10745 let mut new_selections = Vec::new();
10746 let mut edits = Vec::new();
10747 let mut selection_adjustment = 0i32;
10748
10749 for selection in self.selections.all::<usize>(cx) {
10750 let selection_is_empty = selection.is_empty();
10751
10752 let (start, end) = if selection_is_empty {
10753 let word_range = movement::surrounding_word(
10754 &display_map,
10755 selection.start.to_display_point(&display_map),
10756 );
10757 let start = word_range.start.to_offset(&display_map, Bias::Left);
10758 let end = word_range.end.to_offset(&display_map, Bias::Left);
10759 (start, end)
10760 } else {
10761 (selection.start, selection.end)
10762 };
10763
10764 let text = buffer.text_for_range(start..end).collect::<String>();
10765 let old_length = text.len() as i32;
10766 let text = callback(&text);
10767
10768 new_selections.push(Selection {
10769 start: (start as i32 - selection_adjustment) as usize,
10770 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10771 goal: SelectionGoal::None,
10772 ..selection
10773 });
10774
10775 selection_adjustment += old_length - text.len() as i32;
10776
10777 edits.push((start..end, text));
10778 }
10779
10780 self.transact(window, cx, |this, window, cx| {
10781 this.buffer.update(cx, |buffer, cx| {
10782 buffer.edit(edits, None, cx);
10783 });
10784
10785 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10786 s.select(new_selections);
10787 });
10788
10789 this.request_autoscroll(Autoscroll::fit(), cx);
10790 });
10791 }
10792
10793 pub fn move_selection_on_drop(
10794 &mut self,
10795 selection: &Selection<Anchor>,
10796 target: DisplayPoint,
10797 is_cut: bool,
10798 window: &mut Window,
10799 cx: &mut Context<Self>,
10800 ) {
10801 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10802 let buffer = &display_map.buffer_snapshot;
10803 let mut edits = Vec::new();
10804 let insert_point = display_map
10805 .clip_point(target, Bias::Left)
10806 .to_point(&display_map);
10807 let text = buffer
10808 .text_for_range(selection.start..selection.end)
10809 .collect::<String>();
10810 if is_cut {
10811 edits.push(((selection.start..selection.end), String::new()));
10812 }
10813 let insert_anchor = buffer.anchor_before(insert_point);
10814 edits.push(((insert_anchor..insert_anchor), text));
10815 let last_edit_start = insert_anchor.bias_left(buffer);
10816 let last_edit_end = insert_anchor.bias_right(buffer);
10817 self.transact(window, cx, |this, window, cx| {
10818 this.buffer.update(cx, |buffer, cx| {
10819 buffer.edit(edits, None, cx);
10820 });
10821 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10822 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10823 });
10824 });
10825 }
10826
10827 pub fn clear_selection_drag_state(&mut self) {
10828 self.selection_drag_state = SelectionDragState::None;
10829 }
10830
10831 pub fn duplicate(
10832 &mut self,
10833 upwards: bool,
10834 whole_lines: bool,
10835 window: &mut Window,
10836 cx: &mut Context<Self>,
10837 ) {
10838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10839
10840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10841 let buffer = &display_map.buffer_snapshot;
10842 let selections = self.selections.all::<Point>(cx);
10843
10844 let mut edits = Vec::new();
10845 let mut selections_iter = selections.iter().peekable();
10846 while let Some(selection) = selections_iter.next() {
10847 let mut rows = selection.spanned_rows(false, &display_map);
10848 // duplicate line-wise
10849 if whole_lines || selection.start == selection.end {
10850 // Avoid duplicating the same lines twice.
10851 while let Some(next_selection) = selections_iter.peek() {
10852 let next_rows = next_selection.spanned_rows(false, &display_map);
10853 if next_rows.start < rows.end {
10854 rows.end = next_rows.end;
10855 selections_iter.next().unwrap();
10856 } else {
10857 break;
10858 }
10859 }
10860
10861 // Copy the text from the selected row region and splice it either at the start
10862 // or end of the region.
10863 let start = Point::new(rows.start.0, 0);
10864 let end = Point::new(
10865 rows.end.previous_row().0,
10866 buffer.line_len(rows.end.previous_row()),
10867 );
10868 let text = buffer
10869 .text_for_range(start..end)
10870 .chain(Some("\n"))
10871 .collect::<String>();
10872 let insert_location = if upwards {
10873 Point::new(rows.end.0, 0)
10874 } else {
10875 start
10876 };
10877 edits.push((insert_location..insert_location, text));
10878 } else {
10879 // duplicate character-wise
10880 let start = selection.start;
10881 let end = selection.end;
10882 let text = buffer.text_for_range(start..end).collect::<String>();
10883 edits.push((selection.end..selection.end, text));
10884 }
10885 }
10886
10887 self.transact(window, cx, |this, _, cx| {
10888 this.buffer.update(cx, |buffer, cx| {
10889 buffer.edit(edits, None, cx);
10890 });
10891
10892 this.request_autoscroll(Autoscroll::fit(), cx);
10893 });
10894 }
10895
10896 pub fn duplicate_line_up(
10897 &mut self,
10898 _: &DuplicateLineUp,
10899 window: &mut Window,
10900 cx: &mut Context<Self>,
10901 ) {
10902 self.duplicate(true, true, window, cx);
10903 }
10904
10905 pub fn duplicate_line_down(
10906 &mut self,
10907 _: &DuplicateLineDown,
10908 window: &mut Window,
10909 cx: &mut Context<Self>,
10910 ) {
10911 self.duplicate(false, true, window, cx);
10912 }
10913
10914 pub fn duplicate_selection(
10915 &mut self,
10916 _: &DuplicateSelection,
10917 window: &mut Window,
10918 cx: &mut Context<Self>,
10919 ) {
10920 self.duplicate(false, false, window, cx);
10921 }
10922
10923 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10925
10926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10927 let buffer = self.buffer.read(cx).snapshot(cx);
10928
10929 let mut edits = Vec::new();
10930 let mut unfold_ranges = Vec::new();
10931 let mut refold_creases = Vec::new();
10932
10933 let selections = self.selections.all::<Point>(cx);
10934 let mut selections = selections.iter().peekable();
10935 let mut contiguous_row_selections = Vec::new();
10936 let mut new_selections = Vec::new();
10937
10938 while let Some(selection) = selections.next() {
10939 // Find all the selections that span a contiguous row range
10940 let (start_row, end_row) = consume_contiguous_rows(
10941 &mut contiguous_row_selections,
10942 selection,
10943 &display_map,
10944 &mut selections,
10945 );
10946
10947 // Move the text spanned by the row range to be before the line preceding the row range
10948 if start_row.0 > 0 {
10949 let range_to_move = Point::new(
10950 start_row.previous_row().0,
10951 buffer.line_len(start_row.previous_row()),
10952 )
10953 ..Point::new(
10954 end_row.previous_row().0,
10955 buffer.line_len(end_row.previous_row()),
10956 );
10957 let insertion_point = display_map
10958 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10959 .0;
10960
10961 // Don't move lines across excerpts
10962 if buffer
10963 .excerpt_containing(insertion_point..range_to_move.end)
10964 .is_some()
10965 {
10966 let text = buffer
10967 .text_for_range(range_to_move.clone())
10968 .flat_map(|s| s.chars())
10969 .skip(1)
10970 .chain(['\n'])
10971 .collect::<String>();
10972
10973 edits.push((
10974 buffer.anchor_after(range_to_move.start)
10975 ..buffer.anchor_before(range_to_move.end),
10976 String::new(),
10977 ));
10978 let insertion_anchor = buffer.anchor_after(insertion_point);
10979 edits.push((insertion_anchor..insertion_anchor, text));
10980
10981 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10982
10983 // Move selections up
10984 new_selections.extend(contiguous_row_selections.drain(..).map(
10985 |mut selection| {
10986 selection.start.row -= row_delta;
10987 selection.end.row -= row_delta;
10988 selection
10989 },
10990 ));
10991
10992 // Move folds up
10993 unfold_ranges.push(range_to_move.clone());
10994 for fold in display_map.folds_in_range(
10995 buffer.anchor_before(range_to_move.start)
10996 ..buffer.anchor_after(range_to_move.end),
10997 ) {
10998 let mut start = fold.range.start.to_point(&buffer);
10999 let mut end = fold.range.end.to_point(&buffer);
11000 start.row -= row_delta;
11001 end.row -= row_delta;
11002 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11003 }
11004 }
11005 }
11006
11007 // If we didn't move line(s), preserve the existing selections
11008 new_selections.append(&mut contiguous_row_selections);
11009 }
11010
11011 self.transact(window, cx, |this, window, cx| {
11012 this.unfold_ranges(&unfold_ranges, true, true, cx);
11013 this.buffer.update(cx, |buffer, cx| {
11014 for (range, text) in edits {
11015 buffer.edit([(range, text)], None, cx);
11016 }
11017 });
11018 this.fold_creases(refold_creases, true, window, cx);
11019 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11020 s.select(new_selections);
11021 })
11022 });
11023 }
11024
11025 pub fn move_line_down(
11026 &mut self,
11027 _: &MoveLineDown,
11028 window: &mut Window,
11029 cx: &mut Context<Self>,
11030 ) {
11031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11032
11033 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11034 let buffer = self.buffer.read(cx).snapshot(cx);
11035
11036 let mut edits = Vec::new();
11037 let mut unfold_ranges = Vec::new();
11038 let mut refold_creases = Vec::new();
11039
11040 let selections = self.selections.all::<Point>(cx);
11041 let mut selections = selections.iter().peekable();
11042 let mut contiguous_row_selections = Vec::new();
11043 let mut new_selections = Vec::new();
11044
11045 while let Some(selection) = selections.next() {
11046 // Find all the selections that span a contiguous row range
11047 let (start_row, end_row) = consume_contiguous_rows(
11048 &mut contiguous_row_selections,
11049 selection,
11050 &display_map,
11051 &mut selections,
11052 );
11053
11054 // Move the text spanned by the row range to be after the last line of the row range
11055 if end_row.0 <= buffer.max_point().row {
11056 let range_to_move =
11057 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11058 let insertion_point = display_map
11059 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11060 .0;
11061
11062 // Don't move lines across excerpt boundaries
11063 if buffer
11064 .excerpt_containing(range_to_move.start..insertion_point)
11065 .is_some()
11066 {
11067 let mut text = String::from("\n");
11068 text.extend(buffer.text_for_range(range_to_move.clone()));
11069 text.pop(); // Drop trailing newline
11070 edits.push((
11071 buffer.anchor_after(range_to_move.start)
11072 ..buffer.anchor_before(range_to_move.end),
11073 String::new(),
11074 ));
11075 let insertion_anchor = buffer.anchor_after(insertion_point);
11076 edits.push((insertion_anchor..insertion_anchor, text));
11077
11078 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11079
11080 // Move selections down
11081 new_selections.extend(contiguous_row_selections.drain(..).map(
11082 |mut selection| {
11083 selection.start.row += row_delta;
11084 selection.end.row += row_delta;
11085 selection
11086 },
11087 ));
11088
11089 // Move folds down
11090 unfold_ranges.push(range_to_move.clone());
11091 for fold in display_map.folds_in_range(
11092 buffer.anchor_before(range_to_move.start)
11093 ..buffer.anchor_after(range_to_move.end),
11094 ) {
11095 let mut start = fold.range.start.to_point(&buffer);
11096 let mut end = fold.range.end.to_point(&buffer);
11097 start.row += row_delta;
11098 end.row += row_delta;
11099 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11100 }
11101 }
11102 }
11103
11104 // If we didn't move line(s), preserve the existing selections
11105 new_selections.append(&mut contiguous_row_selections);
11106 }
11107
11108 self.transact(window, cx, |this, window, cx| {
11109 this.unfold_ranges(&unfold_ranges, true, true, cx);
11110 this.buffer.update(cx, |buffer, cx| {
11111 for (range, text) in edits {
11112 buffer.edit([(range, text)], None, cx);
11113 }
11114 });
11115 this.fold_creases(refold_creases, true, window, cx);
11116 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11117 s.select(new_selections)
11118 });
11119 });
11120 }
11121
11122 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11123 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11124 let text_layout_details = &self.text_layout_details(window);
11125 self.transact(window, cx, |this, window, cx| {
11126 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11127 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11128 s.move_with(|display_map, selection| {
11129 if !selection.is_empty() {
11130 return;
11131 }
11132
11133 let mut head = selection.head();
11134 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11135 if head.column() == display_map.line_len(head.row()) {
11136 transpose_offset = display_map
11137 .buffer_snapshot
11138 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11139 }
11140
11141 if transpose_offset == 0 {
11142 return;
11143 }
11144
11145 *head.column_mut() += 1;
11146 head = display_map.clip_point(head, Bias::Right);
11147 let goal = SelectionGoal::HorizontalPosition(
11148 display_map
11149 .x_for_display_point(head, text_layout_details)
11150 .into(),
11151 );
11152 selection.collapse_to(head, goal);
11153
11154 let transpose_start = display_map
11155 .buffer_snapshot
11156 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11157 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11158 let transpose_end = display_map
11159 .buffer_snapshot
11160 .clip_offset(transpose_offset + 1, Bias::Right);
11161 if let Some(ch) =
11162 display_map.buffer_snapshot.chars_at(transpose_start).next()
11163 {
11164 edits.push((transpose_start..transpose_offset, String::new()));
11165 edits.push((transpose_end..transpose_end, ch.to_string()));
11166 }
11167 }
11168 });
11169 edits
11170 });
11171 this.buffer
11172 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11173 let selections = this.selections.all::<usize>(cx);
11174 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11175 s.select(selections);
11176 });
11177 });
11178 }
11179
11180 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11181 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11182 self.rewrap_impl(RewrapOptions::default(), cx)
11183 }
11184
11185 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11186 let buffer = self.buffer.read(cx).snapshot(cx);
11187 let selections = self.selections.all::<Point>(cx);
11188
11189 // Shrink and split selections to respect paragraph boundaries.
11190 let ranges = selections.into_iter().flat_map(|selection| {
11191 let language_settings = buffer.language_settings_at(selection.head(), cx);
11192 let language_scope = buffer.language_scope_at(selection.head());
11193
11194 let Some(start_row) = (selection.start.row..=selection.end.row)
11195 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11196 else {
11197 return vec![];
11198 };
11199 let Some(end_row) = (selection.start.row..=selection.end.row)
11200 .rev()
11201 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11202 else {
11203 return vec![];
11204 };
11205
11206 let mut row = start_row;
11207 let mut ranges = Vec::new();
11208 while let Some(blank_row) =
11209 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11210 {
11211 let next_paragraph_start = (blank_row + 1..=end_row)
11212 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11213 .unwrap();
11214 ranges.push((
11215 language_settings.clone(),
11216 language_scope.clone(),
11217 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11218 ));
11219 row = next_paragraph_start;
11220 }
11221 ranges.push((
11222 language_settings.clone(),
11223 language_scope.clone(),
11224 Point::new(row, 0)..Point::new(end_row, 0),
11225 ));
11226
11227 ranges
11228 });
11229
11230 let mut edits = Vec::new();
11231 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11232
11233 for (language_settings, language_scope, range) in ranges {
11234 let mut start_row = range.start.row;
11235 let mut end_row = range.end.row;
11236
11237 // Skip selections that overlap with a range that has already been rewrapped.
11238 let selection_range = start_row..end_row;
11239 if rewrapped_row_ranges
11240 .iter()
11241 .any(|range| range.overlaps(&selection_range))
11242 {
11243 continue;
11244 }
11245
11246 let tab_size = language_settings.tab_size;
11247
11248 // Since not all lines in the selection may be at the same indent
11249 // level, choose the indent size that is the most common between all
11250 // of the lines.
11251 //
11252 // If there is a tie, we use the deepest indent.
11253 let (indent_size, indent_end) = {
11254 let mut indent_size_occurrences = HashMap::default();
11255 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11256
11257 for row in start_row..=end_row {
11258 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11259 rows_by_indent_size.entry(indent).or_default().push(row);
11260 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11261 }
11262
11263 let indent_size = indent_size_occurrences
11264 .into_iter()
11265 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11266 .map(|(indent, _)| indent)
11267 .unwrap_or_default();
11268 let row = rows_by_indent_size[&indent_size][0];
11269 let indent_end = Point::new(row, indent_size.len);
11270
11271 (indent_size, indent_end)
11272 };
11273
11274 let mut line_prefix = indent_size.chars().collect::<String>();
11275
11276 let mut inside_comment = false;
11277 if let Some(comment_prefix) = language_scope.and_then(|language| {
11278 language
11279 .line_comment_prefixes()
11280 .iter()
11281 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11282 .cloned()
11283 }) {
11284 line_prefix.push_str(&comment_prefix);
11285 inside_comment = true;
11286 }
11287
11288 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11289 RewrapBehavior::InComments => inside_comment,
11290 RewrapBehavior::InSelections => !range.is_empty(),
11291 RewrapBehavior::Anywhere => true,
11292 };
11293
11294 let should_rewrap = options.override_language_settings
11295 || allow_rewrap_based_on_language
11296 || self.hard_wrap.is_some();
11297 if !should_rewrap {
11298 continue;
11299 }
11300
11301 if range.is_empty() {
11302 'expand_upwards: while start_row > 0 {
11303 let prev_row = start_row - 1;
11304 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11305 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11306 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11307 {
11308 start_row = prev_row;
11309 } else {
11310 break 'expand_upwards;
11311 }
11312 }
11313
11314 'expand_downwards: while end_row < buffer.max_point().row {
11315 let next_row = end_row + 1;
11316 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11317 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11318 && !buffer.is_line_blank(MultiBufferRow(next_row))
11319 {
11320 end_row = next_row;
11321 } else {
11322 break 'expand_downwards;
11323 }
11324 }
11325 }
11326
11327 let start = Point::new(start_row, 0);
11328 let start_offset = start.to_offset(&buffer);
11329 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11330 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11331 let Some(lines_without_prefixes) = selection_text
11332 .lines()
11333 .map(|line| {
11334 line.strip_prefix(&line_prefix)
11335 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11336 .with_context(|| {
11337 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11338 })
11339 })
11340 .collect::<Result<Vec<_>, _>>()
11341 .log_err()
11342 else {
11343 continue;
11344 };
11345
11346 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11347 buffer
11348 .language_settings_at(Point::new(start_row, 0), cx)
11349 .preferred_line_length as usize
11350 });
11351 let wrapped_text = wrap_with_prefix(
11352 line_prefix,
11353 lines_without_prefixes.join("\n"),
11354 wrap_column,
11355 tab_size,
11356 options.preserve_existing_whitespace,
11357 );
11358
11359 // TODO: should always use char-based diff while still supporting cursor behavior that
11360 // matches vim.
11361 let mut diff_options = DiffOptions::default();
11362 if options.override_language_settings {
11363 diff_options.max_word_diff_len = 0;
11364 diff_options.max_word_diff_line_count = 0;
11365 } else {
11366 diff_options.max_word_diff_len = usize::MAX;
11367 diff_options.max_word_diff_line_count = usize::MAX;
11368 }
11369
11370 for (old_range, new_text) in
11371 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11372 {
11373 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11374 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11375 edits.push((edit_start..edit_end, new_text));
11376 }
11377
11378 rewrapped_row_ranges.push(start_row..=end_row);
11379 }
11380
11381 self.buffer
11382 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11383 }
11384
11385 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11386 let mut text = String::new();
11387 let buffer = self.buffer.read(cx).snapshot(cx);
11388 let mut selections = self.selections.all::<Point>(cx);
11389 let mut clipboard_selections = Vec::with_capacity(selections.len());
11390 {
11391 let max_point = buffer.max_point();
11392 let mut is_first = true;
11393 for selection in &mut selections {
11394 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11395 if is_entire_line {
11396 selection.start = Point::new(selection.start.row, 0);
11397 if !selection.is_empty() && selection.end.column == 0 {
11398 selection.end = cmp::min(max_point, selection.end);
11399 } else {
11400 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11401 }
11402 selection.goal = SelectionGoal::None;
11403 }
11404 if is_first {
11405 is_first = false;
11406 } else {
11407 text += "\n";
11408 }
11409 let mut len = 0;
11410 for chunk in buffer.text_for_range(selection.start..selection.end) {
11411 text.push_str(chunk);
11412 len += chunk.len();
11413 }
11414 clipboard_selections.push(ClipboardSelection {
11415 len,
11416 is_entire_line,
11417 first_line_indent: buffer
11418 .indent_size_for_line(MultiBufferRow(selection.start.row))
11419 .len,
11420 });
11421 }
11422 }
11423
11424 self.transact(window, cx, |this, window, cx| {
11425 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11426 s.select(selections);
11427 });
11428 this.insert("", window, cx);
11429 });
11430 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11431 }
11432
11433 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11435 let item = self.cut_common(window, cx);
11436 cx.write_to_clipboard(item);
11437 }
11438
11439 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11441 self.change_selections(None, window, cx, |s| {
11442 s.move_with(|snapshot, sel| {
11443 if sel.is_empty() {
11444 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11445 }
11446 });
11447 });
11448 let item = self.cut_common(window, cx);
11449 cx.set_global(KillRing(item))
11450 }
11451
11452 pub fn kill_ring_yank(
11453 &mut self,
11454 _: &KillRingYank,
11455 window: &mut Window,
11456 cx: &mut Context<Self>,
11457 ) {
11458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11459 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11460 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11461 (kill_ring.text().to_string(), kill_ring.metadata_json())
11462 } else {
11463 return;
11464 }
11465 } else {
11466 return;
11467 };
11468 self.do_paste(&text, metadata, false, window, cx);
11469 }
11470
11471 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11472 self.do_copy(true, cx);
11473 }
11474
11475 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11476 self.do_copy(false, cx);
11477 }
11478
11479 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11480 let selections = self.selections.all::<Point>(cx);
11481 let buffer = self.buffer.read(cx).read(cx);
11482 let mut text = String::new();
11483
11484 let mut clipboard_selections = Vec::with_capacity(selections.len());
11485 {
11486 let max_point = buffer.max_point();
11487 let mut is_first = true;
11488 for selection in &selections {
11489 let mut start = selection.start;
11490 let mut end = selection.end;
11491 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11492 if is_entire_line {
11493 start = Point::new(start.row, 0);
11494 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11495 }
11496
11497 let mut trimmed_selections = Vec::new();
11498 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11499 let row = MultiBufferRow(start.row);
11500 let first_indent = buffer.indent_size_for_line(row);
11501 if first_indent.len == 0 || start.column > first_indent.len {
11502 trimmed_selections.push(start..end);
11503 } else {
11504 trimmed_selections.push(
11505 Point::new(row.0, first_indent.len)
11506 ..Point::new(row.0, buffer.line_len(row)),
11507 );
11508 for row in start.row + 1..=end.row {
11509 let mut line_len = buffer.line_len(MultiBufferRow(row));
11510 if row == end.row {
11511 line_len = end.column;
11512 }
11513 if line_len == 0 {
11514 trimmed_selections
11515 .push(Point::new(row, 0)..Point::new(row, line_len));
11516 continue;
11517 }
11518 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11519 if row_indent_size.len >= first_indent.len {
11520 trimmed_selections.push(
11521 Point::new(row, first_indent.len)..Point::new(row, line_len),
11522 );
11523 } else {
11524 trimmed_selections.clear();
11525 trimmed_selections.push(start..end);
11526 break;
11527 }
11528 }
11529 }
11530 } else {
11531 trimmed_selections.push(start..end);
11532 }
11533
11534 for trimmed_range in trimmed_selections {
11535 if is_first {
11536 is_first = false;
11537 } else {
11538 text += "\n";
11539 }
11540 let mut len = 0;
11541 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11542 text.push_str(chunk);
11543 len += chunk.len();
11544 }
11545 clipboard_selections.push(ClipboardSelection {
11546 len,
11547 is_entire_line,
11548 first_line_indent: buffer
11549 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11550 .len,
11551 });
11552 }
11553 }
11554 }
11555
11556 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11557 text,
11558 clipboard_selections,
11559 ));
11560 }
11561
11562 pub fn do_paste(
11563 &mut self,
11564 text: &String,
11565 clipboard_selections: Option<Vec<ClipboardSelection>>,
11566 handle_entire_lines: bool,
11567 window: &mut Window,
11568 cx: &mut Context<Self>,
11569 ) {
11570 if self.read_only(cx) {
11571 return;
11572 }
11573
11574 let clipboard_text = Cow::Borrowed(text);
11575
11576 self.transact(window, cx, |this, window, cx| {
11577 if let Some(mut clipboard_selections) = clipboard_selections {
11578 let old_selections = this.selections.all::<usize>(cx);
11579 let all_selections_were_entire_line =
11580 clipboard_selections.iter().all(|s| s.is_entire_line);
11581 let first_selection_indent_column =
11582 clipboard_selections.first().map(|s| s.first_line_indent);
11583 if clipboard_selections.len() != old_selections.len() {
11584 clipboard_selections.drain(..);
11585 }
11586 let cursor_offset = this.selections.last::<usize>(cx).head();
11587 let mut auto_indent_on_paste = true;
11588
11589 this.buffer.update(cx, |buffer, cx| {
11590 let snapshot = buffer.read(cx);
11591 auto_indent_on_paste = snapshot
11592 .language_settings_at(cursor_offset, cx)
11593 .auto_indent_on_paste;
11594
11595 let mut start_offset = 0;
11596 let mut edits = Vec::new();
11597 let mut original_indent_columns = Vec::new();
11598 for (ix, selection) in old_selections.iter().enumerate() {
11599 let to_insert;
11600 let entire_line;
11601 let original_indent_column;
11602 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11603 let end_offset = start_offset + clipboard_selection.len;
11604 to_insert = &clipboard_text[start_offset..end_offset];
11605 entire_line = clipboard_selection.is_entire_line;
11606 start_offset = end_offset + 1;
11607 original_indent_column = Some(clipboard_selection.first_line_indent);
11608 } else {
11609 to_insert = clipboard_text.as_str();
11610 entire_line = all_selections_were_entire_line;
11611 original_indent_column = first_selection_indent_column
11612 }
11613
11614 // If the corresponding selection was empty when this slice of the
11615 // clipboard text was written, then the entire line containing the
11616 // selection was copied. If this selection is also currently empty,
11617 // then paste the line before the current line of the buffer.
11618 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11619 let column = selection.start.to_point(&snapshot).column as usize;
11620 let line_start = selection.start - column;
11621 line_start..line_start
11622 } else {
11623 selection.range()
11624 };
11625
11626 edits.push((range, to_insert));
11627 original_indent_columns.push(original_indent_column);
11628 }
11629 drop(snapshot);
11630
11631 buffer.edit(
11632 edits,
11633 if auto_indent_on_paste {
11634 Some(AutoindentMode::Block {
11635 original_indent_columns,
11636 })
11637 } else {
11638 None
11639 },
11640 cx,
11641 );
11642 });
11643
11644 let selections = this.selections.all::<usize>(cx);
11645 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11646 s.select(selections)
11647 });
11648 } else {
11649 this.insert(&clipboard_text, window, cx);
11650 }
11651 });
11652 }
11653
11654 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11656 if let Some(item) = cx.read_from_clipboard() {
11657 let entries = item.entries();
11658
11659 match entries.first() {
11660 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11661 // of all the pasted entries.
11662 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11663 .do_paste(
11664 clipboard_string.text(),
11665 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11666 true,
11667 window,
11668 cx,
11669 ),
11670 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11671 }
11672 }
11673 }
11674
11675 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11676 if self.read_only(cx) {
11677 return;
11678 }
11679
11680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11681
11682 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11683 if let Some((selections, _)) =
11684 self.selection_history.transaction(transaction_id).cloned()
11685 {
11686 self.change_selections(None, window, cx, |s| {
11687 s.select_anchors(selections.to_vec());
11688 });
11689 } else {
11690 log::error!(
11691 "No entry in selection_history found for undo. \
11692 This may correspond to a bug where undo does not update the selection. \
11693 If this is occurring, please add details to \
11694 https://github.com/zed-industries/zed/issues/22692"
11695 );
11696 }
11697 self.request_autoscroll(Autoscroll::fit(), cx);
11698 self.unmark_text(window, cx);
11699 self.refresh_inline_completion(true, false, window, cx);
11700 cx.emit(EditorEvent::Edited { transaction_id });
11701 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11702 }
11703 }
11704
11705 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11706 if self.read_only(cx) {
11707 return;
11708 }
11709
11710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11711
11712 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11713 if let Some((_, Some(selections))) =
11714 self.selection_history.transaction(transaction_id).cloned()
11715 {
11716 self.change_selections(None, window, cx, |s| {
11717 s.select_anchors(selections.to_vec());
11718 });
11719 } else {
11720 log::error!(
11721 "No entry in selection_history found for redo. \
11722 This may correspond to a bug where undo does not update the selection. \
11723 If this is occurring, please add details to \
11724 https://github.com/zed-industries/zed/issues/22692"
11725 );
11726 }
11727 self.request_autoscroll(Autoscroll::fit(), cx);
11728 self.unmark_text(window, cx);
11729 self.refresh_inline_completion(true, false, window, cx);
11730 cx.emit(EditorEvent::Edited { transaction_id });
11731 }
11732 }
11733
11734 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11735 self.buffer
11736 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11737 }
11738
11739 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11740 self.buffer
11741 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11742 }
11743
11744 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11746 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11747 s.move_with(|map, selection| {
11748 let cursor = if selection.is_empty() {
11749 movement::left(map, selection.start)
11750 } else {
11751 selection.start
11752 };
11753 selection.collapse_to(cursor, SelectionGoal::None);
11754 });
11755 })
11756 }
11757
11758 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11761 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11762 })
11763 }
11764
11765 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11766 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11767 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11768 s.move_with(|map, selection| {
11769 let cursor = if selection.is_empty() {
11770 movement::right(map, selection.end)
11771 } else {
11772 selection.end
11773 };
11774 selection.collapse_to(cursor, SelectionGoal::None)
11775 });
11776 })
11777 }
11778
11779 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11780 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11781 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11782 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11783 })
11784 }
11785
11786 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11787 if self.take_rename(true, window, cx).is_some() {
11788 return;
11789 }
11790
11791 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11792 cx.propagate();
11793 return;
11794 }
11795
11796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11797
11798 let text_layout_details = &self.text_layout_details(window);
11799 let selection_count = self.selections.count();
11800 let first_selection = self.selections.first_anchor();
11801
11802 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11803 s.move_with(|map, selection| {
11804 if !selection.is_empty() {
11805 selection.goal = SelectionGoal::None;
11806 }
11807 let (cursor, goal) = movement::up(
11808 map,
11809 selection.start,
11810 selection.goal,
11811 false,
11812 text_layout_details,
11813 );
11814 selection.collapse_to(cursor, goal);
11815 });
11816 });
11817
11818 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11819 {
11820 cx.propagate();
11821 }
11822 }
11823
11824 pub fn move_up_by_lines(
11825 &mut self,
11826 action: &MoveUpByLines,
11827 window: &mut Window,
11828 cx: &mut Context<Self>,
11829 ) {
11830 if self.take_rename(true, window, cx).is_some() {
11831 return;
11832 }
11833
11834 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11835 cx.propagate();
11836 return;
11837 }
11838
11839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11840
11841 let text_layout_details = &self.text_layout_details(window);
11842
11843 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11844 s.move_with(|map, selection| {
11845 if !selection.is_empty() {
11846 selection.goal = SelectionGoal::None;
11847 }
11848 let (cursor, goal) = movement::up_by_rows(
11849 map,
11850 selection.start,
11851 action.lines,
11852 selection.goal,
11853 false,
11854 text_layout_details,
11855 );
11856 selection.collapse_to(cursor, goal);
11857 });
11858 })
11859 }
11860
11861 pub fn move_down_by_lines(
11862 &mut self,
11863 action: &MoveDownByLines,
11864 window: &mut Window,
11865 cx: &mut Context<Self>,
11866 ) {
11867 if self.take_rename(true, window, cx).is_some() {
11868 return;
11869 }
11870
11871 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11872 cx.propagate();
11873 return;
11874 }
11875
11876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11877
11878 let text_layout_details = &self.text_layout_details(window);
11879
11880 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11881 s.move_with(|map, selection| {
11882 if !selection.is_empty() {
11883 selection.goal = SelectionGoal::None;
11884 }
11885 let (cursor, goal) = movement::down_by_rows(
11886 map,
11887 selection.start,
11888 action.lines,
11889 selection.goal,
11890 false,
11891 text_layout_details,
11892 );
11893 selection.collapse_to(cursor, goal);
11894 });
11895 })
11896 }
11897
11898 pub fn select_down_by_lines(
11899 &mut self,
11900 action: &SelectDownByLines,
11901 window: &mut Window,
11902 cx: &mut Context<Self>,
11903 ) {
11904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11905 let text_layout_details = &self.text_layout_details(window);
11906 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11907 s.move_heads_with(|map, head, goal| {
11908 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11909 })
11910 })
11911 }
11912
11913 pub fn select_up_by_lines(
11914 &mut self,
11915 action: &SelectUpByLines,
11916 window: &mut Window,
11917 cx: &mut Context<Self>,
11918 ) {
11919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11920 let text_layout_details = &self.text_layout_details(window);
11921 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11922 s.move_heads_with(|map, head, goal| {
11923 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11924 })
11925 })
11926 }
11927
11928 pub fn select_page_up(
11929 &mut self,
11930 _: &SelectPageUp,
11931 window: &mut Window,
11932 cx: &mut Context<Self>,
11933 ) {
11934 let Some(row_count) = self.visible_row_count() else {
11935 return;
11936 };
11937
11938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11939
11940 let text_layout_details = &self.text_layout_details(window);
11941
11942 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11943 s.move_heads_with(|map, head, goal| {
11944 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11945 })
11946 })
11947 }
11948
11949 pub fn move_page_up(
11950 &mut self,
11951 action: &MovePageUp,
11952 window: &mut Window,
11953 cx: &mut Context<Self>,
11954 ) {
11955 if self.take_rename(true, window, cx).is_some() {
11956 return;
11957 }
11958
11959 if self
11960 .context_menu
11961 .borrow_mut()
11962 .as_mut()
11963 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11964 .unwrap_or(false)
11965 {
11966 return;
11967 }
11968
11969 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11970 cx.propagate();
11971 return;
11972 }
11973
11974 let Some(row_count) = self.visible_row_count() else {
11975 return;
11976 };
11977
11978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11979
11980 let autoscroll = if action.center_cursor {
11981 Autoscroll::center()
11982 } else {
11983 Autoscroll::fit()
11984 };
11985
11986 let text_layout_details = &self.text_layout_details(window);
11987
11988 self.change_selections(Some(autoscroll), window, cx, |s| {
11989 s.move_with(|map, selection| {
11990 if !selection.is_empty() {
11991 selection.goal = SelectionGoal::None;
11992 }
11993 let (cursor, goal) = movement::up_by_rows(
11994 map,
11995 selection.end,
11996 row_count,
11997 selection.goal,
11998 false,
11999 text_layout_details,
12000 );
12001 selection.collapse_to(cursor, goal);
12002 });
12003 });
12004 }
12005
12006 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12008 let text_layout_details = &self.text_layout_details(window);
12009 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12010 s.move_heads_with(|map, head, goal| {
12011 movement::up(map, head, goal, false, text_layout_details)
12012 })
12013 })
12014 }
12015
12016 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12017 self.take_rename(true, window, cx);
12018
12019 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12020 cx.propagate();
12021 return;
12022 }
12023
12024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12025
12026 let text_layout_details = &self.text_layout_details(window);
12027 let selection_count = self.selections.count();
12028 let first_selection = self.selections.first_anchor();
12029
12030 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12031 s.move_with(|map, selection| {
12032 if !selection.is_empty() {
12033 selection.goal = SelectionGoal::None;
12034 }
12035 let (cursor, goal) = movement::down(
12036 map,
12037 selection.end,
12038 selection.goal,
12039 false,
12040 text_layout_details,
12041 );
12042 selection.collapse_to(cursor, goal);
12043 });
12044 });
12045
12046 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12047 {
12048 cx.propagate();
12049 }
12050 }
12051
12052 pub fn select_page_down(
12053 &mut self,
12054 _: &SelectPageDown,
12055 window: &mut Window,
12056 cx: &mut Context<Self>,
12057 ) {
12058 let Some(row_count) = self.visible_row_count() else {
12059 return;
12060 };
12061
12062 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12063
12064 let text_layout_details = &self.text_layout_details(window);
12065
12066 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12067 s.move_heads_with(|map, head, goal| {
12068 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12069 })
12070 })
12071 }
12072
12073 pub fn move_page_down(
12074 &mut self,
12075 action: &MovePageDown,
12076 window: &mut Window,
12077 cx: &mut Context<Self>,
12078 ) {
12079 if self.take_rename(true, window, cx).is_some() {
12080 return;
12081 }
12082
12083 if self
12084 .context_menu
12085 .borrow_mut()
12086 .as_mut()
12087 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12088 .unwrap_or(false)
12089 {
12090 return;
12091 }
12092
12093 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12094 cx.propagate();
12095 return;
12096 }
12097
12098 let Some(row_count) = self.visible_row_count() else {
12099 return;
12100 };
12101
12102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12103
12104 let autoscroll = if action.center_cursor {
12105 Autoscroll::center()
12106 } else {
12107 Autoscroll::fit()
12108 };
12109
12110 let text_layout_details = &self.text_layout_details(window);
12111 self.change_selections(Some(autoscroll), window, cx, |s| {
12112 s.move_with(|map, selection| {
12113 if !selection.is_empty() {
12114 selection.goal = SelectionGoal::None;
12115 }
12116 let (cursor, goal) = movement::down_by_rows(
12117 map,
12118 selection.end,
12119 row_count,
12120 selection.goal,
12121 false,
12122 text_layout_details,
12123 );
12124 selection.collapse_to(cursor, goal);
12125 });
12126 });
12127 }
12128
12129 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12131 let text_layout_details = &self.text_layout_details(window);
12132 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12133 s.move_heads_with(|map, head, goal| {
12134 movement::down(map, head, goal, false, text_layout_details)
12135 })
12136 });
12137 }
12138
12139 pub fn context_menu_first(
12140 &mut self,
12141 _: &ContextMenuFirst,
12142 window: &mut Window,
12143 cx: &mut Context<Self>,
12144 ) {
12145 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12146 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12147 }
12148 }
12149
12150 pub fn context_menu_prev(
12151 &mut self,
12152 _: &ContextMenuPrevious,
12153 window: &mut Window,
12154 cx: &mut Context<Self>,
12155 ) {
12156 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12157 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12158 }
12159 }
12160
12161 pub fn context_menu_next(
12162 &mut self,
12163 _: &ContextMenuNext,
12164 window: &mut Window,
12165 cx: &mut Context<Self>,
12166 ) {
12167 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12168 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12169 }
12170 }
12171
12172 pub fn context_menu_last(
12173 &mut self,
12174 _: &ContextMenuLast,
12175 window: &mut Window,
12176 cx: &mut Context<Self>,
12177 ) {
12178 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12179 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12180 }
12181 }
12182
12183 pub fn move_to_previous_word_start(
12184 &mut self,
12185 _: &MoveToPreviousWordStart,
12186 window: &mut Window,
12187 cx: &mut Context<Self>,
12188 ) {
12189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12190 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12191 s.move_cursors_with(|map, head, _| {
12192 (
12193 movement::previous_word_start(map, head),
12194 SelectionGoal::None,
12195 )
12196 });
12197 })
12198 }
12199
12200 pub fn move_to_previous_subword_start(
12201 &mut self,
12202 _: &MoveToPreviousSubwordStart,
12203 window: &mut Window,
12204 cx: &mut Context<Self>,
12205 ) {
12206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12207 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12208 s.move_cursors_with(|map, head, _| {
12209 (
12210 movement::previous_subword_start(map, head),
12211 SelectionGoal::None,
12212 )
12213 });
12214 })
12215 }
12216
12217 pub fn select_to_previous_word_start(
12218 &mut self,
12219 _: &SelectToPreviousWordStart,
12220 window: &mut Window,
12221 cx: &mut Context<Self>,
12222 ) {
12223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12224 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12225 s.move_heads_with(|map, head, _| {
12226 (
12227 movement::previous_word_start(map, head),
12228 SelectionGoal::None,
12229 )
12230 });
12231 })
12232 }
12233
12234 pub fn select_to_previous_subword_start(
12235 &mut self,
12236 _: &SelectToPreviousSubwordStart,
12237 window: &mut Window,
12238 cx: &mut Context<Self>,
12239 ) {
12240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12242 s.move_heads_with(|map, head, _| {
12243 (
12244 movement::previous_subword_start(map, head),
12245 SelectionGoal::None,
12246 )
12247 });
12248 })
12249 }
12250
12251 pub fn delete_to_previous_word_start(
12252 &mut self,
12253 action: &DeleteToPreviousWordStart,
12254 window: &mut Window,
12255 cx: &mut Context<Self>,
12256 ) {
12257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12258 self.transact(window, cx, |this, window, cx| {
12259 this.select_autoclose_pair(window, cx);
12260 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12261 s.move_with(|map, selection| {
12262 if selection.is_empty() {
12263 let cursor = if action.ignore_newlines {
12264 movement::previous_word_start(map, selection.head())
12265 } else {
12266 movement::previous_word_start_or_newline(map, selection.head())
12267 };
12268 selection.set_head(cursor, SelectionGoal::None);
12269 }
12270 });
12271 });
12272 this.insert("", window, cx);
12273 });
12274 }
12275
12276 pub fn delete_to_previous_subword_start(
12277 &mut self,
12278 _: &DeleteToPreviousSubwordStart,
12279 window: &mut Window,
12280 cx: &mut Context<Self>,
12281 ) {
12282 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12283 self.transact(window, cx, |this, window, cx| {
12284 this.select_autoclose_pair(window, cx);
12285 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12286 s.move_with(|map, selection| {
12287 if selection.is_empty() {
12288 let cursor = movement::previous_subword_start(map, selection.head());
12289 selection.set_head(cursor, SelectionGoal::None);
12290 }
12291 });
12292 });
12293 this.insert("", window, cx);
12294 });
12295 }
12296
12297 pub fn move_to_next_word_end(
12298 &mut self,
12299 _: &MoveToNextWordEnd,
12300 window: &mut Window,
12301 cx: &mut Context<Self>,
12302 ) {
12303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12304 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12305 s.move_cursors_with(|map, head, _| {
12306 (movement::next_word_end(map, head), SelectionGoal::None)
12307 });
12308 })
12309 }
12310
12311 pub fn move_to_next_subword_end(
12312 &mut self,
12313 _: &MoveToNextSubwordEnd,
12314 window: &mut Window,
12315 cx: &mut Context<Self>,
12316 ) {
12317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12318 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12319 s.move_cursors_with(|map, head, _| {
12320 (movement::next_subword_end(map, head), SelectionGoal::None)
12321 });
12322 })
12323 }
12324
12325 pub fn select_to_next_word_end(
12326 &mut self,
12327 _: &SelectToNextWordEnd,
12328 window: &mut Window,
12329 cx: &mut Context<Self>,
12330 ) {
12331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12332 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12333 s.move_heads_with(|map, head, _| {
12334 (movement::next_word_end(map, head), SelectionGoal::None)
12335 });
12336 })
12337 }
12338
12339 pub fn select_to_next_subword_end(
12340 &mut self,
12341 _: &SelectToNextSubwordEnd,
12342 window: &mut Window,
12343 cx: &mut Context<Self>,
12344 ) {
12345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12346 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12347 s.move_heads_with(|map, head, _| {
12348 (movement::next_subword_end(map, head), SelectionGoal::None)
12349 });
12350 })
12351 }
12352
12353 pub fn delete_to_next_word_end(
12354 &mut self,
12355 action: &DeleteToNextWordEnd,
12356 window: &mut Window,
12357 cx: &mut Context<Self>,
12358 ) {
12359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12360 self.transact(window, cx, |this, window, cx| {
12361 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12362 s.move_with(|map, selection| {
12363 if selection.is_empty() {
12364 let cursor = if action.ignore_newlines {
12365 movement::next_word_end(map, selection.head())
12366 } else {
12367 movement::next_word_end_or_newline(map, selection.head())
12368 };
12369 selection.set_head(cursor, SelectionGoal::None);
12370 }
12371 });
12372 });
12373 this.insert("", window, cx);
12374 });
12375 }
12376
12377 pub fn delete_to_next_subword_end(
12378 &mut self,
12379 _: &DeleteToNextSubwordEnd,
12380 window: &mut Window,
12381 cx: &mut Context<Self>,
12382 ) {
12383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12384 self.transact(window, cx, |this, window, cx| {
12385 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12386 s.move_with(|map, selection| {
12387 if selection.is_empty() {
12388 let cursor = movement::next_subword_end(map, selection.head());
12389 selection.set_head(cursor, SelectionGoal::None);
12390 }
12391 });
12392 });
12393 this.insert("", window, cx);
12394 });
12395 }
12396
12397 pub fn move_to_beginning_of_line(
12398 &mut self,
12399 action: &MoveToBeginningOfLine,
12400 window: &mut Window,
12401 cx: &mut Context<Self>,
12402 ) {
12403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12404 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12405 s.move_cursors_with(|map, head, _| {
12406 (
12407 movement::indented_line_beginning(
12408 map,
12409 head,
12410 action.stop_at_soft_wraps,
12411 action.stop_at_indent,
12412 ),
12413 SelectionGoal::None,
12414 )
12415 });
12416 })
12417 }
12418
12419 pub fn select_to_beginning_of_line(
12420 &mut self,
12421 action: &SelectToBeginningOfLine,
12422 window: &mut Window,
12423 cx: &mut Context<Self>,
12424 ) {
12425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12426 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12427 s.move_heads_with(|map, head, _| {
12428 (
12429 movement::indented_line_beginning(
12430 map,
12431 head,
12432 action.stop_at_soft_wraps,
12433 action.stop_at_indent,
12434 ),
12435 SelectionGoal::None,
12436 )
12437 });
12438 });
12439 }
12440
12441 pub fn delete_to_beginning_of_line(
12442 &mut self,
12443 action: &DeleteToBeginningOfLine,
12444 window: &mut Window,
12445 cx: &mut Context<Self>,
12446 ) {
12447 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12448 self.transact(window, cx, |this, window, cx| {
12449 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12450 s.move_with(|_, selection| {
12451 selection.reversed = true;
12452 });
12453 });
12454
12455 this.select_to_beginning_of_line(
12456 &SelectToBeginningOfLine {
12457 stop_at_soft_wraps: false,
12458 stop_at_indent: action.stop_at_indent,
12459 },
12460 window,
12461 cx,
12462 );
12463 this.backspace(&Backspace, window, cx);
12464 });
12465 }
12466
12467 pub fn move_to_end_of_line(
12468 &mut self,
12469 action: &MoveToEndOfLine,
12470 window: &mut Window,
12471 cx: &mut Context<Self>,
12472 ) {
12473 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12474 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12475 s.move_cursors_with(|map, head, _| {
12476 (
12477 movement::line_end(map, head, action.stop_at_soft_wraps),
12478 SelectionGoal::None,
12479 )
12480 });
12481 })
12482 }
12483
12484 pub fn select_to_end_of_line(
12485 &mut self,
12486 action: &SelectToEndOfLine,
12487 window: &mut Window,
12488 cx: &mut Context<Self>,
12489 ) {
12490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12491 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12492 s.move_heads_with(|map, head, _| {
12493 (
12494 movement::line_end(map, head, action.stop_at_soft_wraps),
12495 SelectionGoal::None,
12496 )
12497 });
12498 })
12499 }
12500
12501 pub fn delete_to_end_of_line(
12502 &mut self,
12503 _: &DeleteToEndOfLine,
12504 window: &mut Window,
12505 cx: &mut Context<Self>,
12506 ) {
12507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12508 self.transact(window, cx, |this, window, cx| {
12509 this.select_to_end_of_line(
12510 &SelectToEndOfLine {
12511 stop_at_soft_wraps: false,
12512 },
12513 window,
12514 cx,
12515 );
12516 this.delete(&Delete, window, cx);
12517 });
12518 }
12519
12520 pub fn cut_to_end_of_line(
12521 &mut self,
12522 _: &CutToEndOfLine,
12523 window: &mut Window,
12524 cx: &mut Context<Self>,
12525 ) {
12526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12527 self.transact(window, cx, |this, window, cx| {
12528 this.select_to_end_of_line(
12529 &SelectToEndOfLine {
12530 stop_at_soft_wraps: false,
12531 },
12532 window,
12533 cx,
12534 );
12535 this.cut(&Cut, window, cx);
12536 });
12537 }
12538
12539 pub fn move_to_start_of_paragraph(
12540 &mut self,
12541 _: &MoveToStartOfParagraph,
12542 window: &mut Window,
12543 cx: &mut Context<Self>,
12544 ) {
12545 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12546 cx.propagate();
12547 return;
12548 }
12549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12550 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12551 s.move_with(|map, selection| {
12552 selection.collapse_to(
12553 movement::start_of_paragraph(map, selection.head(), 1),
12554 SelectionGoal::None,
12555 )
12556 });
12557 })
12558 }
12559
12560 pub fn move_to_end_of_paragraph(
12561 &mut self,
12562 _: &MoveToEndOfParagraph,
12563 window: &mut Window,
12564 cx: &mut Context<Self>,
12565 ) {
12566 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12567 cx.propagate();
12568 return;
12569 }
12570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12571 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12572 s.move_with(|map, selection| {
12573 selection.collapse_to(
12574 movement::end_of_paragraph(map, selection.head(), 1),
12575 SelectionGoal::None,
12576 )
12577 });
12578 })
12579 }
12580
12581 pub fn select_to_start_of_paragraph(
12582 &mut self,
12583 _: &SelectToStartOfParagraph,
12584 window: &mut Window,
12585 cx: &mut Context<Self>,
12586 ) {
12587 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12588 cx.propagate();
12589 return;
12590 }
12591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12593 s.move_heads_with(|map, head, _| {
12594 (
12595 movement::start_of_paragraph(map, head, 1),
12596 SelectionGoal::None,
12597 )
12598 });
12599 })
12600 }
12601
12602 pub fn select_to_end_of_paragraph(
12603 &mut self,
12604 _: &SelectToEndOfParagraph,
12605 window: &mut Window,
12606 cx: &mut Context<Self>,
12607 ) {
12608 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12609 cx.propagate();
12610 return;
12611 }
12612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12613 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12614 s.move_heads_with(|map, head, _| {
12615 (
12616 movement::end_of_paragraph(map, head, 1),
12617 SelectionGoal::None,
12618 )
12619 });
12620 })
12621 }
12622
12623 pub fn move_to_start_of_excerpt(
12624 &mut self,
12625 _: &MoveToStartOfExcerpt,
12626 window: &mut Window,
12627 cx: &mut Context<Self>,
12628 ) {
12629 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12630 cx.propagate();
12631 return;
12632 }
12633 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12634 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12635 s.move_with(|map, selection| {
12636 selection.collapse_to(
12637 movement::start_of_excerpt(
12638 map,
12639 selection.head(),
12640 workspace::searchable::Direction::Prev,
12641 ),
12642 SelectionGoal::None,
12643 )
12644 });
12645 })
12646 }
12647
12648 pub fn move_to_start_of_next_excerpt(
12649 &mut self,
12650 _: &MoveToStartOfNextExcerpt,
12651 window: &mut Window,
12652 cx: &mut Context<Self>,
12653 ) {
12654 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12655 cx.propagate();
12656 return;
12657 }
12658
12659 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12660 s.move_with(|map, selection| {
12661 selection.collapse_to(
12662 movement::start_of_excerpt(
12663 map,
12664 selection.head(),
12665 workspace::searchable::Direction::Next,
12666 ),
12667 SelectionGoal::None,
12668 )
12669 });
12670 })
12671 }
12672
12673 pub fn move_to_end_of_excerpt(
12674 &mut self,
12675 _: &MoveToEndOfExcerpt,
12676 window: &mut Window,
12677 cx: &mut Context<Self>,
12678 ) {
12679 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12680 cx.propagate();
12681 return;
12682 }
12683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12684 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12685 s.move_with(|map, selection| {
12686 selection.collapse_to(
12687 movement::end_of_excerpt(
12688 map,
12689 selection.head(),
12690 workspace::searchable::Direction::Next,
12691 ),
12692 SelectionGoal::None,
12693 )
12694 });
12695 })
12696 }
12697
12698 pub fn move_to_end_of_previous_excerpt(
12699 &mut self,
12700 _: &MoveToEndOfPreviousExcerpt,
12701 window: &mut Window,
12702 cx: &mut Context<Self>,
12703 ) {
12704 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12705 cx.propagate();
12706 return;
12707 }
12708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12709 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12710 s.move_with(|map, selection| {
12711 selection.collapse_to(
12712 movement::end_of_excerpt(
12713 map,
12714 selection.head(),
12715 workspace::searchable::Direction::Prev,
12716 ),
12717 SelectionGoal::None,
12718 )
12719 });
12720 })
12721 }
12722
12723 pub fn select_to_start_of_excerpt(
12724 &mut self,
12725 _: &SelectToStartOfExcerpt,
12726 window: &mut Window,
12727 cx: &mut Context<Self>,
12728 ) {
12729 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12730 cx.propagate();
12731 return;
12732 }
12733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12735 s.move_heads_with(|map, head, _| {
12736 (
12737 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12738 SelectionGoal::None,
12739 )
12740 });
12741 })
12742 }
12743
12744 pub fn select_to_start_of_next_excerpt(
12745 &mut self,
12746 _: &SelectToStartOfNextExcerpt,
12747 window: &mut Window,
12748 cx: &mut Context<Self>,
12749 ) {
12750 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12751 cx.propagate();
12752 return;
12753 }
12754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12755 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12756 s.move_heads_with(|map, head, _| {
12757 (
12758 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12759 SelectionGoal::None,
12760 )
12761 });
12762 })
12763 }
12764
12765 pub fn select_to_end_of_excerpt(
12766 &mut self,
12767 _: &SelectToEndOfExcerpt,
12768 window: &mut Window,
12769 cx: &mut Context<Self>,
12770 ) {
12771 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12772 cx.propagate();
12773 return;
12774 }
12775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12777 s.move_heads_with(|map, head, _| {
12778 (
12779 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12780 SelectionGoal::None,
12781 )
12782 });
12783 })
12784 }
12785
12786 pub fn select_to_end_of_previous_excerpt(
12787 &mut self,
12788 _: &SelectToEndOfPreviousExcerpt,
12789 window: &mut Window,
12790 cx: &mut Context<Self>,
12791 ) {
12792 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12793 cx.propagate();
12794 return;
12795 }
12796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12797 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12798 s.move_heads_with(|map, head, _| {
12799 (
12800 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12801 SelectionGoal::None,
12802 )
12803 });
12804 })
12805 }
12806
12807 pub fn move_to_beginning(
12808 &mut self,
12809 _: &MoveToBeginning,
12810 window: &mut Window,
12811 cx: &mut Context<Self>,
12812 ) {
12813 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12814 cx.propagate();
12815 return;
12816 }
12817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12819 s.select_ranges(vec![0..0]);
12820 });
12821 }
12822
12823 pub fn select_to_beginning(
12824 &mut self,
12825 _: &SelectToBeginning,
12826 window: &mut Window,
12827 cx: &mut Context<Self>,
12828 ) {
12829 let mut selection = self.selections.last::<Point>(cx);
12830 selection.set_head(Point::zero(), SelectionGoal::None);
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12833 s.select(vec![selection]);
12834 });
12835 }
12836
12837 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12838 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12839 cx.propagate();
12840 return;
12841 }
12842 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12843 let cursor = self.buffer.read(cx).read(cx).len();
12844 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12845 s.select_ranges(vec![cursor..cursor])
12846 });
12847 }
12848
12849 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12850 self.nav_history = nav_history;
12851 }
12852
12853 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12854 self.nav_history.as_ref()
12855 }
12856
12857 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12858 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12859 }
12860
12861 fn push_to_nav_history(
12862 &mut self,
12863 cursor_anchor: Anchor,
12864 new_position: Option<Point>,
12865 is_deactivate: bool,
12866 cx: &mut Context<Self>,
12867 ) {
12868 if let Some(nav_history) = self.nav_history.as_mut() {
12869 let buffer = self.buffer.read(cx).read(cx);
12870 let cursor_position = cursor_anchor.to_point(&buffer);
12871 let scroll_state = self.scroll_manager.anchor();
12872 let scroll_top_row = scroll_state.top_row(&buffer);
12873 drop(buffer);
12874
12875 if let Some(new_position) = new_position {
12876 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12877 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12878 return;
12879 }
12880 }
12881
12882 nav_history.push(
12883 Some(NavigationData {
12884 cursor_anchor,
12885 cursor_position,
12886 scroll_anchor: scroll_state,
12887 scroll_top_row,
12888 }),
12889 cx,
12890 );
12891 cx.emit(EditorEvent::PushedToNavHistory {
12892 anchor: cursor_anchor,
12893 is_deactivate,
12894 })
12895 }
12896 }
12897
12898 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12900 let buffer = self.buffer.read(cx).snapshot(cx);
12901 let mut selection = self.selections.first::<usize>(cx);
12902 selection.set_head(buffer.len(), SelectionGoal::None);
12903 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12904 s.select(vec![selection]);
12905 });
12906 }
12907
12908 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12910 let end = self.buffer.read(cx).read(cx).len();
12911 self.change_selections(None, window, cx, |s| {
12912 s.select_ranges(vec![0..end]);
12913 });
12914 }
12915
12916 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12918 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12919 let mut selections = self.selections.all::<Point>(cx);
12920 let max_point = display_map.buffer_snapshot.max_point();
12921 for selection in &mut selections {
12922 let rows = selection.spanned_rows(true, &display_map);
12923 selection.start = Point::new(rows.start.0, 0);
12924 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12925 selection.reversed = false;
12926 }
12927 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12928 s.select(selections);
12929 });
12930 }
12931
12932 pub fn split_selection_into_lines(
12933 &mut self,
12934 _: &SplitSelectionIntoLines,
12935 window: &mut Window,
12936 cx: &mut Context<Self>,
12937 ) {
12938 let selections = self
12939 .selections
12940 .all::<Point>(cx)
12941 .into_iter()
12942 .map(|selection| selection.start..selection.end)
12943 .collect::<Vec<_>>();
12944 self.unfold_ranges(&selections, true, true, cx);
12945
12946 let mut new_selection_ranges = Vec::new();
12947 {
12948 let buffer = self.buffer.read(cx).read(cx);
12949 for selection in selections {
12950 for row in selection.start.row..selection.end.row {
12951 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12952 new_selection_ranges.push(cursor..cursor);
12953 }
12954
12955 let is_multiline_selection = selection.start.row != selection.end.row;
12956 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12957 // so this action feels more ergonomic when paired with other selection operations
12958 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12959 if !should_skip_last {
12960 new_selection_ranges.push(selection.end..selection.end);
12961 }
12962 }
12963 }
12964 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12965 s.select_ranges(new_selection_ranges);
12966 });
12967 }
12968
12969 pub fn add_selection_above(
12970 &mut self,
12971 _: &AddSelectionAbove,
12972 window: &mut Window,
12973 cx: &mut Context<Self>,
12974 ) {
12975 self.add_selection(true, window, cx);
12976 }
12977
12978 pub fn add_selection_below(
12979 &mut self,
12980 _: &AddSelectionBelow,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) {
12984 self.add_selection(false, window, cx);
12985 }
12986
12987 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12989
12990 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12991 let all_selections = self.selections.all::<Point>(cx);
12992 let text_layout_details = self.text_layout_details(window);
12993
12994 let (mut columnar_selections, new_selections_to_columnarize) = {
12995 if let Some(state) = self.add_selections_state.as_ref() {
12996 let columnar_selection_ids: HashSet<_> = state
12997 .groups
12998 .iter()
12999 .flat_map(|group| group.stack.iter())
13000 .copied()
13001 .collect();
13002
13003 all_selections
13004 .into_iter()
13005 .partition(|s| columnar_selection_ids.contains(&s.id))
13006 } else {
13007 (Vec::new(), all_selections)
13008 }
13009 };
13010
13011 let mut state = self
13012 .add_selections_state
13013 .take()
13014 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13015
13016 for selection in new_selections_to_columnarize {
13017 let range = selection.display_range(&display_map).sorted();
13018 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13019 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13020 let positions = start_x.min(end_x)..start_x.max(end_x);
13021 let mut stack = Vec::new();
13022 for row in range.start.row().0..=range.end.row().0 {
13023 if let Some(selection) = self.selections.build_columnar_selection(
13024 &display_map,
13025 DisplayRow(row),
13026 &positions,
13027 selection.reversed,
13028 &text_layout_details,
13029 ) {
13030 stack.push(selection.id);
13031 columnar_selections.push(selection);
13032 }
13033 }
13034 if !stack.is_empty() {
13035 if above {
13036 stack.reverse();
13037 }
13038 state.groups.push(AddSelectionsGroup { above, stack });
13039 }
13040 }
13041
13042 let mut final_selections = Vec::new();
13043 let end_row = if above {
13044 DisplayRow(0)
13045 } else {
13046 display_map.max_point().row()
13047 };
13048
13049 let mut last_added_item_per_group = HashMap::default();
13050 for group in state.groups.iter_mut() {
13051 if let Some(last_id) = group.stack.last() {
13052 last_added_item_per_group.insert(*last_id, group);
13053 }
13054 }
13055
13056 for selection in columnar_selections {
13057 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13058 if above == group.above {
13059 let range = selection.display_range(&display_map).sorted();
13060 debug_assert_eq!(range.start.row(), range.end.row());
13061 let mut row = range.start.row();
13062 let positions =
13063 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13064 px(start)..px(end)
13065 } else {
13066 let start_x =
13067 display_map.x_for_display_point(range.start, &text_layout_details);
13068 let end_x =
13069 display_map.x_for_display_point(range.end, &text_layout_details);
13070 start_x.min(end_x)..start_x.max(end_x)
13071 };
13072
13073 let mut maybe_new_selection = None;
13074 while row != end_row {
13075 if above {
13076 row.0 -= 1;
13077 } else {
13078 row.0 += 1;
13079 }
13080 if let Some(new_selection) = self.selections.build_columnar_selection(
13081 &display_map,
13082 row,
13083 &positions,
13084 selection.reversed,
13085 &text_layout_details,
13086 ) {
13087 maybe_new_selection = Some(new_selection);
13088 break;
13089 }
13090 }
13091
13092 if let Some(new_selection) = maybe_new_selection {
13093 group.stack.push(new_selection.id);
13094 if above {
13095 final_selections.push(new_selection);
13096 final_selections.push(selection);
13097 } else {
13098 final_selections.push(selection);
13099 final_selections.push(new_selection);
13100 }
13101 } else {
13102 final_selections.push(selection);
13103 }
13104 } else {
13105 group.stack.pop();
13106 }
13107 } else {
13108 final_selections.push(selection);
13109 }
13110 }
13111
13112 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13113 s.select(final_selections);
13114 });
13115
13116 let final_selection_ids: HashSet<_> = self
13117 .selections
13118 .all::<Point>(cx)
13119 .iter()
13120 .map(|s| s.id)
13121 .collect();
13122 state.groups.retain_mut(|group| {
13123 // selections might get merged above so we remove invalid items from stacks
13124 group.stack.retain(|id| final_selection_ids.contains(id));
13125
13126 // single selection in stack can be treated as initial state
13127 group.stack.len() > 1
13128 });
13129
13130 if !state.groups.is_empty() {
13131 self.add_selections_state = Some(state);
13132 }
13133 }
13134
13135 fn select_match_ranges(
13136 &mut self,
13137 range: Range<usize>,
13138 reversed: bool,
13139 replace_newest: bool,
13140 auto_scroll: Option<Autoscroll>,
13141 window: &mut Window,
13142 cx: &mut Context<Editor>,
13143 ) {
13144 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13145 self.change_selections(auto_scroll, window, cx, |s| {
13146 if replace_newest {
13147 s.delete(s.newest_anchor().id);
13148 }
13149 if reversed {
13150 s.insert_range(range.end..range.start);
13151 } else {
13152 s.insert_range(range);
13153 }
13154 });
13155 }
13156
13157 pub fn select_next_match_internal(
13158 &mut self,
13159 display_map: &DisplaySnapshot,
13160 replace_newest: bool,
13161 autoscroll: Option<Autoscroll>,
13162 window: &mut Window,
13163 cx: &mut Context<Self>,
13164 ) -> Result<()> {
13165 let buffer = &display_map.buffer_snapshot;
13166 let mut selections = self.selections.all::<usize>(cx);
13167 if let Some(mut select_next_state) = self.select_next_state.take() {
13168 let query = &select_next_state.query;
13169 if !select_next_state.done {
13170 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13171 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13172 let mut next_selected_range = None;
13173
13174 let bytes_after_last_selection =
13175 buffer.bytes_in_range(last_selection.end..buffer.len());
13176 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13177 let query_matches = query
13178 .stream_find_iter(bytes_after_last_selection)
13179 .map(|result| (last_selection.end, result))
13180 .chain(
13181 query
13182 .stream_find_iter(bytes_before_first_selection)
13183 .map(|result| (0, result)),
13184 );
13185
13186 for (start_offset, query_match) in query_matches {
13187 let query_match = query_match.unwrap(); // can only fail due to I/O
13188 let offset_range =
13189 start_offset + query_match.start()..start_offset + query_match.end();
13190 let display_range = offset_range.start.to_display_point(display_map)
13191 ..offset_range.end.to_display_point(display_map);
13192
13193 if !select_next_state.wordwise
13194 || (!movement::is_inside_word(display_map, display_range.start)
13195 && !movement::is_inside_word(display_map, display_range.end))
13196 {
13197 // TODO: This is n^2, because we might check all the selections
13198 if !selections
13199 .iter()
13200 .any(|selection| selection.range().overlaps(&offset_range))
13201 {
13202 next_selected_range = Some(offset_range);
13203 break;
13204 }
13205 }
13206 }
13207
13208 if let Some(next_selected_range) = next_selected_range {
13209 self.select_match_ranges(
13210 next_selected_range,
13211 last_selection.reversed,
13212 replace_newest,
13213 autoscroll,
13214 window,
13215 cx,
13216 );
13217 } else {
13218 select_next_state.done = true;
13219 }
13220 }
13221
13222 self.select_next_state = Some(select_next_state);
13223 } else {
13224 let mut only_carets = true;
13225 let mut same_text_selected = true;
13226 let mut selected_text = None;
13227
13228 let mut selections_iter = selections.iter().peekable();
13229 while let Some(selection) = selections_iter.next() {
13230 if selection.start != selection.end {
13231 only_carets = false;
13232 }
13233
13234 if same_text_selected {
13235 if selected_text.is_none() {
13236 selected_text =
13237 Some(buffer.text_for_range(selection.range()).collect::<String>());
13238 }
13239
13240 if let Some(next_selection) = selections_iter.peek() {
13241 if next_selection.range().len() == selection.range().len() {
13242 let next_selected_text = buffer
13243 .text_for_range(next_selection.range())
13244 .collect::<String>();
13245 if Some(next_selected_text) != selected_text {
13246 same_text_selected = false;
13247 selected_text = None;
13248 }
13249 } else {
13250 same_text_selected = false;
13251 selected_text = None;
13252 }
13253 }
13254 }
13255 }
13256
13257 if only_carets {
13258 for selection in &mut selections {
13259 let word_range = movement::surrounding_word(
13260 display_map,
13261 selection.start.to_display_point(display_map),
13262 );
13263 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13264 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13265 selection.goal = SelectionGoal::None;
13266 selection.reversed = false;
13267 self.select_match_ranges(
13268 selection.start..selection.end,
13269 selection.reversed,
13270 replace_newest,
13271 autoscroll,
13272 window,
13273 cx,
13274 );
13275 }
13276
13277 if selections.len() == 1 {
13278 let selection = selections
13279 .last()
13280 .expect("ensured that there's only one selection");
13281 let query = buffer
13282 .text_for_range(selection.start..selection.end)
13283 .collect::<String>();
13284 let is_empty = query.is_empty();
13285 let select_state = SelectNextState {
13286 query: AhoCorasick::new(&[query])?,
13287 wordwise: true,
13288 done: is_empty,
13289 };
13290 self.select_next_state = Some(select_state);
13291 } else {
13292 self.select_next_state = None;
13293 }
13294 } else if let Some(selected_text) = selected_text {
13295 self.select_next_state = Some(SelectNextState {
13296 query: AhoCorasick::new(&[selected_text])?,
13297 wordwise: false,
13298 done: false,
13299 });
13300 self.select_next_match_internal(
13301 display_map,
13302 replace_newest,
13303 autoscroll,
13304 window,
13305 cx,
13306 )?;
13307 }
13308 }
13309 Ok(())
13310 }
13311
13312 pub fn select_all_matches(
13313 &mut self,
13314 _action: &SelectAllMatches,
13315 window: &mut Window,
13316 cx: &mut Context<Self>,
13317 ) -> Result<()> {
13318 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13319
13320 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13321
13322 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13323 let Some(select_next_state) = self.select_next_state.as_mut() else {
13324 return Ok(());
13325 };
13326 if select_next_state.done {
13327 return Ok(());
13328 }
13329
13330 let mut new_selections = Vec::new();
13331
13332 let reversed = self.selections.oldest::<usize>(cx).reversed;
13333 let buffer = &display_map.buffer_snapshot;
13334 let query_matches = select_next_state
13335 .query
13336 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13337
13338 for query_match in query_matches.into_iter() {
13339 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13340 let offset_range = if reversed {
13341 query_match.end()..query_match.start()
13342 } else {
13343 query_match.start()..query_match.end()
13344 };
13345 let display_range = offset_range.start.to_display_point(&display_map)
13346 ..offset_range.end.to_display_point(&display_map);
13347
13348 if !select_next_state.wordwise
13349 || (!movement::is_inside_word(&display_map, display_range.start)
13350 && !movement::is_inside_word(&display_map, display_range.end))
13351 {
13352 new_selections.push(offset_range.start..offset_range.end);
13353 }
13354 }
13355
13356 select_next_state.done = true;
13357 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13358 self.change_selections(None, window, cx, |selections| {
13359 selections.select_ranges(new_selections)
13360 });
13361
13362 Ok(())
13363 }
13364
13365 pub fn select_next(
13366 &mut self,
13367 action: &SelectNext,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) -> Result<()> {
13371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13372 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13373 self.select_next_match_internal(
13374 &display_map,
13375 action.replace_newest,
13376 Some(Autoscroll::newest()),
13377 window,
13378 cx,
13379 )?;
13380 Ok(())
13381 }
13382
13383 pub fn select_previous(
13384 &mut self,
13385 action: &SelectPrevious,
13386 window: &mut Window,
13387 cx: &mut Context<Self>,
13388 ) -> Result<()> {
13389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13390 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13391 let buffer = &display_map.buffer_snapshot;
13392 let mut selections = self.selections.all::<usize>(cx);
13393 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13394 let query = &select_prev_state.query;
13395 if !select_prev_state.done {
13396 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13397 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13398 let mut next_selected_range = None;
13399 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13400 let bytes_before_last_selection =
13401 buffer.reversed_bytes_in_range(0..last_selection.start);
13402 let bytes_after_first_selection =
13403 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13404 let query_matches = query
13405 .stream_find_iter(bytes_before_last_selection)
13406 .map(|result| (last_selection.start, result))
13407 .chain(
13408 query
13409 .stream_find_iter(bytes_after_first_selection)
13410 .map(|result| (buffer.len(), result)),
13411 );
13412 for (end_offset, query_match) in query_matches {
13413 let query_match = query_match.unwrap(); // can only fail due to I/O
13414 let offset_range =
13415 end_offset - query_match.end()..end_offset - query_match.start();
13416 let display_range = offset_range.start.to_display_point(&display_map)
13417 ..offset_range.end.to_display_point(&display_map);
13418
13419 if !select_prev_state.wordwise
13420 || (!movement::is_inside_word(&display_map, display_range.start)
13421 && !movement::is_inside_word(&display_map, display_range.end))
13422 {
13423 next_selected_range = Some(offset_range);
13424 break;
13425 }
13426 }
13427
13428 if let Some(next_selected_range) = next_selected_range {
13429 self.select_match_ranges(
13430 next_selected_range,
13431 last_selection.reversed,
13432 action.replace_newest,
13433 Some(Autoscroll::newest()),
13434 window,
13435 cx,
13436 );
13437 } else {
13438 select_prev_state.done = true;
13439 }
13440 }
13441
13442 self.select_prev_state = Some(select_prev_state);
13443 } else {
13444 let mut only_carets = true;
13445 let mut same_text_selected = true;
13446 let mut selected_text = None;
13447
13448 let mut selections_iter = selections.iter().peekable();
13449 while let Some(selection) = selections_iter.next() {
13450 if selection.start != selection.end {
13451 only_carets = false;
13452 }
13453
13454 if same_text_selected {
13455 if selected_text.is_none() {
13456 selected_text =
13457 Some(buffer.text_for_range(selection.range()).collect::<String>());
13458 }
13459
13460 if let Some(next_selection) = selections_iter.peek() {
13461 if next_selection.range().len() == selection.range().len() {
13462 let next_selected_text = buffer
13463 .text_for_range(next_selection.range())
13464 .collect::<String>();
13465 if Some(next_selected_text) != selected_text {
13466 same_text_selected = false;
13467 selected_text = None;
13468 }
13469 } else {
13470 same_text_selected = false;
13471 selected_text = None;
13472 }
13473 }
13474 }
13475 }
13476
13477 if only_carets {
13478 for selection in &mut selections {
13479 let word_range = movement::surrounding_word(
13480 &display_map,
13481 selection.start.to_display_point(&display_map),
13482 );
13483 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13484 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13485 selection.goal = SelectionGoal::None;
13486 selection.reversed = false;
13487 self.select_match_ranges(
13488 selection.start..selection.end,
13489 selection.reversed,
13490 action.replace_newest,
13491 Some(Autoscroll::newest()),
13492 window,
13493 cx,
13494 );
13495 }
13496 if selections.len() == 1 {
13497 let selection = selections
13498 .last()
13499 .expect("ensured that there's only one selection");
13500 let query = buffer
13501 .text_for_range(selection.start..selection.end)
13502 .collect::<String>();
13503 let is_empty = query.is_empty();
13504 let select_state = SelectNextState {
13505 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13506 wordwise: true,
13507 done: is_empty,
13508 };
13509 self.select_prev_state = Some(select_state);
13510 } else {
13511 self.select_prev_state = None;
13512 }
13513 } else if let Some(selected_text) = selected_text {
13514 self.select_prev_state = Some(SelectNextState {
13515 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13516 wordwise: false,
13517 done: false,
13518 });
13519 self.select_previous(action, window, cx)?;
13520 }
13521 }
13522 Ok(())
13523 }
13524
13525 pub fn find_next_match(
13526 &mut self,
13527 _: &FindNextMatch,
13528 window: &mut Window,
13529 cx: &mut Context<Self>,
13530 ) -> Result<()> {
13531 let selections = self.selections.disjoint_anchors();
13532 match selections.first() {
13533 Some(first) if selections.len() >= 2 => {
13534 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13535 s.select_ranges([first.range()]);
13536 });
13537 }
13538 _ => self.select_next(
13539 &SelectNext {
13540 replace_newest: true,
13541 },
13542 window,
13543 cx,
13544 )?,
13545 }
13546 Ok(())
13547 }
13548
13549 pub fn find_previous_match(
13550 &mut self,
13551 _: &FindPreviousMatch,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) -> Result<()> {
13555 let selections = self.selections.disjoint_anchors();
13556 match selections.last() {
13557 Some(last) if selections.len() >= 2 => {
13558 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13559 s.select_ranges([last.range()]);
13560 });
13561 }
13562 _ => self.select_previous(
13563 &SelectPrevious {
13564 replace_newest: true,
13565 },
13566 window,
13567 cx,
13568 )?,
13569 }
13570 Ok(())
13571 }
13572
13573 pub fn toggle_comments(
13574 &mut self,
13575 action: &ToggleComments,
13576 window: &mut Window,
13577 cx: &mut Context<Self>,
13578 ) {
13579 if self.read_only(cx) {
13580 return;
13581 }
13582 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13583 let text_layout_details = &self.text_layout_details(window);
13584 self.transact(window, cx, |this, window, cx| {
13585 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13586 let mut edits = Vec::new();
13587 let mut selection_edit_ranges = Vec::new();
13588 let mut last_toggled_row = None;
13589 let snapshot = this.buffer.read(cx).read(cx);
13590 let empty_str: Arc<str> = Arc::default();
13591 let mut suffixes_inserted = Vec::new();
13592 let ignore_indent = action.ignore_indent;
13593
13594 fn comment_prefix_range(
13595 snapshot: &MultiBufferSnapshot,
13596 row: MultiBufferRow,
13597 comment_prefix: &str,
13598 comment_prefix_whitespace: &str,
13599 ignore_indent: bool,
13600 ) -> Range<Point> {
13601 let indent_size = if ignore_indent {
13602 0
13603 } else {
13604 snapshot.indent_size_for_line(row).len
13605 };
13606
13607 let start = Point::new(row.0, indent_size);
13608
13609 let mut line_bytes = snapshot
13610 .bytes_in_range(start..snapshot.max_point())
13611 .flatten()
13612 .copied();
13613
13614 // If this line currently begins with the line comment prefix, then record
13615 // the range containing the prefix.
13616 if line_bytes
13617 .by_ref()
13618 .take(comment_prefix.len())
13619 .eq(comment_prefix.bytes())
13620 {
13621 // Include any whitespace that matches the comment prefix.
13622 let matching_whitespace_len = line_bytes
13623 .zip(comment_prefix_whitespace.bytes())
13624 .take_while(|(a, b)| a == b)
13625 .count() as u32;
13626 let end = Point::new(
13627 start.row,
13628 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13629 );
13630 start..end
13631 } else {
13632 start..start
13633 }
13634 }
13635
13636 fn comment_suffix_range(
13637 snapshot: &MultiBufferSnapshot,
13638 row: MultiBufferRow,
13639 comment_suffix: &str,
13640 comment_suffix_has_leading_space: bool,
13641 ) -> Range<Point> {
13642 let end = Point::new(row.0, snapshot.line_len(row));
13643 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13644
13645 let mut line_end_bytes = snapshot
13646 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13647 .flatten()
13648 .copied();
13649
13650 let leading_space_len = if suffix_start_column > 0
13651 && line_end_bytes.next() == Some(b' ')
13652 && comment_suffix_has_leading_space
13653 {
13654 1
13655 } else {
13656 0
13657 };
13658
13659 // If this line currently begins with the line comment prefix, then record
13660 // the range containing the prefix.
13661 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13662 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13663 start..end
13664 } else {
13665 end..end
13666 }
13667 }
13668
13669 // TODO: Handle selections that cross excerpts
13670 for selection in &mut selections {
13671 let start_column = snapshot
13672 .indent_size_for_line(MultiBufferRow(selection.start.row))
13673 .len;
13674 let language = if let Some(language) =
13675 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13676 {
13677 language
13678 } else {
13679 continue;
13680 };
13681
13682 selection_edit_ranges.clear();
13683
13684 // If multiple selections contain a given row, avoid processing that
13685 // row more than once.
13686 let mut start_row = MultiBufferRow(selection.start.row);
13687 if last_toggled_row == Some(start_row) {
13688 start_row = start_row.next_row();
13689 }
13690 let end_row =
13691 if selection.end.row > selection.start.row && selection.end.column == 0 {
13692 MultiBufferRow(selection.end.row - 1)
13693 } else {
13694 MultiBufferRow(selection.end.row)
13695 };
13696 last_toggled_row = Some(end_row);
13697
13698 if start_row > end_row {
13699 continue;
13700 }
13701
13702 // If the language has line comments, toggle those.
13703 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13704
13705 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13706 if ignore_indent {
13707 full_comment_prefixes = full_comment_prefixes
13708 .into_iter()
13709 .map(|s| Arc::from(s.trim_end()))
13710 .collect();
13711 }
13712
13713 if !full_comment_prefixes.is_empty() {
13714 let first_prefix = full_comment_prefixes
13715 .first()
13716 .expect("prefixes is non-empty");
13717 let prefix_trimmed_lengths = full_comment_prefixes
13718 .iter()
13719 .map(|p| p.trim_end_matches(' ').len())
13720 .collect::<SmallVec<[usize; 4]>>();
13721
13722 let mut all_selection_lines_are_comments = true;
13723
13724 for row in start_row.0..=end_row.0 {
13725 let row = MultiBufferRow(row);
13726 if start_row < end_row && snapshot.is_line_blank(row) {
13727 continue;
13728 }
13729
13730 let prefix_range = full_comment_prefixes
13731 .iter()
13732 .zip(prefix_trimmed_lengths.iter().copied())
13733 .map(|(prefix, trimmed_prefix_len)| {
13734 comment_prefix_range(
13735 snapshot.deref(),
13736 row,
13737 &prefix[..trimmed_prefix_len],
13738 &prefix[trimmed_prefix_len..],
13739 ignore_indent,
13740 )
13741 })
13742 .max_by_key(|range| range.end.column - range.start.column)
13743 .expect("prefixes is non-empty");
13744
13745 if prefix_range.is_empty() {
13746 all_selection_lines_are_comments = false;
13747 }
13748
13749 selection_edit_ranges.push(prefix_range);
13750 }
13751
13752 if all_selection_lines_are_comments {
13753 edits.extend(
13754 selection_edit_ranges
13755 .iter()
13756 .cloned()
13757 .map(|range| (range, empty_str.clone())),
13758 );
13759 } else {
13760 let min_column = selection_edit_ranges
13761 .iter()
13762 .map(|range| range.start.column)
13763 .min()
13764 .unwrap_or(0);
13765 edits.extend(selection_edit_ranges.iter().map(|range| {
13766 let position = Point::new(range.start.row, min_column);
13767 (position..position, first_prefix.clone())
13768 }));
13769 }
13770 } else if let Some((full_comment_prefix, comment_suffix)) =
13771 language.block_comment_delimiters()
13772 {
13773 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13774 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13775 let prefix_range = comment_prefix_range(
13776 snapshot.deref(),
13777 start_row,
13778 comment_prefix,
13779 comment_prefix_whitespace,
13780 ignore_indent,
13781 );
13782 let suffix_range = comment_suffix_range(
13783 snapshot.deref(),
13784 end_row,
13785 comment_suffix.trim_start_matches(' '),
13786 comment_suffix.starts_with(' '),
13787 );
13788
13789 if prefix_range.is_empty() || suffix_range.is_empty() {
13790 edits.push((
13791 prefix_range.start..prefix_range.start,
13792 full_comment_prefix.clone(),
13793 ));
13794 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13795 suffixes_inserted.push((end_row, comment_suffix.len()));
13796 } else {
13797 edits.push((prefix_range, empty_str.clone()));
13798 edits.push((suffix_range, empty_str.clone()));
13799 }
13800 } else {
13801 continue;
13802 }
13803 }
13804
13805 drop(snapshot);
13806 this.buffer.update(cx, |buffer, cx| {
13807 buffer.edit(edits, None, cx);
13808 });
13809
13810 // Adjust selections so that they end before any comment suffixes that
13811 // were inserted.
13812 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13813 let mut selections = this.selections.all::<Point>(cx);
13814 let snapshot = this.buffer.read(cx).read(cx);
13815 for selection in &mut selections {
13816 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13817 match row.cmp(&MultiBufferRow(selection.end.row)) {
13818 Ordering::Less => {
13819 suffixes_inserted.next();
13820 continue;
13821 }
13822 Ordering::Greater => break,
13823 Ordering::Equal => {
13824 if selection.end.column == snapshot.line_len(row) {
13825 if selection.is_empty() {
13826 selection.start.column -= suffix_len as u32;
13827 }
13828 selection.end.column -= suffix_len as u32;
13829 }
13830 break;
13831 }
13832 }
13833 }
13834 }
13835
13836 drop(snapshot);
13837 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13838 s.select(selections)
13839 });
13840
13841 let selections = this.selections.all::<Point>(cx);
13842 let selections_on_single_row = selections.windows(2).all(|selections| {
13843 selections[0].start.row == selections[1].start.row
13844 && selections[0].end.row == selections[1].end.row
13845 && selections[0].start.row == selections[0].end.row
13846 });
13847 let selections_selecting = selections
13848 .iter()
13849 .any(|selection| selection.start != selection.end);
13850 let advance_downwards = action.advance_downwards
13851 && selections_on_single_row
13852 && !selections_selecting
13853 && !matches!(this.mode, EditorMode::SingleLine { .. });
13854
13855 if advance_downwards {
13856 let snapshot = this.buffer.read(cx).snapshot(cx);
13857
13858 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13859 s.move_cursors_with(|display_snapshot, display_point, _| {
13860 let mut point = display_point.to_point(display_snapshot);
13861 point.row += 1;
13862 point = snapshot.clip_point(point, Bias::Left);
13863 let display_point = point.to_display_point(display_snapshot);
13864 let goal = SelectionGoal::HorizontalPosition(
13865 display_snapshot
13866 .x_for_display_point(display_point, text_layout_details)
13867 .into(),
13868 );
13869 (display_point, goal)
13870 })
13871 });
13872 }
13873 });
13874 }
13875
13876 pub fn select_enclosing_symbol(
13877 &mut self,
13878 _: &SelectEnclosingSymbol,
13879 window: &mut Window,
13880 cx: &mut Context<Self>,
13881 ) {
13882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13883
13884 let buffer = self.buffer.read(cx).snapshot(cx);
13885 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13886
13887 fn update_selection(
13888 selection: &Selection<usize>,
13889 buffer_snap: &MultiBufferSnapshot,
13890 ) -> Option<Selection<usize>> {
13891 let cursor = selection.head();
13892 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13893 for symbol in symbols.iter().rev() {
13894 let start = symbol.range.start.to_offset(buffer_snap);
13895 let end = symbol.range.end.to_offset(buffer_snap);
13896 let new_range = start..end;
13897 if start < selection.start || end > selection.end {
13898 return Some(Selection {
13899 id: selection.id,
13900 start: new_range.start,
13901 end: new_range.end,
13902 goal: SelectionGoal::None,
13903 reversed: selection.reversed,
13904 });
13905 }
13906 }
13907 None
13908 }
13909
13910 let mut selected_larger_symbol = false;
13911 let new_selections = old_selections
13912 .iter()
13913 .map(|selection| match update_selection(selection, &buffer) {
13914 Some(new_selection) => {
13915 if new_selection.range() != selection.range() {
13916 selected_larger_symbol = true;
13917 }
13918 new_selection
13919 }
13920 None => selection.clone(),
13921 })
13922 .collect::<Vec<_>>();
13923
13924 if selected_larger_symbol {
13925 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13926 s.select(new_selections);
13927 });
13928 }
13929 }
13930
13931 pub fn select_larger_syntax_node(
13932 &mut self,
13933 _: &SelectLargerSyntaxNode,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) {
13937 let Some(visible_row_count) = self.visible_row_count() else {
13938 return;
13939 };
13940 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13941 if old_selections.is_empty() {
13942 return;
13943 }
13944
13945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13946
13947 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13948 let buffer = self.buffer.read(cx).snapshot(cx);
13949
13950 let mut selected_larger_node = false;
13951 let mut new_selections = old_selections
13952 .iter()
13953 .map(|selection| {
13954 let old_range = selection.start..selection.end;
13955
13956 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13957 // manually select word at selection
13958 if ["string_content", "inline"].contains(&node.kind()) {
13959 let word_range = {
13960 let display_point = buffer
13961 .offset_to_point(old_range.start)
13962 .to_display_point(&display_map);
13963 let Range { start, end } =
13964 movement::surrounding_word(&display_map, display_point);
13965 start.to_point(&display_map).to_offset(&buffer)
13966 ..end.to_point(&display_map).to_offset(&buffer)
13967 };
13968 // ignore if word is already selected
13969 if !word_range.is_empty() && old_range != word_range {
13970 let last_word_range = {
13971 let display_point = buffer
13972 .offset_to_point(old_range.end)
13973 .to_display_point(&display_map);
13974 let Range { start, end } =
13975 movement::surrounding_word(&display_map, display_point);
13976 start.to_point(&display_map).to_offset(&buffer)
13977 ..end.to_point(&display_map).to_offset(&buffer)
13978 };
13979 // only select word if start and end point belongs to same word
13980 if word_range == last_word_range {
13981 selected_larger_node = true;
13982 return Selection {
13983 id: selection.id,
13984 start: word_range.start,
13985 end: word_range.end,
13986 goal: SelectionGoal::None,
13987 reversed: selection.reversed,
13988 };
13989 }
13990 }
13991 }
13992 }
13993
13994 let mut new_range = old_range.clone();
13995 while let Some((_node, containing_range)) =
13996 buffer.syntax_ancestor(new_range.clone())
13997 {
13998 new_range = match containing_range {
13999 MultiOrSingleBufferOffsetRange::Single(_) => break,
14000 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14001 };
14002 if !display_map.intersects_fold(new_range.start)
14003 && !display_map.intersects_fold(new_range.end)
14004 {
14005 break;
14006 }
14007 }
14008
14009 selected_larger_node |= new_range != old_range;
14010 Selection {
14011 id: selection.id,
14012 start: new_range.start,
14013 end: new_range.end,
14014 goal: SelectionGoal::None,
14015 reversed: selection.reversed,
14016 }
14017 })
14018 .collect::<Vec<_>>();
14019
14020 if !selected_larger_node {
14021 return; // don't put this call in the history
14022 }
14023
14024 // scroll based on transformation done to the last selection created by the user
14025 let (last_old, last_new) = old_selections
14026 .last()
14027 .zip(new_selections.last().cloned())
14028 .expect("old_selections isn't empty");
14029
14030 // revert selection
14031 let is_selection_reversed = {
14032 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14033 new_selections.last_mut().expect("checked above").reversed =
14034 should_newest_selection_be_reversed;
14035 should_newest_selection_be_reversed
14036 };
14037
14038 if selected_larger_node {
14039 self.select_syntax_node_history.disable_clearing = true;
14040 self.change_selections(None, window, cx, |s| {
14041 s.select(new_selections.clone());
14042 });
14043 self.select_syntax_node_history.disable_clearing = false;
14044 }
14045
14046 let start_row = last_new.start.to_display_point(&display_map).row().0;
14047 let end_row = last_new.end.to_display_point(&display_map).row().0;
14048 let selection_height = end_row - start_row + 1;
14049 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14050
14051 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14052 let scroll_behavior = if fits_on_the_screen {
14053 self.request_autoscroll(Autoscroll::fit(), cx);
14054 SelectSyntaxNodeScrollBehavior::FitSelection
14055 } else if is_selection_reversed {
14056 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14057 SelectSyntaxNodeScrollBehavior::CursorTop
14058 } else {
14059 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14060 SelectSyntaxNodeScrollBehavior::CursorBottom
14061 };
14062
14063 self.select_syntax_node_history.push((
14064 old_selections,
14065 scroll_behavior,
14066 is_selection_reversed,
14067 ));
14068 }
14069
14070 pub fn select_smaller_syntax_node(
14071 &mut self,
14072 _: &SelectSmallerSyntaxNode,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14077
14078 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14079 self.select_syntax_node_history.pop()
14080 {
14081 if let Some(selection) = selections.last_mut() {
14082 selection.reversed = is_selection_reversed;
14083 }
14084
14085 self.select_syntax_node_history.disable_clearing = true;
14086 self.change_selections(None, window, cx, |s| {
14087 s.select(selections.to_vec());
14088 });
14089 self.select_syntax_node_history.disable_clearing = false;
14090
14091 match scroll_behavior {
14092 SelectSyntaxNodeScrollBehavior::CursorTop => {
14093 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14094 }
14095 SelectSyntaxNodeScrollBehavior::FitSelection => {
14096 self.request_autoscroll(Autoscroll::fit(), cx);
14097 }
14098 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14099 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14100 }
14101 }
14102 }
14103 }
14104
14105 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14106 if !EditorSettings::get_global(cx).gutter.runnables {
14107 self.clear_tasks();
14108 return Task::ready(());
14109 }
14110 let project = self.project.as_ref().map(Entity::downgrade);
14111 let task_sources = self.lsp_task_sources(cx);
14112 let multi_buffer = self.buffer.downgrade();
14113 cx.spawn_in(window, async move |editor, cx| {
14114 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14115 let Some(project) = project.and_then(|p| p.upgrade()) else {
14116 return;
14117 };
14118 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14119 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14120 }) else {
14121 return;
14122 };
14123
14124 let hide_runnables = project
14125 .update(cx, |project, cx| {
14126 // Do not display any test indicators in non-dev server remote projects.
14127 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14128 })
14129 .unwrap_or(true);
14130 if hide_runnables {
14131 return;
14132 }
14133 let new_rows =
14134 cx.background_spawn({
14135 let snapshot = display_snapshot.clone();
14136 async move {
14137 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14138 }
14139 })
14140 .await;
14141 let Ok(lsp_tasks) =
14142 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14143 else {
14144 return;
14145 };
14146 let lsp_tasks = lsp_tasks.await;
14147
14148 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14149 lsp_tasks
14150 .into_iter()
14151 .flat_map(|(kind, tasks)| {
14152 tasks.into_iter().filter_map(move |(location, task)| {
14153 Some((kind.clone(), location?, task))
14154 })
14155 })
14156 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14157 let buffer = location.target.buffer;
14158 let buffer_snapshot = buffer.read(cx).snapshot();
14159 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14160 |(excerpt_id, snapshot, _)| {
14161 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14162 display_snapshot
14163 .buffer_snapshot
14164 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14165 } else {
14166 None
14167 }
14168 },
14169 );
14170 if let Some(offset) = offset {
14171 let task_buffer_range =
14172 location.target.range.to_point(&buffer_snapshot);
14173 let context_buffer_range =
14174 task_buffer_range.to_offset(&buffer_snapshot);
14175 let context_range = BufferOffset(context_buffer_range.start)
14176 ..BufferOffset(context_buffer_range.end);
14177
14178 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14179 .or_insert_with(|| RunnableTasks {
14180 templates: Vec::new(),
14181 offset,
14182 column: task_buffer_range.start.column,
14183 extra_variables: HashMap::default(),
14184 context_range,
14185 })
14186 .templates
14187 .push((kind, task.original_task().clone()));
14188 }
14189
14190 acc
14191 })
14192 }) else {
14193 return;
14194 };
14195
14196 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14197 buffer.language_settings(cx).tasks.prefer_lsp
14198 }) else {
14199 return;
14200 };
14201
14202 let rows = Self::runnable_rows(
14203 project,
14204 display_snapshot,
14205 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14206 new_rows,
14207 cx.clone(),
14208 )
14209 .await;
14210 editor
14211 .update(cx, |editor, _| {
14212 editor.clear_tasks();
14213 for (key, mut value) in rows {
14214 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14215 value.templates.extend(lsp_tasks.templates);
14216 }
14217
14218 editor.insert_tasks(key, value);
14219 }
14220 for (key, value) in lsp_tasks_by_rows {
14221 editor.insert_tasks(key, value);
14222 }
14223 })
14224 .ok();
14225 })
14226 }
14227 fn fetch_runnable_ranges(
14228 snapshot: &DisplaySnapshot,
14229 range: Range<Anchor>,
14230 ) -> Vec<language::RunnableRange> {
14231 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14232 }
14233
14234 fn runnable_rows(
14235 project: Entity<Project>,
14236 snapshot: DisplaySnapshot,
14237 prefer_lsp: bool,
14238 runnable_ranges: Vec<RunnableRange>,
14239 cx: AsyncWindowContext,
14240 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14241 cx.spawn(async move |cx| {
14242 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14243 for mut runnable in runnable_ranges {
14244 let Some(tasks) = cx
14245 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14246 .ok()
14247 else {
14248 continue;
14249 };
14250 let mut tasks = tasks.await;
14251
14252 if prefer_lsp {
14253 tasks.retain(|(task_kind, _)| {
14254 !matches!(task_kind, TaskSourceKind::Language { .. })
14255 });
14256 }
14257 if tasks.is_empty() {
14258 continue;
14259 }
14260
14261 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14262 let Some(row) = snapshot
14263 .buffer_snapshot
14264 .buffer_line_for_row(MultiBufferRow(point.row))
14265 .map(|(_, range)| range.start.row)
14266 else {
14267 continue;
14268 };
14269
14270 let context_range =
14271 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14272 runnable_rows.push((
14273 (runnable.buffer_id, row),
14274 RunnableTasks {
14275 templates: tasks,
14276 offset: snapshot
14277 .buffer_snapshot
14278 .anchor_before(runnable.run_range.start),
14279 context_range,
14280 column: point.column,
14281 extra_variables: runnable.extra_captures,
14282 },
14283 ));
14284 }
14285 runnable_rows
14286 })
14287 }
14288
14289 fn templates_with_tags(
14290 project: &Entity<Project>,
14291 runnable: &mut Runnable,
14292 cx: &mut App,
14293 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14294 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14295 let (worktree_id, file) = project
14296 .buffer_for_id(runnable.buffer, cx)
14297 .and_then(|buffer| buffer.read(cx).file())
14298 .map(|file| (file.worktree_id(cx), file.clone()))
14299 .unzip();
14300
14301 (
14302 project.task_store().read(cx).task_inventory().cloned(),
14303 worktree_id,
14304 file,
14305 )
14306 });
14307
14308 let tags = mem::take(&mut runnable.tags);
14309 let language = runnable.language.clone();
14310 cx.spawn(async move |cx| {
14311 let mut templates_with_tags = Vec::new();
14312 if let Some(inventory) = inventory {
14313 for RunnableTag(tag) in tags {
14314 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14315 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14316 }) else {
14317 return templates_with_tags;
14318 };
14319 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14320 move |(_, template)| {
14321 template.tags.iter().any(|source_tag| source_tag == &tag)
14322 },
14323 ));
14324 }
14325 }
14326 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14327
14328 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14329 // Strongest source wins; if we have worktree tag binding, prefer that to
14330 // global and language bindings;
14331 // if we have a global binding, prefer that to language binding.
14332 let first_mismatch = templates_with_tags
14333 .iter()
14334 .position(|(tag_source, _)| tag_source != leading_tag_source);
14335 if let Some(index) = first_mismatch {
14336 templates_with_tags.truncate(index);
14337 }
14338 }
14339
14340 templates_with_tags
14341 })
14342 }
14343
14344 pub fn move_to_enclosing_bracket(
14345 &mut self,
14346 _: &MoveToEnclosingBracket,
14347 window: &mut Window,
14348 cx: &mut Context<Self>,
14349 ) {
14350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14351 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14352 s.move_offsets_with(|snapshot, selection| {
14353 let Some(enclosing_bracket_ranges) =
14354 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14355 else {
14356 return;
14357 };
14358
14359 let mut best_length = usize::MAX;
14360 let mut best_inside = false;
14361 let mut best_in_bracket_range = false;
14362 let mut best_destination = None;
14363 for (open, close) in enclosing_bracket_ranges {
14364 let close = close.to_inclusive();
14365 let length = close.end() - open.start;
14366 let inside = selection.start >= open.end && selection.end <= *close.start();
14367 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14368 || close.contains(&selection.head());
14369
14370 // If best is next to a bracket and current isn't, skip
14371 if !in_bracket_range && best_in_bracket_range {
14372 continue;
14373 }
14374
14375 // Prefer smaller lengths unless best is inside and current isn't
14376 if length > best_length && (best_inside || !inside) {
14377 continue;
14378 }
14379
14380 best_length = length;
14381 best_inside = inside;
14382 best_in_bracket_range = in_bracket_range;
14383 best_destination = Some(
14384 if close.contains(&selection.start) && close.contains(&selection.end) {
14385 if inside { open.end } else { open.start }
14386 } else if inside {
14387 *close.start()
14388 } else {
14389 *close.end()
14390 },
14391 );
14392 }
14393
14394 if let Some(destination) = best_destination {
14395 selection.collapse_to(destination, SelectionGoal::None);
14396 }
14397 })
14398 });
14399 }
14400
14401 pub fn undo_selection(
14402 &mut self,
14403 _: &UndoSelection,
14404 window: &mut Window,
14405 cx: &mut Context<Self>,
14406 ) {
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14409 self.selection_history.mode = SelectionHistoryMode::Undoing;
14410 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14411 this.end_selection(window, cx);
14412 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14413 s.select_anchors(entry.selections.to_vec())
14414 });
14415 });
14416 self.selection_history.mode = SelectionHistoryMode::Normal;
14417
14418 self.select_next_state = entry.select_next_state;
14419 self.select_prev_state = entry.select_prev_state;
14420 self.add_selections_state = entry.add_selections_state;
14421 }
14422 }
14423
14424 pub fn redo_selection(
14425 &mut self,
14426 _: &RedoSelection,
14427 window: &mut Window,
14428 cx: &mut Context<Self>,
14429 ) {
14430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14431 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14432 self.selection_history.mode = SelectionHistoryMode::Redoing;
14433 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14434 this.end_selection(window, cx);
14435 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14436 s.select_anchors(entry.selections.to_vec())
14437 });
14438 });
14439 self.selection_history.mode = SelectionHistoryMode::Normal;
14440
14441 self.select_next_state = entry.select_next_state;
14442 self.select_prev_state = entry.select_prev_state;
14443 self.add_selections_state = entry.add_selections_state;
14444 }
14445 }
14446
14447 pub fn expand_excerpts(
14448 &mut self,
14449 action: &ExpandExcerpts,
14450 _: &mut Window,
14451 cx: &mut Context<Self>,
14452 ) {
14453 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14454 }
14455
14456 pub fn expand_excerpts_down(
14457 &mut self,
14458 action: &ExpandExcerptsDown,
14459 _: &mut Window,
14460 cx: &mut Context<Self>,
14461 ) {
14462 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14463 }
14464
14465 pub fn expand_excerpts_up(
14466 &mut self,
14467 action: &ExpandExcerptsUp,
14468 _: &mut Window,
14469 cx: &mut Context<Self>,
14470 ) {
14471 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14472 }
14473
14474 pub fn expand_excerpts_for_direction(
14475 &mut self,
14476 lines: u32,
14477 direction: ExpandExcerptDirection,
14478
14479 cx: &mut Context<Self>,
14480 ) {
14481 let selections = self.selections.disjoint_anchors();
14482
14483 let lines = if lines == 0 {
14484 EditorSettings::get_global(cx).expand_excerpt_lines
14485 } else {
14486 lines
14487 };
14488
14489 self.buffer.update(cx, |buffer, cx| {
14490 let snapshot = buffer.snapshot(cx);
14491 let mut excerpt_ids = selections
14492 .iter()
14493 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14494 .collect::<Vec<_>>();
14495 excerpt_ids.sort();
14496 excerpt_ids.dedup();
14497 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14498 })
14499 }
14500
14501 pub fn expand_excerpt(
14502 &mut self,
14503 excerpt: ExcerptId,
14504 direction: ExpandExcerptDirection,
14505 window: &mut Window,
14506 cx: &mut Context<Self>,
14507 ) {
14508 let current_scroll_position = self.scroll_position(cx);
14509 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14510 let mut should_scroll_up = false;
14511
14512 if direction == ExpandExcerptDirection::Down {
14513 let multi_buffer = self.buffer.read(cx);
14514 let snapshot = multi_buffer.snapshot(cx);
14515 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14516 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14517 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14518 let buffer_snapshot = buffer.read(cx).snapshot();
14519 let excerpt_end_row =
14520 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14521 let last_row = buffer_snapshot.max_point().row;
14522 let lines_below = last_row.saturating_sub(excerpt_end_row);
14523 should_scroll_up = lines_below >= lines_to_expand;
14524 }
14525 }
14526 }
14527 }
14528
14529 self.buffer.update(cx, |buffer, cx| {
14530 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14531 });
14532
14533 if should_scroll_up {
14534 let new_scroll_position =
14535 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14536 self.set_scroll_position(new_scroll_position, window, cx);
14537 }
14538 }
14539
14540 pub fn go_to_singleton_buffer_point(
14541 &mut self,
14542 point: Point,
14543 window: &mut Window,
14544 cx: &mut Context<Self>,
14545 ) {
14546 self.go_to_singleton_buffer_range(point..point, window, cx);
14547 }
14548
14549 pub fn go_to_singleton_buffer_range(
14550 &mut self,
14551 range: Range<Point>,
14552 window: &mut Window,
14553 cx: &mut Context<Self>,
14554 ) {
14555 let multibuffer = self.buffer().read(cx);
14556 let Some(buffer) = multibuffer.as_singleton() else {
14557 return;
14558 };
14559 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14560 return;
14561 };
14562 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14563 return;
14564 };
14565 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14566 s.select_anchor_ranges([start..end])
14567 });
14568 }
14569
14570 pub fn go_to_diagnostic(
14571 &mut self,
14572 _: &GoToDiagnostic,
14573 window: &mut Window,
14574 cx: &mut Context<Self>,
14575 ) {
14576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14577 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14578 }
14579
14580 pub fn go_to_prev_diagnostic(
14581 &mut self,
14582 _: &GoToPreviousDiagnostic,
14583 window: &mut Window,
14584 cx: &mut Context<Self>,
14585 ) {
14586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14587 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14588 }
14589
14590 pub fn go_to_diagnostic_impl(
14591 &mut self,
14592 direction: Direction,
14593 window: &mut Window,
14594 cx: &mut Context<Self>,
14595 ) {
14596 let buffer = self.buffer.read(cx).snapshot(cx);
14597 let selection = self.selections.newest::<usize>(cx);
14598
14599 let mut active_group_id = None;
14600 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14601 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14602 active_group_id = Some(active_group.group_id);
14603 }
14604 }
14605
14606 fn filtered(
14607 snapshot: EditorSnapshot,
14608 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14609 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14610 diagnostics
14611 .filter(|entry| entry.range.start != entry.range.end)
14612 .filter(|entry| !entry.diagnostic.is_unnecessary)
14613 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14614 }
14615
14616 let snapshot = self.snapshot(window, cx);
14617 let before = filtered(
14618 snapshot.clone(),
14619 buffer
14620 .diagnostics_in_range(0..selection.start)
14621 .filter(|entry| entry.range.start <= selection.start),
14622 );
14623 let after = filtered(
14624 snapshot,
14625 buffer
14626 .diagnostics_in_range(selection.start..buffer.len())
14627 .filter(|entry| entry.range.start >= selection.start),
14628 );
14629
14630 let mut found: Option<DiagnosticEntry<usize>> = None;
14631 if direction == Direction::Prev {
14632 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14633 {
14634 for diagnostic in prev_diagnostics.into_iter().rev() {
14635 if diagnostic.range.start != selection.start
14636 || active_group_id
14637 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14638 {
14639 found = Some(diagnostic);
14640 break 'outer;
14641 }
14642 }
14643 }
14644 } else {
14645 for diagnostic in after.chain(before) {
14646 if diagnostic.range.start != selection.start
14647 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14648 {
14649 found = Some(diagnostic);
14650 break;
14651 }
14652 }
14653 }
14654 let Some(next_diagnostic) = found else {
14655 return;
14656 };
14657
14658 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14659 return;
14660 };
14661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14662 s.select_ranges(vec![
14663 next_diagnostic.range.start..next_diagnostic.range.start,
14664 ])
14665 });
14666 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14667 self.refresh_inline_completion(false, true, window, cx);
14668 }
14669
14670 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14671 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14672 let snapshot = self.snapshot(window, cx);
14673 let selection = self.selections.newest::<Point>(cx);
14674 self.go_to_hunk_before_or_after_position(
14675 &snapshot,
14676 selection.head(),
14677 Direction::Next,
14678 window,
14679 cx,
14680 );
14681 }
14682
14683 pub fn go_to_hunk_before_or_after_position(
14684 &mut self,
14685 snapshot: &EditorSnapshot,
14686 position: Point,
14687 direction: Direction,
14688 window: &mut Window,
14689 cx: &mut Context<Editor>,
14690 ) {
14691 let row = if direction == Direction::Next {
14692 self.hunk_after_position(snapshot, position)
14693 .map(|hunk| hunk.row_range.start)
14694 } else {
14695 self.hunk_before_position(snapshot, position)
14696 };
14697
14698 if let Some(row) = row {
14699 let destination = Point::new(row.0, 0);
14700 let autoscroll = Autoscroll::center();
14701
14702 self.unfold_ranges(&[destination..destination], false, false, cx);
14703 self.change_selections(Some(autoscroll), window, cx, |s| {
14704 s.select_ranges([destination..destination]);
14705 });
14706 }
14707 }
14708
14709 fn hunk_after_position(
14710 &mut self,
14711 snapshot: &EditorSnapshot,
14712 position: Point,
14713 ) -> Option<MultiBufferDiffHunk> {
14714 snapshot
14715 .buffer_snapshot
14716 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14717 .find(|hunk| hunk.row_range.start.0 > position.row)
14718 .or_else(|| {
14719 snapshot
14720 .buffer_snapshot
14721 .diff_hunks_in_range(Point::zero()..position)
14722 .find(|hunk| hunk.row_range.end.0 < position.row)
14723 })
14724 }
14725
14726 fn go_to_prev_hunk(
14727 &mut self,
14728 _: &GoToPreviousHunk,
14729 window: &mut Window,
14730 cx: &mut Context<Self>,
14731 ) {
14732 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14733 let snapshot = self.snapshot(window, cx);
14734 let selection = self.selections.newest::<Point>(cx);
14735 self.go_to_hunk_before_or_after_position(
14736 &snapshot,
14737 selection.head(),
14738 Direction::Prev,
14739 window,
14740 cx,
14741 );
14742 }
14743
14744 fn hunk_before_position(
14745 &mut self,
14746 snapshot: &EditorSnapshot,
14747 position: Point,
14748 ) -> Option<MultiBufferRow> {
14749 snapshot
14750 .buffer_snapshot
14751 .diff_hunk_before(position)
14752 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14753 }
14754
14755 fn go_to_next_change(
14756 &mut self,
14757 _: &GoToNextChange,
14758 window: &mut Window,
14759 cx: &mut Context<Self>,
14760 ) {
14761 if let Some(selections) = self
14762 .change_list
14763 .next_change(1, Direction::Next)
14764 .map(|s| s.to_vec())
14765 {
14766 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14767 let map = s.display_map();
14768 s.select_display_ranges(selections.iter().map(|a| {
14769 let point = a.to_display_point(&map);
14770 point..point
14771 }))
14772 })
14773 }
14774 }
14775
14776 fn go_to_previous_change(
14777 &mut self,
14778 _: &GoToPreviousChange,
14779 window: &mut Window,
14780 cx: &mut Context<Self>,
14781 ) {
14782 if let Some(selections) = self
14783 .change_list
14784 .next_change(1, Direction::Prev)
14785 .map(|s| s.to_vec())
14786 {
14787 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14788 let map = s.display_map();
14789 s.select_display_ranges(selections.iter().map(|a| {
14790 let point = a.to_display_point(&map);
14791 point..point
14792 }))
14793 })
14794 }
14795 }
14796
14797 fn go_to_line<T: 'static>(
14798 &mut self,
14799 position: Anchor,
14800 highlight_color: Option<Hsla>,
14801 window: &mut Window,
14802 cx: &mut Context<Self>,
14803 ) {
14804 let snapshot = self.snapshot(window, cx).display_snapshot;
14805 let position = position.to_point(&snapshot.buffer_snapshot);
14806 let start = snapshot
14807 .buffer_snapshot
14808 .clip_point(Point::new(position.row, 0), Bias::Left);
14809 let end = start + Point::new(1, 0);
14810 let start = snapshot.buffer_snapshot.anchor_before(start);
14811 let end = snapshot.buffer_snapshot.anchor_before(end);
14812
14813 self.highlight_rows::<T>(
14814 start..end,
14815 highlight_color
14816 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14817 Default::default(),
14818 cx,
14819 );
14820
14821 if self.buffer.read(cx).is_singleton() {
14822 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14823 }
14824 }
14825
14826 pub fn go_to_definition(
14827 &mut self,
14828 _: &GoToDefinition,
14829 window: &mut Window,
14830 cx: &mut Context<Self>,
14831 ) -> Task<Result<Navigated>> {
14832 let definition =
14833 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14834 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14835 cx.spawn_in(window, async move |editor, cx| {
14836 if definition.await? == Navigated::Yes {
14837 return Ok(Navigated::Yes);
14838 }
14839 match fallback_strategy {
14840 GoToDefinitionFallback::None => Ok(Navigated::No),
14841 GoToDefinitionFallback::FindAllReferences => {
14842 match editor.update_in(cx, |editor, window, cx| {
14843 editor.find_all_references(&FindAllReferences, window, cx)
14844 })? {
14845 Some(references) => references.await,
14846 None => Ok(Navigated::No),
14847 }
14848 }
14849 }
14850 })
14851 }
14852
14853 pub fn go_to_declaration(
14854 &mut self,
14855 _: &GoToDeclaration,
14856 window: &mut Window,
14857 cx: &mut Context<Self>,
14858 ) -> Task<Result<Navigated>> {
14859 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14860 }
14861
14862 pub fn go_to_declaration_split(
14863 &mut self,
14864 _: &GoToDeclaration,
14865 window: &mut Window,
14866 cx: &mut Context<Self>,
14867 ) -> Task<Result<Navigated>> {
14868 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14869 }
14870
14871 pub fn go_to_implementation(
14872 &mut self,
14873 _: &GoToImplementation,
14874 window: &mut Window,
14875 cx: &mut Context<Self>,
14876 ) -> Task<Result<Navigated>> {
14877 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14878 }
14879
14880 pub fn go_to_implementation_split(
14881 &mut self,
14882 _: &GoToImplementationSplit,
14883 window: &mut Window,
14884 cx: &mut Context<Self>,
14885 ) -> Task<Result<Navigated>> {
14886 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14887 }
14888
14889 pub fn go_to_type_definition(
14890 &mut self,
14891 _: &GoToTypeDefinition,
14892 window: &mut Window,
14893 cx: &mut Context<Self>,
14894 ) -> Task<Result<Navigated>> {
14895 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14896 }
14897
14898 pub fn go_to_definition_split(
14899 &mut self,
14900 _: &GoToDefinitionSplit,
14901 window: &mut Window,
14902 cx: &mut Context<Self>,
14903 ) -> Task<Result<Navigated>> {
14904 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14905 }
14906
14907 pub fn go_to_type_definition_split(
14908 &mut self,
14909 _: &GoToTypeDefinitionSplit,
14910 window: &mut Window,
14911 cx: &mut Context<Self>,
14912 ) -> Task<Result<Navigated>> {
14913 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14914 }
14915
14916 fn go_to_definition_of_kind(
14917 &mut self,
14918 kind: GotoDefinitionKind,
14919 split: bool,
14920 window: &mut Window,
14921 cx: &mut Context<Self>,
14922 ) -> Task<Result<Navigated>> {
14923 let Some(provider) = self.semantics_provider.clone() else {
14924 return Task::ready(Ok(Navigated::No));
14925 };
14926 let head = self.selections.newest::<usize>(cx).head();
14927 let buffer = self.buffer.read(cx);
14928 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14929 text_anchor
14930 } else {
14931 return Task::ready(Ok(Navigated::No));
14932 };
14933
14934 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14935 return Task::ready(Ok(Navigated::No));
14936 };
14937
14938 cx.spawn_in(window, async move |editor, cx| {
14939 let definitions = definitions.await?;
14940 let navigated = editor
14941 .update_in(cx, |editor, window, cx| {
14942 editor.navigate_to_hover_links(
14943 Some(kind),
14944 definitions
14945 .into_iter()
14946 .filter(|location| {
14947 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14948 })
14949 .map(HoverLink::Text)
14950 .collect::<Vec<_>>(),
14951 split,
14952 window,
14953 cx,
14954 )
14955 })?
14956 .await?;
14957 anyhow::Ok(navigated)
14958 })
14959 }
14960
14961 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14962 let selection = self.selections.newest_anchor();
14963 let head = selection.head();
14964 let tail = selection.tail();
14965
14966 let Some((buffer, start_position)) =
14967 self.buffer.read(cx).text_anchor_for_position(head, cx)
14968 else {
14969 return;
14970 };
14971
14972 let end_position = if head != tail {
14973 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14974 return;
14975 };
14976 Some(pos)
14977 } else {
14978 None
14979 };
14980
14981 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14982 let url = if let Some(end_pos) = end_position {
14983 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14984 } else {
14985 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14986 };
14987
14988 if let Some(url) = url {
14989 editor.update(cx, |_, cx| {
14990 cx.open_url(&url);
14991 })
14992 } else {
14993 Ok(())
14994 }
14995 });
14996
14997 url_finder.detach();
14998 }
14999
15000 pub fn open_selected_filename(
15001 &mut self,
15002 _: &OpenSelectedFilename,
15003 window: &mut Window,
15004 cx: &mut Context<Self>,
15005 ) {
15006 let Some(workspace) = self.workspace() else {
15007 return;
15008 };
15009
15010 let position = self.selections.newest_anchor().head();
15011
15012 let Some((buffer, buffer_position)) =
15013 self.buffer.read(cx).text_anchor_for_position(position, cx)
15014 else {
15015 return;
15016 };
15017
15018 let project = self.project.clone();
15019
15020 cx.spawn_in(window, async move |_, cx| {
15021 let result = find_file(&buffer, project, buffer_position, cx).await;
15022
15023 if let Some((_, path)) = result {
15024 workspace
15025 .update_in(cx, |workspace, window, cx| {
15026 workspace.open_resolved_path(path, window, cx)
15027 })?
15028 .await?;
15029 }
15030 anyhow::Ok(())
15031 })
15032 .detach();
15033 }
15034
15035 pub(crate) fn navigate_to_hover_links(
15036 &mut self,
15037 kind: Option<GotoDefinitionKind>,
15038 mut definitions: Vec<HoverLink>,
15039 split: bool,
15040 window: &mut Window,
15041 cx: &mut Context<Editor>,
15042 ) -> Task<Result<Navigated>> {
15043 // If there is one definition, just open it directly
15044 if definitions.len() == 1 {
15045 let definition = definitions.pop().unwrap();
15046
15047 enum TargetTaskResult {
15048 Location(Option<Location>),
15049 AlreadyNavigated,
15050 }
15051
15052 let target_task = match definition {
15053 HoverLink::Text(link) => {
15054 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15055 }
15056 HoverLink::InlayHint(lsp_location, server_id) => {
15057 let computation =
15058 self.compute_target_location(lsp_location, server_id, window, cx);
15059 cx.background_spawn(async move {
15060 let location = computation.await?;
15061 Ok(TargetTaskResult::Location(location))
15062 })
15063 }
15064 HoverLink::Url(url) => {
15065 cx.open_url(&url);
15066 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15067 }
15068 HoverLink::File(path) => {
15069 if let Some(workspace) = self.workspace() {
15070 cx.spawn_in(window, async move |_, cx| {
15071 workspace
15072 .update_in(cx, |workspace, window, cx| {
15073 workspace.open_resolved_path(path, window, cx)
15074 })?
15075 .await
15076 .map(|_| TargetTaskResult::AlreadyNavigated)
15077 })
15078 } else {
15079 Task::ready(Ok(TargetTaskResult::Location(None)))
15080 }
15081 }
15082 };
15083 cx.spawn_in(window, async move |editor, cx| {
15084 let target = match target_task.await.context("target resolution task")? {
15085 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15086 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15087 TargetTaskResult::Location(Some(target)) => target,
15088 };
15089
15090 editor.update_in(cx, |editor, window, cx| {
15091 let Some(workspace) = editor.workspace() else {
15092 return Navigated::No;
15093 };
15094 let pane = workspace.read(cx).active_pane().clone();
15095
15096 let range = target.range.to_point(target.buffer.read(cx));
15097 let range = editor.range_for_match(&range);
15098 let range = collapse_multiline_range(range);
15099
15100 if !split
15101 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15102 {
15103 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15104 } else {
15105 window.defer(cx, move |window, cx| {
15106 let target_editor: Entity<Self> =
15107 workspace.update(cx, |workspace, cx| {
15108 let pane = if split {
15109 workspace.adjacent_pane(window, cx)
15110 } else {
15111 workspace.active_pane().clone()
15112 };
15113
15114 workspace.open_project_item(
15115 pane,
15116 target.buffer.clone(),
15117 true,
15118 true,
15119 window,
15120 cx,
15121 )
15122 });
15123 target_editor.update(cx, |target_editor, cx| {
15124 // When selecting a definition in a different buffer, disable the nav history
15125 // to avoid creating a history entry at the previous cursor location.
15126 pane.update(cx, |pane, _| pane.disable_history());
15127 target_editor.go_to_singleton_buffer_range(range, window, cx);
15128 pane.update(cx, |pane, _| pane.enable_history());
15129 });
15130 });
15131 }
15132 Navigated::Yes
15133 })
15134 })
15135 } else if !definitions.is_empty() {
15136 cx.spawn_in(window, async move |editor, cx| {
15137 let (title, location_tasks, workspace) = editor
15138 .update_in(cx, |editor, window, cx| {
15139 let tab_kind = match kind {
15140 Some(GotoDefinitionKind::Implementation) => "Implementations",
15141 _ => "Definitions",
15142 };
15143 let title = definitions
15144 .iter()
15145 .find_map(|definition| match definition {
15146 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15147 let buffer = origin.buffer.read(cx);
15148 format!(
15149 "{} for {}",
15150 tab_kind,
15151 buffer
15152 .text_for_range(origin.range.clone())
15153 .collect::<String>()
15154 )
15155 }),
15156 HoverLink::InlayHint(_, _) => None,
15157 HoverLink::Url(_) => None,
15158 HoverLink::File(_) => None,
15159 })
15160 .unwrap_or(tab_kind.to_string());
15161 let location_tasks = definitions
15162 .into_iter()
15163 .map(|definition| match definition {
15164 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15165 HoverLink::InlayHint(lsp_location, server_id) => editor
15166 .compute_target_location(lsp_location, server_id, window, cx),
15167 HoverLink::Url(_) => Task::ready(Ok(None)),
15168 HoverLink::File(_) => Task::ready(Ok(None)),
15169 })
15170 .collect::<Vec<_>>();
15171 (title, location_tasks, editor.workspace().clone())
15172 })
15173 .context("location tasks preparation")?;
15174
15175 let locations: Vec<Location> = future::join_all(location_tasks)
15176 .await
15177 .into_iter()
15178 .filter_map(|location| location.transpose())
15179 .collect::<Result<_>>()
15180 .context("location tasks")?;
15181
15182 if locations.is_empty() {
15183 return Ok(Navigated::No);
15184 }
15185
15186 let Some(workspace) = workspace else {
15187 return Ok(Navigated::No);
15188 };
15189
15190 let opened = workspace
15191 .update_in(cx, |workspace, window, cx| {
15192 Self::open_locations_in_multibuffer(
15193 workspace,
15194 locations,
15195 title,
15196 split,
15197 MultibufferSelectionMode::First,
15198 window,
15199 cx,
15200 )
15201 })
15202 .ok();
15203
15204 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15205 })
15206 } else {
15207 Task::ready(Ok(Navigated::No))
15208 }
15209 }
15210
15211 fn compute_target_location(
15212 &self,
15213 lsp_location: lsp::Location,
15214 server_id: LanguageServerId,
15215 window: &mut Window,
15216 cx: &mut Context<Self>,
15217 ) -> Task<anyhow::Result<Option<Location>>> {
15218 let Some(project) = self.project.clone() else {
15219 return Task::ready(Ok(None));
15220 };
15221
15222 cx.spawn_in(window, async move |editor, cx| {
15223 let location_task = editor.update(cx, |_, cx| {
15224 project.update(cx, |project, cx| {
15225 let language_server_name = project
15226 .language_server_statuses(cx)
15227 .find(|(id, _)| server_id == *id)
15228 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15229 language_server_name.map(|language_server_name| {
15230 project.open_local_buffer_via_lsp(
15231 lsp_location.uri.clone(),
15232 server_id,
15233 language_server_name,
15234 cx,
15235 )
15236 })
15237 })
15238 })?;
15239 let location = match location_task {
15240 Some(task) => Some({
15241 let target_buffer_handle = task.await.context("open local buffer")?;
15242 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15243 let target_start = target_buffer
15244 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15245 let target_end = target_buffer
15246 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15247 target_buffer.anchor_after(target_start)
15248 ..target_buffer.anchor_before(target_end)
15249 })?;
15250 Location {
15251 buffer: target_buffer_handle,
15252 range,
15253 }
15254 }),
15255 None => None,
15256 };
15257 Ok(location)
15258 })
15259 }
15260
15261 pub fn find_all_references(
15262 &mut self,
15263 _: &FindAllReferences,
15264 window: &mut Window,
15265 cx: &mut Context<Self>,
15266 ) -> Option<Task<Result<Navigated>>> {
15267 let selection = self.selections.newest::<usize>(cx);
15268 let multi_buffer = self.buffer.read(cx);
15269 let head = selection.head();
15270
15271 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15272 let head_anchor = multi_buffer_snapshot.anchor_at(
15273 head,
15274 if head < selection.tail() {
15275 Bias::Right
15276 } else {
15277 Bias::Left
15278 },
15279 );
15280
15281 match self
15282 .find_all_references_task_sources
15283 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15284 {
15285 Ok(_) => {
15286 log::info!(
15287 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15288 );
15289 return None;
15290 }
15291 Err(i) => {
15292 self.find_all_references_task_sources.insert(i, head_anchor);
15293 }
15294 }
15295
15296 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15297 let workspace = self.workspace()?;
15298 let project = workspace.read(cx).project().clone();
15299 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15300 Some(cx.spawn_in(window, async move |editor, cx| {
15301 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15302 if let Ok(i) = editor
15303 .find_all_references_task_sources
15304 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15305 {
15306 editor.find_all_references_task_sources.remove(i);
15307 }
15308 });
15309
15310 let locations = references.await?;
15311 if locations.is_empty() {
15312 return anyhow::Ok(Navigated::No);
15313 }
15314
15315 workspace.update_in(cx, |workspace, window, cx| {
15316 let title = locations
15317 .first()
15318 .as_ref()
15319 .map(|location| {
15320 let buffer = location.buffer.read(cx);
15321 format!(
15322 "References to `{}`",
15323 buffer
15324 .text_for_range(location.range.clone())
15325 .collect::<String>()
15326 )
15327 })
15328 .unwrap();
15329 Self::open_locations_in_multibuffer(
15330 workspace,
15331 locations,
15332 title,
15333 false,
15334 MultibufferSelectionMode::First,
15335 window,
15336 cx,
15337 );
15338 Navigated::Yes
15339 })
15340 }))
15341 }
15342
15343 /// Opens a multibuffer with the given project locations in it
15344 pub fn open_locations_in_multibuffer(
15345 workspace: &mut Workspace,
15346 mut locations: Vec<Location>,
15347 title: String,
15348 split: bool,
15349 multibuffer_selection_mode: MultibufferSelectionMode,
15350 window: &mut Window,
15351 cx: &mut Context<Workspace>,
15352 ) {
15353 if locations.is_empty() {
15354 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15355 return;
15356 }
15357
15358 // If there are multiple definitions, open them in a multibuffer
15359 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15360 let mut locations = locations.into_iter().peekable();
15361 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15362 let capability = workspace.project().read(cx).capability();
15363
15364 let excerpt_buffer = cx.new(|cx| {
15365 let mut multibuffer = MultiBuffer::new(capability);
15366 while let Some(location) = locations.next() {
15367 let buffer = location.buffer.read(cx);
15368 let mut ranges_for_buffer = Vec::new();
15369 let range = location.range.to_point(buffer);
15370 ranges_for_buffer.push(range.clone());
15371
15372 while let Some(next_location) = locations.peek() {
15373 if next_location.buffer == location.buffer {
15374 ranges_for_buffer.push(next_location.range.to_point(buffer));
15375 locations.next();
15376 } else {
15377 break;
15378 }
15379 }
15380
15381 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15382 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15383 PathKey::for_buffer(&location.buffer, cx),
15384 location.buffer.clone(),
15385 ranges_for_buffer,
15386 DEFAULT_MULTIBUFFER_CONTEXT,
15387 cx,
15388 );
15389 ranges.extend(new_ranges)
15390 }
15391
15392 multibuffer.with_title(title)
15393 });
15394
15395 let editor = cx.new(|cx| {
15396 Editor::for_multibuffer(
15397 excerpt_buffer,
15398 Some(workspace.project().clone()),
15399 window,
15400 cx,
15401 )
15402 });
15403 editor.update(cx, |editor, cx| {
15404 match multibuffer_selection_mode {
15405 MultibufferSelectionMode::First => {
15406 if let Some(first_range) = ranges.first() {
15407 editor.change_selections(None, window, cx, |selections| {
15408 selections.clear_disjoint();
15409 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15410 });
15411 }
15412 editor.highlight_background::<Self>(
15413 &ranges,
15414 |theme| theme.colors().editor_highlighted_line_background,
15415 cx,
15416 );
15417 }
15418 MultibufferSelectionMode::All => {
15419 editor.change_selections(None, window, cx, |selections| {
15420 selections.clear_disjoint();
15421 selections.select_anchor_ranges(ranges);
15422 });
15423 }
15424 }
15425 editor.register_buffers_with_language_servers(cx);
15426 });
15427
15428 let item = Box::new(editor);
15429 let item_id = item.item_id();
15430
15431 if split {
15432 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15433 } else {
15434 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15435 let (preview_item_id, preview_item_idx) =
15436 workspace.active_pane().read_with(cx, |pane, _| {
15437 (pane.preview_item_id(), pane.preview_item_idx())
15438 });
15439
15440 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15441
15442 if let Some(preview_item_id) = preview_item_id {
15443 workspace.active_pane().update(cx, |pane, cx| {
15444 pane.remove_item(preview_item_id, false, false, window, cx);
15445 });
15446 }
15447 } else {
15448 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15449 }
15450 }
15451 workspace.active_pane().update(cx, |pane, cx| {
15452 pane.set_preview_item_id(Some(item_id), cx);
15453 });
15454 }
15455
15456 pub fn rename(
15457 &mut self,
15458 _: &Rename,
15459 window: &mut Window,
15460 cx: &mut Context<Self>,
15461 ) -> Option<Task<Result<()>>> {
15462 use language::ToOffset as _;
15463
15464 let provider = self.semantics_provider.clone()?;
15465 let selection = self.selections.newest_anchor().clone();
15466 let (cursor_buffer, cursor_buffer_position) = self
15467 .buffer
15468 .read(cx)
15469 .text_anchor_for_position(selection.head(), cx)?;
15470 let (tail_buffer, cursor_buffer_position_end) = self
15471 .buffer
15472 .read(cx)
15473 .text_anchor_for_position(selection.tail(), cx)?;
15474 if tail_buffer != cursor_buffer {
15475 return None;
15476 }
15477
15478 let snapshot = cursor_buffer.read(cx).snapshot();
15479 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15480 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15481 let prepare_rename = provider
15482 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15483 .unwrap_or_else(|| Task::ready(Ok(None)));
15484 drop(snapshot);
15485
15486 Some(cx.spawn_in(window, async move |this, cx| {
15487 let rename_range = if let Some(range) = prepare_rename.await? {
15488 Some(range)
15489 } else {
15490 this.update(cx, |this, cx| {
15491 let buffer = this.buffer.read(cx).snapshot(cx);
15492 let mut buffer_highlights = this
15493 .document_highlights_for_position(selection.head(), &buffer)
15494 .filter(|highlight| {
15495 highlight.start.excerpt_id == selection.head().excerpt_id
15496 && highlight.end.excerpt_id == selection.head().excerpt_id
15497 });
15498 buffer_highlights
15499 .next()
15500 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15501 })?
15502 };
15503 if let Some(rename_range) = rename_range {
15504 this.update_in(cx, |this, window, cx| {
15505 let snapshot = cursor_buffer.read(cx).snapshot();
15506 let rename_buffer_range = rename_range.to_offset(&snapshot);
15507 let cursor_offset_in_rename_range =
15508 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15509 let cursor_offset_in_rename_range_end =
15510 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15511
15512 this.take_rename(false, window, cx);
15513 let buffer = this.buffer.read(cx).read(cx);
15514 let cursor_offset = selection.head().to_offset(&buffer);
15515 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15516 let rename_end = rename_start + rename_buffer_range.len();
15517 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15518 let mut old_highlight_id = None;
15519 let old_name: Arc<str> = buffer
15520 .chunks(rename_start..rename_end, true)
15521 .map(|chunk| {
15522 if old_highlight_id.is_none() {
15523 old_highlight_id = chunk.syntax_highlight_id;
15524 }
15525 chunk.text
15526 })
15527 .collect::<String>()
15528 .into();
15529
15530 drop(buffer);
15531
15532 // Position the selection in the rename editor so that it matches the current selection.
15533 this.show_local_selections = false;
15534 let rename_editor = cx.new(|cx| {
15535 let mut editor = Editor::single_line(window, cx);
15536 editor.buffer.update(cx, |buffer, cx| {
15537 buffer.edit([(0..0, old_name.clone())], None, cx)
15538 });
15539 let rename_selection_range = match cursor_offset_in_rename_range
15540 .cmp(&cursor_offset_in_rename_range_end)
15541 {
15542 Ordering::Equal => {
15543 editor.select_all(&SelectAll, window, cx);
15544 return editor;
15545 }
15546 Ordering::Less => {
15547 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15548 }
15549 Ordering::Greater => {
15550 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15551 }
15552 };
15553 if rename_selection_range.end > old_name.len() {
15554 editor.select_all(&SelectAll, window, cx);
15555 } else {
15556 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15557 s.select_ranges([rename_selection_range]);
15558 });
15559 }
15560 editor
15561 });
15562 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15563 if e == &EditorEvent::Focused {
15564 cx.emit(EditorEvent::FocusedIn)
15565 }
15566 })
15567 .detach();
15568
15569 let write_highlights =
15570 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15571 let read_highlights =
15572 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15573 let ranges = write_highlights
15574 .iter()
15575 .flat_map(|(_, ranges)| ranges.iter())
15576 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15577 .cloned()
15578 .collect();
15579
15580 this.highlight_text::<Rename>(
15581 ranges,
15582 HighlightStyle {
15583 fade_out: Some(0.6),
15584 ..Default::default()
15585 },
15586 cx,
15587 );
15588 let rename_focus_handle = rename_editor.focus_handle(cx);
15589 window.focus(&rename_focus_handle);
15590 let block_id = this.insert_blocks(
15591 [BlockProperties {
15592 style: BlockStyle::Flex,
15593 placement: BlockPlacement::Below(range.start),
15594 height: Some(1),
15595 render: Arc::new({
15596 let rename_editor = rename_editor.clone();
15597 move |cx: &mut BlockContext| {
15598 let mut text_style = cx.editor_style.text.clone();
15599 if let Some(highlight_style) = old_highlight_id
15600 .and_then(|h| h.style(&cx.editor_style.syntax))
15601 {
15602 text_style = text_style.highlight(highlight_style);
15603 }
15604 div()
15605 .block_mouse_except_scroll()
15606 .pl(cx.anchor_x)
15607 .child(EditorElement::new(
15608 &rename_editor,
15609 EditorStyle {
15610 background: cx.theme().system().transparent,
15611 local_player: cx.editor_style.local_player,
15612 text: text_style,
15613 scrollbar_width: cx.editor_style.scrollbar_width,
15614 syntax: cx.editor_style.syntax.clone(),
15615 status: cx.editor_style.status.clone(),
15616 inlay_hints_style: HighlightStyle {
15617 font_weight: Some(FontWeight::BOLD),
15618 ..make_inlay_hints_style(cx.app)
15619 },
15620 inline_completion_styles: make_suggestion_styles(
15621 cx.app,
15622 ),
15623 ..EditorStyle::default()
15624 },
15625 ))
15626 .into_any_element()
15627 }
15628 }),
15629 priority: 0,
15630 render_in_minimap: true,
15631 }],
15632 Some(Autoscroll::fit()),
15633 cx,
15634 )[0];
15635 this.pending_rename = Some(RenameState {
15636 range,
15637 old_name,
15638 editor: rename_editor,
15639 block_id,
15640 });
15641 })?;
15642 }
15643
15644 Ok(())
15645 }))
15646 }
15647
15648 pub fn confirm_rename(
15649 &mut self,
15650 _: &ConfirmRename,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) -> Option<Task<Result<()>>> {
15654 let rename = self.take_rename(false, window, cx)?;
15655 let workspace = self.workspace()?.downgrade();
15656 let (buffer, start) = self
15657 .buffer
15658 .read(cx)
15659 .text_anchor_for_position(rename.range.start, cx)?;
15660 let (end_buffer, _) = self
15661 .buffer
15662 .read(cx)
15663 .text_anchor_for_position(rename.range.end, cx)?;
15664 if buffer != end_buffer {
15665 return None;
15666 }
15667
15668 let old_name = rename.old_name;
15669 let new_name = rename.editor.read(cx).text(cx);
15670
15671 let rename = self.semantics_provider.as_ref()?.perform_rename(
15672 &buffer,
15673 start,
15674 new_name.clone(),
15675 cx,
15676 )?;
15677
15678 Some(cx.spawn_in(window, async move |editor, cx| {
15679 let project_transaction = rename.await?;
15680 Self::open_project_transaction(
15681 &editor,
15682 workspace,
15683 project_transaction,
15684 format!("Rename: {} → {}", old_name, new_name),
15685 cx,
15686 )
15687 .await?;
15688
15689 editor.update(cx, |editor, cx| {
15690 editor.refresh_document_highlights(cx);
15691 })?;
15692 Ok(())
15693 }))
15694 }
15695
15696 fn take_rename(
15697 &mut self,
15698 moving_cursor: bool,
15699 window: &mut Window,
15700 cx: &mut Context<Self>,
15701 ) -> Option<RenameState> {
15702 let rename = self.pending_rename.take()?;
15703 if rename.editor.focus_handle(cx).is_focused(window) {
15704 window.focus(&self.focus_handle);
15705 }
15706
15707 self.remove_blocks(
15708 [rename.block_id].into_iter().collect(),
15709 Some(Autoscroll::fit()),
15710 cx,
15711 );
15712 self.clear_highlights::<Rename>(cx);
15713 self.show_local_selections = true;
15714
15715 if moving_cursor {
15716 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15717 editor.selections.newest::<usize>(cx).head()
15718 });
15719
15720 // Update the selection to match the position of the selection inside
15721 // the rename editor.
15722 let snapshot = self.buffer.read(cx).read(cx);
15723 let rename_range = rename.range.to_offset(&snapshot);
15724 let cursor_in_editor = snapshot
15725 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15726 .min(rename_range.end);
15727 drop(snapshot);
15728
15729 self.change_selections(None, window, cx, |s| {
15730 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15731 });
15732 } else {
15733 self.refresh_document_highlights(cx);
15734 }
15735
15736 Some(rename)
15737 }
15738
15739 pub fn pending_rename(&self) -> Option<&RenameState> {
15740 self.pending_rename.as_ref()
15741 }
15742
15743 fn format(
15744 &mut self,
15745 _: &Format,
15746 window: &mut Window,
15747 cx: &mut Context<Self>,
15748 ) -> Option<Task<Result<()>>> {
15749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15750
15751 let project = match &self.project {
15752 Some(project) => project.clone(),
15753 None => return None,
15754 };
15755
15756 Some(self.perform_format(
15757 project,
15758 FormatTrigger::Manual,
15759 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15760 window,
15761 cx,
15762 ))
15763 }
15764
15765 fn format_selections(
15766 &mut self,
15767 _: &FormatSelections,
15768 window: &mut Window,
15769 cx: &mut Context<Self>,
15770 ) -> Option<Task<Result<()>>> {
15771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15772
15773 let project = match &self.project {
15774 Some(project) => project.clone(),
15775 None => return None,
15776 };
15777
15778 let ranges = self
15779 .selections
15780 .all_adjusted(cx)
15781 .into_iter()
15782 .map(|selection| selection.range())
15783 .collect_vec();
15784
15785 Some(self.perform_format(
15786 project,
15787 FormatTrigger::Manual,
15788 FormatTarget::Ranges(ranges),
15789 window,
15790 cx,
15791 ))
15792 }
15793
15794 fn perform_format(
15795 &mut self,
15796 project: Entity<Project>,
15797 trigger: FormatTrigger,
15798 target: FormatTarget,
15799 window: &mut Window,
15800 cx: &mut Context<Self>,
15801 ) -> Task<Result<()>> {
15802 let buffer = self.buffer.clone();
15803 let (buffers, target) = match target {
15804 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15805 FormatTarget::Ranges(selection_ranges) => {
15806 let multi_buffer = buffer.read(cx);
15807 let snapshot = multi_buffer.read(cx);
15808 let mut buffers = HashSet::default();
15809 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15810 BTreeMap::new();
15811 for selection_range in selection_ranges {
15812 for (buffer, buffer_range, _) in
15813 snapshot.range_to_buffer_ranges(selection_range)
15814 {
15815 let buffer_id = buffer.remote_id();
15816 let start = buffer.anchor_before(buffer_range.start);
15817 let end = buffer.anchor_after(buffer_range.end);
15818 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15819 buffer_id_to_ranges
15820 .entry(buffer_id)
15821 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15822 .or_insert_with(|| vec![start..end]);
15823 }
15824 }
15825 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15826 }
15827 };
15828
15829 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15830 let selections_prev = transaction_id_prev
15831 .and_then(|transaction_id_prev| {
15832 // default to selections as they were after the last edit, if we have them,
15833 // instead of how they are now.
15834 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15835 // will take you back to where you made the last edit, instead of staying where you scrolled
15836 self.selection_history
15837 .transaction(transaction_id_prev)
15838 .map(|t| t.0.clone())
15839 })
15840 .unwrap_or_else(|| {
15841 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15842 self.selections.disjoint_anchors()
15843 });
15844
15845 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15846 let format = project.update(cx, |project, cx| {
15847 project.format(buffers, target, true, trigger, cx)
15848 });
15849
15850 cx.spawn_in(window, async move |editor, cx| {
15851 let transaction = futures::select_biased! {
15852 transaction = format.log_err().fuse() => transaction,
15853 () = timeout => {
15854 log::warn!("timed out waiting for formatting");
15855 None
15856 }
15857 };
15858
15859 buffer
15860 .update(cx, |buffer, cx| {
15861 if let Some(transaction) = transaction {
15862 if !buffer.is_singleton() {
15863 buffer.push_transaction(&transaction.0, cx);
15864 }
15865 }
15866 cx.notify();
15867 })
15868 .ok();
15869
15870 if let Some(transaction_id_now) =
15871 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15872 {
15873 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15874 if has_new_transaction {
15875 _ = editor.update(cx, |editor, _| {
15876 editor
15877 .selection_history
15878 .insert_transaction(transaction_id_now, selections_prev);
15879 });
15880 }
15881 }
15882
15883 Ok(())
15884 })
15885 }
15886
15887 fn organize_imports(
15888 &mut self,
15889 _: &OrganizeImports,
15890 window: &mut Window,
15891 cx: &mut Context<Self>,
15892 ) -> Option<Task<Result<()>>> {
15893 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15894 let project = match &self.project {
15895 Some(project) => project.clone(),
15896 None => return None,
15897 };
15898 Some(self.perform_code_action_kind(
15899 project,
15900 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15901 window,
15902 cx,
15903 ))
15904 }
15905
15906 fn perform_code_action_kind(
15907 &mut self,
15908 project: Entity<Project>,
15909 kind: CodeActionKind,
15910 window: &mut Window,
15911 cx: &mut Context<Self>,
15912 ) -> Task<Result<()>> {
15913 let buffer = self.buffer.clone();
15914 let buffers = buffer.read(cx).all_buffers();
15915 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15916 let apply_action = project.update(cx, |project, cx| {
15917 project.apply_code_action_kind(buffers, kind, true, cx)
15918 });
15919 cx.spawn_in(window, async move |_, cx| {
15920 let transaction = futures::select_biased! {
15921 () = timeout => {
15922 log::warn!("timed out waiting for executing code action");
15923 None
15924 }
15925 transaction = apply_action.log_err().fuse() => transaction,
15926 };
15927 buffer
15928 .update(cx, |buffer, cx| {
15929 // check if we need this
15930 if let Some(transaction) = transaction {
15931 if !buffer.is_singleton() {
15932 buffer.push_transaction(&transaction.0, cx);
15933 }
15934 }
15935 cx.notify();
15936 })
15937 .ok();
15938 Ok(())
15939 })
15940 }
15941
15942 fn restart_language_server(
15943 &mut self,
15944 _: &RestartLanguageServer,
15945 _: &mut Window,
15946 cx: &mut Context<Self>,
15947 ) {
15948 if let Some(project) = self.project.clone() {
15949 self.buffer.update(cx, |multi_buffer, cx| {
15950 project.update(cx, |project, cx| {
15951 project.restart_language_servers_for_buffers(
15952 multi_buffer.all_buffers().into_iter().collect(),
15953 cx,
15954 );
15955 });
15956 })
15957 }
15958 }
15959
15960 fn stop_language_server(
15961 &mut self,
15962 _: &StopLanguageServer,
15963 _: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) {
15966 if let Some(project) = self.project.clone() {
15967 self.buffer.update(cx, |multi_buffer, cx| {
15968 project.update(cx, |project, cx| {
15969 project.stop_language_servers_for_buffers(
15970 multi_buffer.all_buffers().into_iter().collect(),
15971 cx,
15972 );
15973 cx.emit(project::Event::RefreshInlayHints);
15974 });
15975 });
15976 }
15977 }
15978
15979 fn cancel_language_server_work(
15980 workspace: &mut Workspace,
15981 _: &actions::CancelLanguageServerWork,
15982 _: &mut Window,
15983 cx: &mut Context<Workspace>,
15984 ) {
15985 let project = workspace.project();
15986 let buffers = workspace
15987 .active_item(cx)
15988 .and_then(|item| item.act_as::<Editor>(cx))
15989 .map_or(HashSet::default(), |editor| {
15990 editor.read(cx).buffer.read(cx).all_buffers()
15991 });
15992 project.update(cx, |project, cx| {
15993 project.cancel_language_server_work_for_buffers(buffers, cx);
15994 });
15995 }
15996
15997 fn show_character_palette(
15998 &mut self,
15999 _: &ShowCharacterPalette,
16000 window: &mut Window,
16001 _: &mut Context<Self>,
16002 ) {
16003 window.show_character_palette();
16004 }
16005
16006 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16007 if self.mode.is_minimap() {
16008 return;
16009 }
16010
16011 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16012 let buffer = self.buffer.read(cx).snapshot(cx);
16013 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16014 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16015 let is_valid = buffer
16016 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16017 .any(|entry| {
16018 entry.diagnostic.is_primary
16019 && !entry.range.is_empty()
16020 && entry.range.start == primary_range_start
16021 && entry.diagnostic.message == active_diagnostics.active_message
16022 });
16023
16024 if !is_valid {
16025 self.dismiss_diagnostics(cx);
16026 }
16027 }
16028 }
16029
16030 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16031 match &self.active_diagnostics {
16032 ActiveDiagnostic::Group(group) => Some(group),
16033 _ => None,
16034 }
16035 }
16036
16037 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16038 self.dismiss_diagnostics(cx);
16039 self.active_diagnostics = ActiveDiagnostic::All;
16040 }
16041
16042 fn activate_diagnostics(
16043 &mut self,
16044 buffer_id: BufferId,
16045 diagnostic: DiagnosticEntry<usize>,
16046 window: &mut Window,
16047 cx: &mut Context<Self>,
16048 ) {
16049 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16050 return;
16051 }
16052 self.dismiss_diagnostics(cx);
16053 let snapshot = self.snapshot(window, cx);
16054 let buffer = self.buffer.read(cx).snapshot(cx);
16055 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16056 return;
16057 };
16058
16059 let diagnostic_group = buffer
16060 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16061 .collect::<Vec<_>>();
16062
16063 let blocks =
16064 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16065
16066 let blocks = self.display_map.update(cx, |display_map, cx| {
16067 display_map.insert_blocks(blocks, cx).into_iter().collect()
16068 });
16069 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16070 active_range: buffer.anchor_before(diagnostic.range.start)
16071 ..buffer.anchor_after(diagnostic.range.end),
16072 active_message: diagnostic.diagnostic.message.clone(),
16073 group_id: diagnostic.diagnostic.group_id,
16074 blocks,
16075 });
16076 cx.notify();
16077 }
16078
16079 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16080 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16081 return;
16082 };
16083
16084 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16085 if let ActiveDiagnostic::Group(group) = prev {
16086 self.display_map.update(cx, |display_map, cx| {
16087 display_map.remove_blocks(group.blocks, cx);
16088 });
16089 cx.notify();
16090 }
16091 }
16092
16093 /// Disable inline diagnostics rendering for this editor.
16094 pub fn disable_inline_diagnostics(&mut self) {
16095 self.inline_diagnostics_enabled = false;
16096 self.inline_diagnostics_update = Task::ready(());
16097 self.inline_diagnostics.clear();
16098 }
16099
16100 pub fn diagnostics_enabled(&self) -> bool {
16101 self.mode.is_full()
16102 }
16103
16104 pub fn inline_diagnostics_enabled(&self) -> bool {
16105 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16106 }
16107
16108 pub fn show_inline_diagnostics(&self) -> bool {
16109 self.show_inline_diagnostics
16110 }
16111
16112 pub fn toggle_inline_diagnostics(
16113 &mut self,
16114 _: &ToggleInlineDiagnostics,
16115 window: &mut Window,
16116 cx: &mut Context<Editor>,
16117 ) {
16118 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16119 self.refresh_inline_diagnostics(false, window, cx);
16120 }
16121
16122 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16123 self.diagnostics_max_severity = severity;
16124 self.display_map.update(cx, |display_map, _| {
16125 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16126 });
16127 }
16128
16129 pub fn toggle_diagnostics(
16130 &mut self,
16131 _: &ToggleDiagnostics,
16132 window: &mut Window,
16133 cx: &mut Context<Editor>,
16134 ) {
16135 if !self.diagnostics_enabled() {
16136 return;
16137 }
16138
16139 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16140 EditorSettings::get_global(cx)
16141 .diagnostics_max_severity
16142 .filter(|severity| severity != &DiagnosticSeverity::Off)
16143 .unwrap_or(DiagnosticSeverity::Hint)
16144 } else {
16145 DiagnosticSeverity::Off
16146 };
16147 self.set_max_diagnostics_severity(new_severity, cx);
16148 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16149 self.active_diagnostics = ActiveDiagnostic::None;
16150 self.inline_diagnostics_update = Task::ready(());
16151 self.inline_diagnostics.clear();
16152 } else {
16153 self.refresh_inline_diagnostics(false, window, cx);
16154 }
16155
16156 cx.notify();
16157 }
16158
16159 pub fn toggle_minimap(
16160 &mut self,
16161 _: &ToggleMinimap,
16162 window: &mut Window,
16163 cx: &mut Context<Editor>,
16164 ) {
16165 if self.supports_minimap(cx) {
16166 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16167 }
16168 }
16169
16170 fn refresh_inline_diagnostics(
16171 &mut self,
16172 debounce: bool,
16173 window: &mut Window,
16174 cx: &mut Context<Self>,
16175 ) {
16176 let max_severity = ProjectSettings::get_global(cx)
16177 .diagnostics
16178 .inline
16179 .max_severity
16180 .unwrap_or(self.diagnostics_max_severity);
16181
16182 if !self.inline_diagnostics_enabled()
16183 || !self.show_inline_diagnostics
16184 || max_severity == DiagnosticSeverity::Off
16185 {
16186 self.inline_diagnostics_update = Task::ready(());
16187 self.inline_diagnostics.clear();
16188 return;
16189 }
16190
16191 let debounce_ms = ProjectSettings::get_global(cx)
16192 .diagnostics
16193 .inline
16194 .update_debounce_ms;
16195 let debounce = if debounce && debounce_ms > 0 {
16196 Some(Duration::from_millis(debounce_ms))
16197 } else {
16198 None
16199 };
16200 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16201 if let Some(debounce) = debounce {
16202 cx.background_executor().timer(debounce).await;
16203 }
16204 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16205 editor
16206 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16207 .ok()
16208 }) else {
16209 return;
16210 };
16211
16212 let new_inline_diagnostics = cx
16213 .background_spawn(async move {
16214 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16215 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16216 let message = diagnostic_entry
16217 .diagnostic
16218 .message
16219 .split_once('\n')
16220 .map(|(line, _)| line)
16221 .map(SharedString::new)
16222 .unwrap_or_else(|| {
16223 SharedString::from(diagnostic_entry.diagnostic.message)
16224 });
16225 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16226 let (Ok(i) | Err(i)) = inline_diagnostics
16227 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16228 inline_diagnostics.insert(
16229 i,
16230 (
16231 start_anchor,
16232 InlineDiagnostic {
16233 message,
16234 group_id: diagnostic_entry.diagnostic.group_id,
16235 start: diagnostic_entry.range.start.to_point(&snapshot),
16236 is_primary: diagnostic_entry.diagnostic.is_primary,
16237 severity: diagnostic_entry.diagnostic.severity,
16238 },
16239 ),
16240 );
16241 }
16242 inline_diagnostics
16243 })
16244 .await;
16245
16246 editor
16247 .update(cx, |editor, cx| {
16248 editor.inline_diagnostics = new_inline_diagnostics;
16249 cx.notify();
16250 })
16251 .ok();
16252 });
16253 }
16254
16255 fn pull_diagnostics(
16256 &mut self,
16257 buffer_id: Option<BufferId>,
16258 window: &Window,
16259 cx: &mut Context<Self>,
16260 ) -> Option<()> {
16261 if !self.mode().is_full() {
16262 return None;
16263 }
16264 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16265 .diagnostics
16266 .lsp_pull_diagnostics;
16267 if !pull_diagnostics_settings.enabled {
16268 return None;
16269 }
16270 let project = self.project.as_ref()?.downgrade();
16271 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16272 let mut buffers = self.buffer.read(cx).all_buffers();
16273 if let Some(buffer_id) = buffer_id {
16274 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16275 }
16276
16277 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16278 cx.background_executor().timer(debounce).await;
16279
16280 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16281 buffers
16282 .into_iter()
16283 .filter_map(|buffer| {
16284 project
16285 .update(cx, |project, cx| {
16286 project.lsp_store().update(cx, |lsp_store, cx| {
16287 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16288 })
16289 })
16290 .ok()
16291 })
16292 .collect::<FuturesUnordered<_>>()
16293 }) else {
16294 return;
16295 };
16296
16297 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16298 match pull_task {
16299 Ok(()) => {
16300 if editor
16301 .update_in(cx, |editor, window, cx| {
16302 editor.update_diagnostics_state(window, cx);
16303 })
16304 .is_err()
16305 {
16306 return;
16307 }
16308 }
16309 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16310 }
16311 }
16312 });
16313
16314 Some(())
16315 }
16316
16317 pub fn set_selections_from_remote(
16318 &mut self,
16319 selections: Vec<Selection<Anchor>>,
16320 pending_selection: Option<Selection<Anchor>>,
16321 window: &mut Window,
16322 cx: &mut Context<Self>,
16323 ) {
16324 let old_cursor_position = self.selections.newest_anchor().head();
16325 self.selections.change_with(cx, |s| {
16326 s.select_anchors(selections);
16327 if let Some(pending_selection) = pending_selection {
16328 s.set_pending(pending_selection, SelectMode::Character);
16329 } else {
16330 s.clear_pending();
16331 }
16332 });
16333 self.selections_did_change(
16334 false,
16335 &old_cursor_position,
16336 SelectionEffects::default(),
16337 window,
16338 cx,
16339 );
16340 }
16341
16342 pub fn transact(
16343 &mut self,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16347 ) -> Option<TransactionId> {
16348 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16349 this.start_transaction_at(Instant::now(), window, cx);
16350 update(this, window, cx);
16351 this.end_transaction_at(Instant::now(), cx)
16352 })
16353 }
16354
16355 pub fn start_transaction_at(
16356 &mut self,
16357 now: Instant,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) {
16361 self.end_selection(window, cx);
16362 if let Some(tx_id) = self
16363 .buffer
16364 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16365 {
16366 self.selection_history
16367 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16368 cx.emit(EditorEvent::TransactionBegun {
16369 transaction_id: tx_id,
16370 })
16371 }
16372 }
16373
16374 pub fn end_transaction_at(
16375 &mut self,
16376 now: Instant,
16377 cx: &mut Context<Self>,
16378 ) -> Option<TransactionId> {
16379 if let Some(transaction_id) = self
16380 .buffer
16381 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16382 {
16383 if let Some((_, end_selections)) =
16384 self.selection_history.transaction_mut(transaction_id)
16385 {
16386 *end_selections = Some(self.selections.disjoint_anchors());
16387 } else {
16388 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16389 }
16390
16391 cx.emit(EditorEvent::Edited { transaction_id });
16392 Some(transaction_id)
16393 } else {
16394 None
16395 }
16396 }
16397
16398 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16399 if self.selection_mark_mode {
16400 self.change_selections(None, window, cx, |s| {
16401 s.move_with(|_, sel| {
16402 sel.collapse_to(sel.head(), SelectionGoal::None);
16403 });
16404 })
16405 }
16406 self.selection_mark_mode = true;
16407 cx.notify();
16408 }
16409
16410 pub fn swap_selection_ends(
16411 &mut self,
16412 _: &actions::SwapSelectionEnds,
16413 window: &mut Window,
16414 cx: &mut Context<Self>,
16415 ) {
16416 self.change_selections(None, window, cx, |s| {
16417 s.move_with(|_, sel| {
16418 if sel.start != sel.end {
16419 sel.reversed = !sel.reversed
16420 }
16421 });
16422 });
16423 self.request_autoscroll(Autoscroll::newest(), cx);
16424 cx.notify();
16425 }
16426
16427 pub fn toggle_fold(
16428 &mut self,
16429 _: &actions::ToggleFold,
16430 window: &mut Window,
16431 cx: &mut Context<Self>,
16432 ) {
16433 if self.is_singleton(cx) {
16434 let selection = self.selections.newest::<Point>(cx);
16435
16436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16437 let range = if selection.is_empty() {
16438 let point = selection.head().to_display_point(&display_map);
16439 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16440 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16441 .to_point(&display_map);
16442 start..end
16443 } else {
16444 selection.range()
16445 };
16446 if display_map.folds_in_range(range).next().is_some() {
16447 self.unfold_lines(&Default::default(), window, cx)
16448 } else {
16449 self.fold(&Default::default(), window, cx)
16450 }
16451 } else {
16452 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16453 let buffer_ids: HashSet<_> = self
16454 .selections
16455 .disjoint_anchor_ranges()
16456 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16457 .collect();
16458
16459 let should_unfold = buffer_ids
16460 .iter()
16461 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16462
16463 for buffer_id in buffer_ids {
16464 if should_unfold {
16465 self.unfold_buffer(buffer_id, cx);
16466 } else {
16467 self.fold_buffer(buffer_id, cx);
16468 }
16469 }
16470 }
16471 }
16472
16473 pub fn toggle_fold_recursive(
16474 &mut self,
16475 _: &actions::ToggleFoldRecursive,
16476 window: &mut Window,
16477 cx: &mut Context<Self>,
16478 ) {
16479 let selection = self.selections.newest::<Point>(cx);
16480
16481 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16482 let range = if selection.is_empty() {
16483 let point = selection.head().to_display_point(&display_map);
16484 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16485 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16486 .to_point(&display_map);
16487 start..end
16488 } else {
16489 selection.range()
16490 };
16491 if display_map.folds_in_range(range).next().is_some() {
16492 self.unfold_recursive(&Default::default(), window, cx)
16493 } else {
16494 self.fold_recursive(&Default::default(), window, cx)
16495 }
16496 }
16497
16498 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16499 if self.is_singleton(cx) {
16500 let mut to_fold = Vec::new();
16501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16502 let selections = self.selections.all_adjusted(cx);
16503
16504 for selection in selections {
16505 let range = selection.range().sorted();
16506 let buffer_start_row = range.start.row;
16507
16508 if range.start.row != range.end.row {
16509 let mut found = false;
16510 let mut row = range.start.row;
16511 while row <= range.end.row {
16512 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16513 {
16514 found = true;
16515 row = crease.range().end.row + 1;
16516 to_fold.push(crease);
16517 } else {
16518 row += 1
16519 }
16520 }
16521 if found {
16522 continue;
16523 }
16524 }
16525
16526 for row in (0..=range.start.row).rev() {
16527 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16528 if crease.range().end.row >= buffer_start_row {
16529 to_fold.push(crease);
16530 if row <= range.start.row {
16531 break;
16532 }
16533 }
16534 }
16535 }
16536 }
16537
16538 self.fold_creases(to_fold, true, window, cx);
16539 } else {
16540 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16541 let buffer_ids = self
16542 .selections
16543 .disjoint_anchor_ranges()
16544 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16545 .collect::<HashSet<_>>();
16546 for buffer_id in buffer_ids {
16547 self.fold_buffer(buffer_id, cx);
16548 }
16549 }
16550 }
16551
16552 fn fold_at_level(
16553 &mut self,
16554 fold_at: &FoldAtLevel,
16555 window: &mut Window,
16556 cx: &mut Context<Self>,
16557 ) {
16558 if !self.buffer.read(cx).is_singleton() {
16559 return;
16560 }
16561
16562 let fold_at_level = fold_at.0;
16563 let snapshot = self.buffer.read(cx).snapshot(cx);
16564 let mut to_fold = Vec::new();
16565 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16566
16567 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16568 while start_row < end_row {
16569 match self
16570 .snapshot(window, cx)
16571 .crease_for_buffer_row(MultiBufferRow(start_row))
16572 {
16573 Some(crease) => {
16574 let nested_start_row = crease.range().start.row + 1;
16575 let nested_end_row = crease.range().end.row;
16576
16577 if current_level < fold_at_level {
16578 stack.push((nested_start_row, nested_end_row, current_level + 1));
16579 } else if current_level == fold_at_level {
16580 to_fold.push(crease);
16581 }
16582
16583 start_row = nested_end_row + 1;
16584 }
16585 None => start_row += 1,
16586 }
16587 }
16588 }
16589
16590 self.fold_creases(to_fold, true, window, cx);
16591 }
16592
16593 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16594 if self.buffer.read(cx).is_singleton() {
16595 let mut fold_ranges = Vec::new();
16596 let snapshot = self.buffer.read(cx).snapshot(cx);
16597
16598 for row in 0..snapshot.max_row().0 {
16599 if let Some(foldable_range) = self
16600 .snapshot(window, cx)
16601 .crease_for_buffer_row(MultiBufferRow(row))
16602 {
16603 fold_ranges.push(foldable_range);
16604 }
16605 }
16606
16607 self.fold_creases(fold_ranges, true, window, cx);
16608 } else {
16609 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16610 editor
16611 .update_in(cx, |editor, _, cx| {
16612 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16613 editor.fold_buffer(buffer_id, cx);
16614 }
16615 })
16616 .ok();
16617 });
16618 }
16619 }
16620
16621 pub fn fold_function_bodies(
16622 &mut self,
16623 _: &actions::FoldFunctionBodies,
16624 window: &mut Window,
16625 cx: &mut Context<Self>,
16626 ) {
16627 let snapshot = self.buffer.read(cx).snapshot(cx);
16628
16629 let ranges = snapshot
16630 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16631 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16632 .collect::<Vec<_>>();
16633
16634 let creases = ranges
16635 .into_iter()
16636 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16637 .collect();
16638
16639 self.fold_creases(creases, true, window, cx);
16640 }
16641
16642 pub fn fold_recursive(
16643 &mut self,
16644 _: &actions::FoldRecursive,
16645 window: &mut Window,
16646 cx: &mut Context<Self>,
16647 ) {
16648 let mut to_fold = Vec::new();
16649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16650 let selections = self.selections.all_adjusted(cx);
16651
16652 for selection in selections {
16653 let range = selection.range().sorted();
16654 let buffer_start_row = range.start.row;
16655
16656 if range.start.row != range.end.row {
16657 let mut found = false;
16658 for row in range.start.row..=range.end.row {
16659 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16660 found = true;
16661 to_fold.push(crease);
16662 }
16663 }
16664 if found {
16665 continue;
16666 }
16667 }
16668
16669 for row in (0..=range.start.row).rev() {
16670 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16671 if crease.range().end.row >= buffer_start_row {
16672 to_fold.push(crease);
16673 } else {
16674 break;
16675 }
16676 }
16677 }
16678 }
16679
16680 self.fold_creases(to_fold, true, window, cx);
16681 }
16682
16683 pub fn fold_at(
16684 &mut self,
16685 buffer_row: MultiBufferRow,
16686 window: &mut Window,
16687 cx: &mut Context<Self>,
16688 ) {
16689 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16690
16691 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16692 let autoscroll = self
16693 .selections
16694 .all::<Point>(cx)
16695 .iter()
16696 .any(|selection| crease.range().overlaps(&selection.range()));
16697
16698 self.fold_creases(vec![crease], autoscroll, window, cx);
16699 }
16700 }
16701
16702 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16703 if self.is_singleton(cx) {
16704 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16705 let buffer = &display_map.buffer_snapshot;
16706 let selections = self.selections.all::<Point>(cx);
16707 let ranges = selections
16708 .iter()
16709 .map(|s| {
16710 let range = s.display_range(&display_map).sorted();
16711 let mut start = range.start.to_point(&display_map);
16712 let mut end = range.end.to_point(&display_map);
16713 start.column = 0;
16714 end.column = buffer.line_len(MultiBufferRow(end.row));
16715 start..end
16716 })
16717 .collect::<Vec<_>>();
16718
16719 self.unfold_ranges(&ranges, true, true, cx);
16720 } else {
16721 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16722 let buffer_ids = self
16723 .selections
16724 .disjoint_anchor_ranges()
16725 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16726 .collect::<HashSet<_>>();
16727 for buffer_id in buffer_ids {
16728 self.unfold_buffer(buffer_id, cx);
16729 }
16730 }
16731 }
16732
16733 pub fn unfold_recursive(
16734 &mut self,
16735 _: &UnfoldRecursive,
16736 _window: &mut Window,
16737 cx: &mut Context<Self>,
16738 ) {
16739 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16740 let selections = self.selections.all::<Point>(cx);
16741 let ranges = selections
16742 .iter()
16743 .map(|s| {
16744 let mut range = s.display_range(&display_map).sorted();
16745 *range.start.column_mut() = 0;
16746 *range.end.column_mut() = display_map.line_len(range.end.row());
16747 let start = range.start.to_point(&display_map);
16748 let end = range.end.to_point(&display_map);
16749 start..end
16750 })
16751 .collect::<Vec<_>>();
16752
16753 self.unfold_ranges(&ranges, true, true, cx);
16754 }
16755
16756 pub fn unfold_at(
16757 &mut self,
16758 buffer_row: MultiBufferRow,
16759 _window: &mut Window,
16760 cx: &mut Context<Self>,
16761 ) {
16762 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16763
16764 let intersection_range = Point::new(buffer_row.0, 0)
16765 ..Point::new(
16766 buffer_row.0,
16767 display_map.buffer_snapshot.line_len(buffer_row),
16768 );
16769
16770 let autoscroll = self
16771 .selections
16772 .all::<Point>(cx)
16773 .iter()
16774 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16775
16776 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16777 }
16778
16779 pub fn unfold_all(
16780 &mut self,
16781 _: &actions::UnfoldAll,
16782 _window: &mut Window,
16783 cx: &mut Context<Self>,
16784 ) {
16785 if self.buffer.read(cx).is_singleton() {
16786 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16787 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16788 } else {
16789 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16790 editor
16791 .update(cx, |editor, cx| {
16792 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16793 editor.unfold_buffer(buffer_id, cx);
16794 }
16795 })
16796 .ok();
16797 });
16798 }
16799 }
16800
16801 pub fn fold_selected_ranges(
16802 &mut self,
16803 _: &FoldSelectedRanges,
16804 window: &mut Window,
16805 cx: &mut Context<Self>,
16806 ) {
16807 let selections = self.selections.all_adjusted(cx);
16808 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16809 let ranges = selections
16810 .into_iter()
16811 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16812 .collect::<Vec<_>>();
16813 self.fold_creases(ranges, true, window, cx);
16814 }
16815
16816 pub fn fold_ranges<T: ToOffset + Clone>(
16817 &mut self,
16818 ranges: Vec<Range<T>>,
16819 auto_scroll: bool,
16820 window: &mut Window,
16821 cx: &mut Context<Self>,
16822 ) {
16823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16824 let ranges = ranges
16825 .into_iter()
16826 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16827 .collect::<Vec<_>>();
16828 self.fold_creases(ranges, auto_scroll, window, cx);
16829 }
16830
16831 pub fn fold_creases<T: ToOffset + Clone>(
16832 &mut self,
16833 creases: Vec<Crease<T>>,
16834 auto_scroll: bool,
16835 _window: &mut Window,
16836 cx: &mut Context<Self>,
16837 ) {
16838 if creases.is_empty() {
16839 return;
16840 }
16841
16842 let mut buffers_affected = HashSet::default();
16843 let multi_buffer = self.buffer().read(cx);
16844 for crease in &creases {
16845 if let Some((_, buffer, _)) =
16846 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16847 {
16848 buffers_affected.insert(buffer.read(cx).remote_id());
16849 };
16850 }
16851
16852 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16853
16854 if auto_scroll {
16855 self.request_autoscroll(Autoscroll::fit(), cx);
16856 }
16857
16858 cx.notify();
16859
16860 self.scrollbar_marker_state.dirty = true;
16861 self.folds_did_change(cx);
16862 }
16863
16864 /// Removes any folds whose ranges intersect any of the given ranges.
16865 pub fn unfold_ranges<T: ToOffset + Clone>(
16866 &mut self,
16867 ranges: &[Range<T>],
16868 inclusive: bool,
16869 auto_scroll: bool,
16870 cx: &mut Context<Self>,
16871 ) {
16872 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16873 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16874 });
16875 self.folds_did_change(cx);
16876 }
16877
16878 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16879 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16880 return;
16881 }
16882 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16883 self.display_map.update(cx, |display_map, cx| {
16884 display_map.fold_buffers([buffer_id], cx)
16885 });
16886 cx.emit(EditorEvent::BufferFoldToggled {
16887 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16888 folded: true,
16889 });
16890 cx.notify();
16891 }
16892
16893 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16894 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16895 return;
16896 }
16897 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16898 self.display_map.update(cx, |display_map, cx| {
16899 display_map.unfold_buffers([buffer_id], cx);
16900 });
16901 cx.emit(EditorEvent::BufferFoldToggled {
16902 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16903 folded: false,
16904 });
16905 cx.notify();
16906 }
16907
16908 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16909 self.display_map.read(cx).is_buffer_folded(buffer)
16910 }
16911
16912 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16913 self.display_map.read(cx).folded_buffers()
16914 }
16915
16916 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16917 self.display_map.update(cx, |display_map, cx| {
16918 display_map.disable_header_for_buffer(buffer_id, cx);
16919 });
16920 cx.notify();
16921 }
16922
16923 /// Removes any folds with the given ranges.
16924 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16925 &mut self,
16926 ranges: &[Range<T>],
16927 type_id: TypeId,
16928 auto_scroll: bool,
16929 cx: &mut Context<Self>,
16930 ) {
16931 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16932 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16933 });
16934 self.folds_did_change(cx);
16935 }
16936
16937 fn remove_folds_with<T: ToOffset + Clone>(
16938 &mut self,
16939 ranges: &[Range<T>],
16940 auto_scroll: bool,
16941 cx: &mut Context<Self>,
16942 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16943 ) {
16944 if ranges.is_empty() {
16945 return;
16946 }
16947
16948 let mut buffers_affected = HashSet::default();
16949 let multi_buffer = self.buffer().read(cx);
16950 for range in ranges {
16951 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16952 buffers_affected.insert(buffer.read(cx).remote_id());
16953 };
16954 }
16955
16956 self.display_map.update(cx, update);
16957
16958 if auto_scroll {
16959 self.request_autoscroll(Autoscroll::fit(), cx);
16960 }
16961
16962 cx.notify();
16963 self.scrollbar_marker_state.dirty = true;
16964 self.active_indent_guides_state.dirty = true;
16965 }
16966
16967 pub fn update_fold_widths(
16968 &mut self,
16969 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16970 cx: &mut Context<Self>,
16971 ) -> bool {
16972 self.display_map
16973 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16974 }
16975
16976 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16977 self.display_map.read(cx).fold_placeholder.clone()
16978 }
16979
16980 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16981 self.buffer.update(cx, |buffer, cx| {
16982 buffer.set_all_diff_hunks_expanded(cx);
16983 });
16984 }
16985
16986 pub fn expand_all_diff_hunks(
16987 &mut self,
16988 _: &ExpandAllDiffHunks,
16989 _window: &mut Window,
16990 cx: &mut Context<Self>,
16991 ) {
16992 self.buffer.update(cx, |buffer, cx| {
16993 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16994 });
16995 }
16996
16997 pub fn toggle_selected_diff_hunks(
16998 &mut self,
16999 _: &ToggleSelectedDiffHunks,
17000 _window: &mut Window,
17001 cx: &mut Context<Self>,
17002 ) {
17003 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17004 self.toggle_diff_hunks_in_ranges(ranges, cx);
17005 }
17006
17007 pub fn diff_hunks_in_ranges<'a>(
17008 &'a self,
17009 ranges: &'a [Range<Anchor>],
17010 buffer: &'a MultiBufferSnapshot,
17011 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17012 ranges.iter().flat_map(move |range| {
17013 let end_excerpt_id = range.end.excerpt_id;
17014 let range = range.to_point(buffer);
17015 let mut peek_end = range.end;
17016 if range.end.row < buffer.max_row().0 {
17017 peek_end = Point::new(range.end.row + 1, 0);
17018 }
17019 buffer
17020 .diff_hunks_in_range(range.start..peek_end)
17021 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17022 })
17023 }
17024
17025 pub fn has_stageable_diff_hunks_in_ranges(
17026 &self,
17027 ranges: &[Range<Anchor>],
17028 snapshot: &MultiBufferSnapshot,
17029 ) -> bool {
17030 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17031 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17032 }
17033
17034 pub fn toggle_staged_selected_diff_hunks(
17035 &mut self,
17036 _: &::git::ToggleStaged,
17037 _: &mut Window,
17038 cx: &mut Context<Self>,
17039 ) {
17040 let snapshot = self.buffer.read(cx).snapshot(cx);
17041 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17042 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17043 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17044 }
17045
17046 pub fn set_render_diff_hunk_controls(
17047 &mut self,
17048 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17049 cx: &mut Context<Self>,
17050 ) {
17051 self.render_diff_hunk_controls = render_diff_hunk_controls;
17052 cx.notify();
17053 }
17054
17055 pub fn stage_and_next(
17056 &mut self,
17057 _: &::git::StageAndNext,
17058 window: &mut Window,
17059 cx: &mut Context<Self>,
17060 ) {
17061 self.do_stage_or_unstage_and_next(true, window, cx);
17062 }
17063
17064 pub fn unstage_and_next(
17065 &mut self,
17066 _: &::git::UnstageAndNext,
17067 window: &mut Window,
17068 cx: &mut Context<Self>,
17069 ) {
17070 self.do_stage_or_unstage_and_next(false, window, cx);
17071 }
17072
17073 pub fn stage_or_unstage_diff_hunks(
17074 &mut self,
17075 stage: bool,
17076 ranges: Vec<Range<Anchor>>,
17077 cx: &mut Context<Self>,
17078 ) {
17079 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17080 cx.spawn(async move |this, cx| {
17081 task.await?;
17082 this.update(cx, |this, cx| {
17083 let snapshot = this.buffer.read(cx).snapshot(cx);
17084 let chunk_by = this
17085 .diff_hunks_in_ranges(&ranges, &snapshot)
17086 .chunk_by(|hunk| hunk.buffer_id);
17087 for (buffer_id, hunks) in &chunk_by {
17088 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17089 }
17090 })
17091 })
17092 .detach_and_log_err(cx);
17093 }
17094
17095 fn save_buffers_for_ranges_if_needed(
17096 &mut self,
17097 ranges: &[Range<Anchor>],
17098 cx: &mut Context<Editor>,
17099 ) -> Task<Result<()>> {
17100 let multibuffer = self.buffer.read(cx);
17101 let snapshot = multibuffer.read(cx);
17102 let buffer_ids: HashSet<_> = ranges
17103 .iter()
17104 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17105 .collect();
17106 drop(snapshot);
17107
17108 let mut buffers = HashSet::default();
17109 for buffer_id in buffer_ids {
17110 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17111 let buffer = buffer_entity.read(cx);
17112 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17113 {
17114 buffers.insert(buffer_entity);
17115 }
17116 }
17117 }
17118
17119 if let Some(project) = &self.project {
17120 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17121 } else {
17122 Task::ready(Ok(()))
17123 }
17124 }
17125
17126 fn do_stage_or_unstage_and_next(
17127 &mut self,
17128 stage: bool,
17129 window: &mut Window,
17130 cx: &mut Context<Self>,
17131 ) {
17132 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17133
17134 if ranges.iter().any(|range| range.start != range.end) {
17135 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17136 return;
17137 }
17138
17139 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17140 let snapshot = self.snapshot(window, cx);
17141 let position = self.selections.newest::<Point>(cx).head();
17142 let mut row = snapshot
17143 .buffer_snapshot
17144 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17145 .find(|hunk| hunk.row_range.start.0 > position.row)
17146 .map(|hunk| hunk.row_range.start);
17147
17148 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17149 // Outside of the project diff editor, wrap around to the beginning.
17150 if !all_diff_hunks_expanded {
17151 row = row.or_else(|| {
17152 snapshot
17153 .buffer_snapshot
17154 .diff_hunks_in_range(Point::zero()..position)
17155 .find(|hunk| hunk.row_range.end.0 < position.row)
17156 .map(|hunk| hunk.row_range.start)
17157 });
17158 }
17159
17160 if let Some(row) = row {
17161 let destination = Point::new(row.0, 0);
17162 let autoscroll = Autoscroll::center();
17163
17164 self.unfold_ranges(&[destination..destination], false, false, cx);
17165 self.change_selections(Some(autoscroll), window, cx, |s| {
17166 s.select_ranges([destination..destination]);
17167 });
17168 }
17169 }
17170
17171 fn do_stage_or_unstage(
17172 &self,
17173 stage: bool,
17174 buffer_id: BufferId,
17175 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17176 cx: &mut App,
17177 ) -> Option<()> {
17178 let project = self.project.as_ref()?;
17179 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17180 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17181 let buffer_snapshot = buffer.read(cx).snapshot();
17182 let file_exists = buffer_snapshot
17183 .file()
17184 .is_some_and(|file| file.disk_state().exists());
17185 diff.update(cx, |diff, cx| {
17186 diff.stage_or_unstage_hunks(
17187 stage,
17188 &hunks
17189 .map(|hunk| buffer_diff::DiffHunk {
17190 buffer_range: hunk.buffer_range,
17191 diff_base_byte_range: hunk.diff_base_byte_range,
17192 secondary_status: hunk.secondary_status,
17193 range: Point::zero()..Point::zero(), // unused
17194 })
17195 .collect::<Vec<_>>(),
17196 &buffer_snapshot,
17197 file_exists,
17198 cx,
17199 )
17200 });
17201 None
17202 }
17203
17204 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17205 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17206 self.buffer
17207 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17208 }
17209
17210 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17211 self.buffer.update(cx, |buffer, cx| {
17212 let ranges = vec![Anchor::min()..Anchor::max()];
17213 if !buffer.all_diff_hunks_expanded()
17214 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17215 {
17216 buffer.collapse_diff_hunks(ranges, cx);
17217 true
17218 } else {
17219 false
17220 }
17221 })
17222 }
17223
17224 fn toggle_diff_hunks_in_ranges(
17225 &mut self,
17226 ranges: Vec<Range<Anchor>>,
17227 cx: &mut Context<Editor>,
17228 ) {
17229 self.buffer.update(cx, |buffer, cx| {
17230 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17231 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17232 })
17233 }
17234
17235 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17236 self.buffer.update(cx, |buffer, cx| {
17237 let snapshot = buffer.snapshot(cx);
17238 let excerpt_id = range.end.excerpt_id;
17239 let point_range = range.to_point(&snapshot);
17240 let expand = !buffer.single_hunk_is_expanded(range, cx);
17241 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17242 })
17243 }
17244
17245 pub(crate) fn apply_all_diff_hunks(
17246 &mut self,
17247 _: &ApplyAllDiffHunks,
17248 window: &mut Window,
17249 cx: &mut Context<Self>,
17250 ) {
17251 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17252
17253 let buffers = self.buffer.read(cx).all_buffers();
17254 for branch_buffer in buffers {
17255 branch_buffer.update(cx, |branch_buffer, cx| {
17256 branch_buffer.merge_into_base(Vec::new(), cx);
17257 });
17258 }
17259
17260 if let Some(project) = self.project.clone() {
17261 self.save(
17262 SaveOptions {
17263 format: true,
17264 autosave: false,
17265 },
17266 project,
17267 window,
17268 cx,
17269 )
17270 .detach_and_log_err(cx);
17271 }
17272 }
17273
17274 pub(crate) fn apply_selected_diff_hunks(
17275 &mut self,
17276 _: &ApplyDiffHunk,
17277 window: &mut Window,
17278 cx: &mut Context<Self>,
17279 ) {
17280 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17281 let snapshot = self.snapshot(window, cx);
17282 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17283 let mut ranges_by_buffer = HashMap::default();
17284 self.transact(window, cx, |editor, _window, cx| {
17285 for hunk in hunks {
17286 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17287 ranges_by_buffer
17288 .entry(buffer.clone())
17289 .or_insert_with(Vec::new)
17290 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17291 }
17292 }
17293
17294 for (buffer, ranges) in ranges_by_buffer {
17295 buffer.update(cx, |buffer, cx| {
17296 buffer.merge_into_base(ranges, cx);
17297 });
17298 }
17299 });
17300
17301 if let Some(project) = self.project.clone() {
17302 self.save(
17303 SaveOptions {
17304 format: true,
17305 autosave: false,
17306 },
17307 project,
17308 window,
17309 cx,
17310 )
17311 .detach_and_log_err(cx);
17312 }
17313 }
17314
17315 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17316 if hovered != self.gutter_hovered {
17317 self.gutter_hovered = hovered;
17318 cx.notify();
17319 }
17320 }
17321
17322 pub fn insert_blocks(
17323 &mut self,
17324 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17325 autoscroll: Option<Autoscroll>,
17326 cx: &mut Context<Self>,
17327 ) -> Vec<CustomBlockId> {
17328 let blocks = self
17329 .display_map
17330 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17331 if let Some(autoscroll) = autoscroll {
17332 self.request_autoscroll(autoscroll, cx);
17333 }
17334 cx.notify();
17335 blocks
17336 }
17337
17338 pub fn resize_blocks(
17339 &mut self,
17340 heights: HashMap<CustomBlockId, u32>,
17341 autoscroll: Option<Autoscroll>,
17342 cx: &mut Context<Self>,
17343 ) {
17344 self.display_map
17345 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17346 if let Some(autoscroll) = autoscroll {
17347 self.request_autoscroll(autoscroll, cx);
17348 }
17349 cx.notify();
17350 }
17351
17352 pub fn replace_blocks(
17353 &mut self,
17354 renderers: HashMap<CustomBlockId, RenderBlock>,
17355 autoscroll: Option<Autoscroll>,
17356 cx: &mut Context<Self>,
17357 ) {
17358 self.display_map
17359 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17360 if let Some(autoscroll) = autoscroll {
17361 self.request_autoscroll(autoscroll, cx);
17362 }
17363 cx.notify();
17364 }
17365
17366 pub fn remove_blocks(
17367 &mut self,
17368 block_ids: HashSet<CustomBlockId>,
17369 autoscroll: Option<Autoscroll>,
17370 cx: &mut Context<Self>,
17371 ) {
17372 self.display_map.update(cx, |display_map, cx| {
17373 display_map.remove_blocks(block_ids, cx)
17374 });
17375 if let Some(autoscroll) = autoscroll {
17376 self.request_autoscroll(autoscroll, cx);
17377 }
17378 cx.notify();
17379 }
17380
17381 pub fn row_for_block(
17382 &self,
17383 block_id: CustomBlockId,
17384 cx: &mut Context<Self>,
17385 ) -> Option<DisplayRow> {
17386 self.display_map
17387 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17388 }
17389
17390 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17391 self.focused_block = Some(focused_block);
17392 }
17393
17394 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17395 self.focused_block.take()
17396 }
17397
17398 pub fn insert_creases(
17399 &mut self,
17400 creases: impl IntoIterator<Item = Crease<Anchor>>,
17401 cx: &mut Context<Self>,
17402 ) -> Vec<CreaseId> {
17403 self.display_map
17404 .update(cx, |map, cx| map.insert_creases(creases, cx))
17405 }
17406
17407 pub fn remove_creases(
17408 &mut self,
17409 ids: impl IntoIterator<Item = CreaseId>,
17410 cx: &mut Context<Self>,
17411 ) -> Vec<(CreaseId, Range<Anchor>)> {
17412 self.display_map
17413 .update(cx, |map, cx| map.remove_creases(ids, cx))
17414 }
17415
17416 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17417 self.display_map
17418 .update(cx, |map, cx| map.snapshot(cx))
17419 .longest_row()
17420 }
17421
17422 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17423 self.display_map
17424 .update(cx, |map, cx| map.snapshot(cx))
17425 .max_point()
17426 }
17427
17428 pub fn text(&self, cx: &App) -> String {
17429 self.buffer.read(cx).read(cx).text()
17430 }
17431
17432 pub fn is_empty(&self, cx: &App) -> bool {
17433 self.buffer.read(cx).read(cx).is_empty()
17434 }
17435
17436 pub fn text_option(&self, cx: &App) -> Option<String> {
17437 let text = self.text(cx);
17438 let text = text.trim();
17439
17440 if text.is_empty() {
17441 return None;
17442 }
17443
17444 Some(text.to_string())
17445 }
17446
17447 pub fn set_text(
17448 &mut self,
17449 text: impl Into<Arc<str>>,
17450 window: &mut Window,
17451 cx: &mut Context<Self>,
17452 ) {
17453 self.transact(window, cx, |this, _, cx| {
17454 this.buffer
17455 .read(cx)
17456 .as_singleton()
17457 .expect("you can only call set_text on editors for singleton buffers")
17458 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17459 });
17460 }
17461
17462 pub fn display_text(&self, cx: &mut App) -> String {
17463 self.display_map
17464 .update(cx, |map, cx| map.snapshot(cx))
17465 .text()
17466 }
17467
17468 fn create_minimap(
17469 &self,
17470 minimap_settings: MinimapSettings,
17471 window: &mut Window,
17472 cx: &mut Context<Self>,
17473 ) -> Option<Entity<Self>> {
17474 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17475 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17476 }
17477
17478 fn initialize_new_minimap(
17479 &self,
17480 minimap_settings: MinimapSettings,
17481 window: &mut Window,
17482 cx: &mut Context<Self>,
17483 ) -> Entity<Self> {
17484 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17485
17486 let mut minimap = Editor::new_internal(
17487 EditorMode::Minimap {
17488 parent: cx.weak_entity(),
17489 },
17490 self.buffer.clone(),
17491 self.project.clone(),
17492 Some(self.display_map.clone()),
17493 window,
17494 cx,
17495 );
17496 minimap.scroll_manager.clone_state(&self.scroll_manager);
17497 minimap.set_text_style_refinement(TextStyleRefinement {
17498 font_size: Some(MINIMAP_FONT_SIZE),
17499 font_weight: Some(MINIMAP_FONT_WEIGHT),
17500 ..Default::default()
17501 });
17502 minimap.update_minimap_configuration(minimap_settings, cx);
17503 cx.new(|_| minimap)
17504 }
17505
17506 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17507 let current_line_highlight = minimap_settings
17508 .current_line_highlight
17509 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17510 self.set_current_line_highlight(Some(current_line_highlight));
17511 }
17512
17513 pub fn minimap(&self) -> Option<&Entity<Self>> {
17514 self.minimap
17515 .as_ref()
17516 .filter(|_| self.minimap_visibility.visible())
17517 }
17518
17519 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17520 let mut wrap_guides = smallvec![];
17521
17522 if self.show_wrap_guides == Some(false) {
17523 return wrap_guides;
17524 }
17525
17526 let settings = self.buffer.read(cx).language_settings(cx);
17527 if settings.show_wrap_guides {
17528 match self.soft_wrap_mode(cx) {
17529 SoftWrap::Column(soft_wrap) => {
17530 wrap_guides.push((soft_wrap as usize, true));
17531 }
17532 SoftWrap::Bounded(soft_wrap) => {
17533 wrap_guides.push((soft_wrap as usize, true));
17534 }
17535 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17536 }
17537 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17538 }
17539
17540 wrap_guides
17541 }
17542
17543 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17544 let settings = self.buffer.read(cx).language_settings(cx);
17545 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17546 match mode {
17547 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17548 SoftWrap::None
17549 }
17550 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17551 language_settings::SoftWrap::PreferredLineLength => {
17552 SoftWrap::Column(settings.preferred_line_length)
17553 }
17554 language_settings::SoftWrap::Bounded => {
17555 SoftWrap::Bounded(settings.preferred_line_length)
17556 }
17557 }
17558 }
17559
17560 pub fn set_soft_wrap_mode(
17561 &mut self,
17562 mode: language_settings::SoftWrap,
17563
17564 cx: &mut Context<Self>,
17565 ) {
17566 self.soft_wrap_mode_override = Some(mode);
17567 cx.notify();
17568 }
17569
17570 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17571 self.hard_wrap = hard_wrap;
17572 cx.notify();
17573 }
17574
17575 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17576 self.text_style_refinement = Some(style);
17577 }
17578
17579 /// called by the Element so we know what style we were most recently rendered with.
17580 pub(crate) fn set_style(
17581 &mut self,
17582 style: EditorStyle,
17583 window: &mut Window,
17584 cx: &mut Context<Self>,
17585 ) {
17586 // We intentionally do not inform the display map about the minimap style
17587 // so that wrapping is not recalculated and stays consistent for the editor
17588 // and its linked minimap.
17589 if !self.mode.is_minimap() {
17590 let rem_size = window.rem_size();
17591 self.display_map.update(cx, |map, cx| {
17592 map.set_font(
17593 style.text.font(),
17594 style.text.font_size.to_pixels(rem_size),
17595 cx,
17596 )
17597 });
17598 }
17599 self.style = Some(style);
17600 }
17601
17602 pub fn style(&self) -> Option<&EditorStyle> {
17603 self.style.as_ref()
17604 }
17605
17606 // Called by the element. This method is not designed to be called outside of the editor
17607 // element's layout code because it does not notify when rewrapping is computed synchronously.
17608 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17609 self.display_map
17610 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17611 }
17612
17613 pub fn set_soft_wrap(&mut self) {
17614 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17615 }
17616
17617 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17618 if self.soft_wrap_mode_override.is_some() {
17619 self.soft_wrap_mode_override.take();
17620 } else {
17621 let soft_wrap = match self.soft_wrap_mode(cx) {
17622 SoftWrap::GitDiff => return,
17623 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17624 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17625 language_settings::SoftWrap::None
17626 }
17627 };
17628 self.soft_wrap_mode_override = Some(soft_wrap);
17629 }
17630 cx.notify();
17631 }
17632
17633 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17634 let Some(workspace) = self.workspace() else {
17635 return;
17636 };
17637 let fs = workspace.read(cx).app_state().fs.clone();
17638 let current_show = TabBarSettings::get_global(cx).show;
17639 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17640 setting.show = Some(!current_show);
17641 });
17642 }
17643
17644 pub fn toggle_indent_guides(
17645 &mut self,
17646 _: &ToggleIndentGuides,
17647 _: &mut Window,
17648 cx: &mut Context<Self>,
17649 ) {
17650 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17651 self.buffer
17652 .read(cx)
17653 .language_settings(cx)
17654 .indent_guides
17655 .enabled
17656 });
17657 self.show_indent_guides = Some(!currently_enabled);
17658 cx.notify();
17659 }
17660
17661 fn should_show_indent_guides(&self) -> Option<bool> {
17662 self.show_indent_guides
17663 }
17664
17665 pub fn toggle_line_numbers(
17666 &mut self,
17667 _: &ToggleLineNumbers,
17668 _: &mut Window,
17669 cx: &mut Context<Self>,
17670 ) {
17671 let mut editor_settings = EditorSettings::get_global(cx).clone();
17672 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17673 EditorSettings::override_global(editor_settings, cx);
17674 }
17675
17676 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17677 if let Some(show_line_numbers) = self.show_line_numbers {
17678 return show_line_numbers;
17679 }
17680 EditorSettings::get_global(cx).gutter.line_numbers
17681 }
17682
17683 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17684 self.use_relative_line_numbers
17685 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17686 }
17687
17688 pub fn toggle_relative_line_numbers(
17689 &mut self,
17690 _: &ToggleRelativeLineNumbers,
17691 _: &mut Window,
17692 cx: &mut Context<Self>,
17693 ) {
17694 let is_relative = self.should_use_relative_line_numbers(cx);
17695 self.set_relative_line_number(Some(!is_relative), cx)
17696 }
17697
17698 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17699 self.use_relative_line_numbers = is_relative;
17700 cx.notify();
17701 }
17702
17703 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17704 self.show_gutter = show_gutter;
17705 cx.notify();
17706 }
17707
17708 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17709 self.show_scrollbars = ScrollbarAxes {
17710 horizontal: show,
17711 vertical: show,
17712 };
17713 cx.notify();
17714 }
17715
17716 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17717 self.show_scrollbars.vertical = show;
17718 cx.notify();
17719 }
17720
17721 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17722 self.show_scrollbars.horizontal = show;
17723 cx.notify();
17724 }
17725
17726 pub fn set_minimap_visibility(
17727 &mut self,
17728 minimap_visibility: MinimapVisibility,
17729 window: &mut Window,
17730 cx: &mut Context<Self>,
17731 ) {
17732 if self.minimap_visibility != minimap_visibility {
17733 if minimap_visibility.visible() && self.minimap.is_none() {
17734 let minimap_settings = EditorSettings::get_global(cx).minimap;
17735 self.minimap =
17736 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17737 }
17738 self.minimap_visibility = minimap_visibility;
17739 cx.notify();
17740 }
17741 }
17742
17743 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17744 self.set_show_scrollbars(false, cx);
17745 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17746 }
17747
17748 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17749 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17750 }
17751
17752 /// Normally the text in full mode and auto height editors is padded on the
17753 /// left side by roughly half a character width for improved hit testing.
17754 ///
17755 /// Use this method to disable this for cases where this is not wanted (e.g.
17756 /// if you want to align the editor text with some other text above or below)
17757 /// or if you want to add this padding to single-line editors.
17758 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17759 self.offset_content = offset_content;
17760 cx.notify();
17761 }
17762
17763 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17764 self.show_line_numbers = Some(show_line_numbers);
17765 cx.notify();
17766 }
17767
17768 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17769 self.disable_expand_excerpt_buttons = true;
17770 cx.notify();
17771 }
17772
17773 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17774 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17775 cx.notify();
17776 }
17777
17778 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17779 self.show_code_actions = Some(show_code_actions);
17780 cx.notify();
17781 }
17782
17783 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17784 self.show_runnables = Some(show_runnables);
17785 cx.notify();
17786 }
17787
17788 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17789 self.show_breakpoints = Some(show_breakpoints);
17790 cx.notify();
17791 }
17792
17793 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17794 if self.display_map.read(cx).masked != masked {
17795 self.display_map.update(cx, |map, _| map.masked = masked);
17796 }
17797 cx.notify()
17798 }
17799
17800 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17801 self.show_wrap_guides = Some(show_wrap_guides);
17802 cx.notify();
17803 }
17804
17805 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17806 self.show_indent_guides = Some(show_indent_guides);
17807 cx.notify();
17808 }
17809
17810 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17811 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17812 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17813 if let Some(dir) = file.abs_path(cx).parent() {
17814 return Some(dir.to_owned());
17815 }
17816 }
17817
17818 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17819 return Some(project_path.path.to_path_buf());
17820 }
17821 }
17822
17823 None
17824 }
17825
17826 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17827 self.active_excerpt(cx)?
17828 .1
17829 .read(cx)
17830 .file()
17831 .and_then(|f| f.as_local())
17832 }
17833
17834 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17835 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17836 let buffer = buffer.read(cx);
17837 if let Some(project_path) = buffer.project_path(cx) {
17838 let project = self.project.as_ref()?.read(cx);
17839 project.absolute_path(&project_path, cx)
17840 } else {
17841 buffer
17842 .file()
17843 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17844 }
17845 })
17846 }
17847
17848 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17849 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17850 let project_path = buffer.read(cx).project_path(cx)?;
17851 let project = self.project.as_ref()?.read(cx);
17852 let entry = project.entry_for_path(&project_path, cx)?;
17853 let path = entry.path.to_path_buf();
17854 Some(path)
17855 })
17856 }
17857
17858 pub fn reveal_in_finder(
17859 &mut self,
17860 _: &RevealInFileManager,
17861 _window: &mut Window,
17862 cx: &mut Context<Self>,
17863 ) {
17864 if let Some(target) = self.target_file(cx) {
17865 cx.reveal_path(&target.abs_path(cx));
17866 }
17867 }
17868
17869 pub fn copy_path(
17870 &mut self,
17871 _: &zed_actions::workspace::CopyPath,
17872 _window: &mut Window,
17873 cx: &mut Context<Self>,
17874 ) {
17875 if let Some(path) = self.target_file_abs_path(cx) {
17876 if let Some(path) = path.to_str() {
17877 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17878 }
17879 }
17880 }
17881
17882 pub fn copy_relative_path(
17883 &mut self,
17884 _: &zed_actions::workspace::CopyRelativePath,
17885 _window: &mut Window,
17886 cx: &mut Context<Self>,
17887 ) {
17888 if let Some(path) = self.target_file_path(cx) {
17889 if let Some(path) = path.to_str() {
17890 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17891 }
17892 }
17893 }
17894
17895 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17896 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17897 buffer.read(cx).project_path(cx)
17898 } else {
17899 None
17900 }
17901 }
17902
17903 // Returns true if the editor handled a go-to-line request
17904 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17905 maybe!({
17906 let breakpoint_store = self.breakpoint_store.as_ref()?;
17907
17908 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17909 else {
17910 self.clear_row_highlights::<ActiveDebugLine>();
17911 return None;
17912 };
17913
17914 let position = active_stack_frame.position;
17915 let buffer_id = position.buffer_id?;
17916 let snapshot = self
17917 .project
17918 .as_ref()?
17919 .read(cx)
17920 .buffer_for_id(buffer_id, cx)?
17921 .read(cx)
17922 .snapshot();
17923
17924 let mut handled = false;
17925 for (id, ExcerptRange { context, .. }) in
17926 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17927 {
17928 if context.start.cmp(&position, &snapshot).is_ge()
17929 || context.end.cmp(&position, &snapshot).is_lt()
17930 {
17931 continue;
17932 }
17933 let snapshot = self.buffer.read(cx).snapshot(cx);
17934 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17935
17936 handled = true;
17937 self.clear_row_highlights::<ActiveDebugLine>();
17938
17939 self.go_to_line::<ActiveDebugLine>(
17940 multibuffer_anchor,
17941 Some(cx.theme().colors().editor_debugger_active_line_background),
17942 window,
17943 cx,
17944 );
17945
17946 cx.notify();
17947 }
17948
17949 handled.then_some(())
17950 })
17951 .is_some()
17952 }
17953
17954 pub fn copy_file_name_without_extension(
17955 &mut self,
17956 _: &CopyFileNameWithoutExtension,
17957 _: &mut Window,
17958 cx: &mut Context<Self>,
17959 ) {
17960 if let Some(file) = self.target_file(cx) {
17961 if let Some(file_stem) = file.path().file_stem() {
17962 if let Some(name) = file_stem.to_str() {
17963 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17964 }
17965 }
17966 }
17967 }
17968
17969 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17970 if let Some(file) = self.target_file(cx) {
17971 if let Some(file_name) = file.path().file_name() {
17972 if let Some(name) = file_name.to_str() {
17973 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17974 }
17975 }
17976 }
17977 }
17978
17979 pub fn toggle_git_blame(
17980 &mut self,
17981 _: &::git::Blame,
17982 window: &mut Window,
17983 cx: &mut Context<Self>,
17984 ) {
17985 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17986
17987 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17988 self.start_git_blame(true, window, cx);
17989 }
17990
17991 cx.notify();
17992 }
17993
17994 pub fn toggle_git_blame_inline(
17995 &mut self,
17996 _: &ToggleGitBlameInline,
17997 window: &mut Window,
17998 cx: &mut Context<Self>,
17999 ) {
18000 self.toggle_git_blame_inline_internal(true, window, cx);
18001 cx.notify();
18002 }
18003
18004 pub fn open_git_blame_commit(
18005 &mut self,
18006 _: &OpenGitBlameCommit,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) {
18010 self.open_git_blame_commit_internal(window, cx);
18011 }
18012
18013 fn open_git_blame_commit_internal(
18014 &mut self,
18015 window: &mut Window,
18016 cx: &mut Context<Self>,
18017 ) -> Option<()> {
18018 let blame = self.blame.as_ref()?;
18019 let snapshot = self.snapshot(window, cx);
18020 let cursor = self.selections.newest::<Point>(cx).head();
18021 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18022 let blame_entry = blame
18023 .update(cx, |blame, cx| {
18024 blame
18025 .blame_for_rows(
18026 &[RowInfo {
18027 buffer_id: Some(buffer.remote_id()),
18028 buffer_row: Some(point.row),
18029 ..Default::default()
18030 }],
18031 cx,
18032 )
18033 .next()
18034 })
18035 .flatten()?;
18036 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18037 let repo = blame.read(cx).repository(cx)?;
18038 let workspace = self.workspace()?.downgrade();
18039 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18040 None
18041 }
18042
18043 pub fn git_blame_inline_enabled(&self) -> bool {
18044 self.git_blame_inline_enabled
18045 }
18046
18047 pub fn toggle_selection_menu(
18048 &mut self,
18049 _: &ToggleSelectionMenu,
18050 _: &mut Window,
18051 cx: &mut Context<Self>,
18052 ) {
18053 self.show_selection_menu = self
18054 .show_selection_menu
18055 .map(|show_selections_menu| !show_selections_menu)
18056 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18057
18058 cx.notify();
18059 }
18060
18061 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18062 self.show_selection_menu
18063 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18064 }
18065
18066 fn start_git_blame(
18067 &mut self,
18068 user_triggered: bool,
18069 window: &mut Window,
18070 cx: &mut Context<Self>,
18071 ) {
18072 if let Some(project) = self.project.as_ref() {
18073 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18074 return;
18075 };
18076
18077 if buffer.read(cx).file().is_none() {
18078 return;
18079 }
18080
18081 let focused = self.focus_handle(cx).contains_focused(window, cx);
18082
18083 let project = project.clone();
18084 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18085 self.blame_subscription =
18086 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18087 self.blame = Some(blame);
18088 }
18089 }
18090
18091 fn toggle_git_blame_inline_internal(
18092 &mut self,
18093 user_triggered: bool,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 if self.git_blame_inline_enabled {
18098 self.git_blame_inline_enabled = false;
18099 self.show_git_blame_inline = false;
18100 self.show_git_blame_inline_delay_task.take();
18101 } else {
18102 self.git_blame_inline_enabled = true;
18103 self.start_git_blame_inline(user_triggered, window, cx);
18104 }
18105
18106 cx.notify();
18107 }
18108
18109 fn start_git_blame_inline(
18110 &mut self,
18111 user_triggered: bool,
18112 window: &mut Window,
18113 cx: &mut Context<Self>,
18114 ) {
18115 self.start_git_blame(user_triggered, window, cx);
18116
18117 if ProjectSettings::get_global(cx)
18118 .git
18119 .inline_blame_delay()
18120 .is_some()
18121 {
18122 self.start_inline_blame_timer(window, cx);
18123 } else {
18124 self.show_git_blame_inline = true
18125 }
18126 }
18127
18128 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18129 self.blame.as_ref()
18130 }
18131
18132 pub fn show_git_blame_gutter(&self) -> bool {
18133 self.show_git_blame_gutter
18134 }
18135
18136 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18137 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18138 }
18139
18140 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18141 self.show_git_blame_inline
18142 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18143 && !self.newest_selection_head_on_empty_line(cx)
18144 && self.has_blame_entries(cx)
18145 }
18146
18147 fn has_blame_entries(&self, cx: &App) -> bool {
18148 self.blame()
18149 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18150 }
18151
18152 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18153 let cursor_anchor = self.selections.newest_anchor().head();
18154
18155 let snapshot = self.buffer.read(cx).snapshot(cx);
18156 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18157
18158 snapshot.line_len(buffer_row) == 0
18159 }
18160
18161 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18162 let buffer_and_selection = maybe!({
18163 let selection = self.selections.newest::<Point>(cx);
18164 let selection_range = selection.range();
18165
18166 let multi_buffer = self.buffer().read(cx);
18167 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18168 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18169
18170 let (buffer, range, _) = if selection.reversed {
18171 buffer_ranges.first()
18172 } else {
18173 buffer_ranges.last()
18174 }?;
18175
18176 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18177 ..text::ToPoint::to_point(&range.end, &buffer).row;
18178 Some((
18179 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18180 selection,
18181 ))
18182 });
18183
18184 let Some((buffer, selection)) = buffer_and_selection else {
18185 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18186 };
18187
18188 let Some(project) = self.project.as_ref() else {
18189 return Task::ready(Err(anyhow!("editor does not have project")));
18190 };
18191
18192 project.update(cx, |project, cx| {
18193 project.get_permalink_to_line(&buffer, selection, cx)
18194 })
18195 }
18196
18197 pub fn copy_permalink_to_line(
18198 &mut self,
18199 _: &CopyPermalinkToLine,
18200 window: &mut Window,
18201 cx: &mut Context<Self>,
18202 ) {
18203 let permalink_task = self.get_permalink_to_line(cx);
18204 let workspace = self.workspace();
18205
18206 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18207 Ok(permalink) => {
18208 cx.update(|_, cx| {
18209 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18210 })
18211 .ok();
18212 }
18213 Err(err) => {
18214 let message = format!("Failed to copy permalink: {err}");
18215
18216 anyhow::Result::<()>::Err(err).log_err();
18217
18218 if let Some(workspace) = workspace {
18219 workspace
18220 .update_in(cx, |workspace, _, cx| {
18221 struct CopyPermalinkToLine;
18222
18223 workspace.show_toast(
18224 Toast::new(
18225 NotificationId::unique::<CopyPermalinkToLine>(),
18226 message,
18227 ),
18228 cx,
18229 )
18230 })
18231 .ok();
18232 }
18233 }
18234 })
18235 .detach();
18236 }
18237
18238 pub fn copy_file_location(
18239 &mut self,
18240 _: &CopyFileLocation,
18241 _: &mut Window,
18242 cx: &mut Context<Self>,
18243 ) {
18244 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18245 if let Some(file) = self.target_file(cx) {
18246 if let Some(path) = file.path().to_str() {
18247 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18248 }
18249 }
18250 }
18251
18252 pub fn open_permalink_to_line(
18253 &mut self,
18254 _: &OpenPermalinkToLine,
18255 window: &mut Window,
18256 cx: &mut Context<Self>,
18257 ) {
18258 let permalink_task = self.get_permalink_to_line(cx);
18259 let workspace = self.workspace();
18260
18261 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18262 Ok(permalink) => {
18263 cx.update(|_, cx| {
18264 cx.open_url(permalink.as_ref());
18265 })
18266 .ok();
18267 }
18268 Err(err) => {
18269 let message = format!("Failed to open permalink: {err}");
18270
18271 anyhow::Result::<()>::Err(err).log_err();
18272
18273 if let Some(workspace) = workspace {
18274 workspace
18275 .update(cx, |workspace, cx| {
18276 struct OpenPermalinkToLine;
18277
18278 workspace.show_toast(
18279 Toast::new(
18280 NotificationId::unique::<OpenPermalinkToLine>(),
18281 message,
18282 ),
18283 cx,
18284 )
18285 })
18286 .ok();
18287 }
18288 }
18289 })
18290 .detach();
18291 }
18292
18293 pub fn insert_uuid_v4(
18294 &mut self,
18295 _: &InsertUuidV4,
18296 window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) {
18299 self.insert_uuid(UuidVersion::V4, window, cx);
18300 }
18301
18302 pub fn insert_uuid_v7(
18303 &mut self,
18304 _: &InsertUuidV7,
18305 window: &mut Window,
18306 cx: &mut Context<Self>,
18307 ) {
18308 self.insert_uuid(UuidVersion::V7, window, cx);
18309 }
18310
18311 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18312 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18313 self.transact(window, cx, |this, window, cx| {
18314 let edits = this
18315 .selections
18316 .all::<Point>(cx)
18317 .into_iter()
18318 .map(|selection| {
18319 let uuid = match version {
18320 UuidVersion::V4 => uuid::Uuid::new_v4(),
18321 UuidVersion::V7 => uuid::Uuid::now_v7(),
18322 };
18323
18324 (selection.range(), uuid.to_string())
18325 });
18326 this.edit(edits, cx);
18327 this.refresh_inline_completion(true, false, window, cx);
18328 });
18329 }
18330
18331 pub fn open_selections_in_multibuffer(
18332 &mut self,
18333 _: &OpenSelectionsInMultibuffer,
18334 window: &mut Window,
18335 cx: &mut Context<Self>,
18336 ) {
18337 let multibuffer = self.buffer.read(cx);
18338
18339 let Some(buffer) = multibuffer.as_singleton() else {
18340 return;
18341 };
18342
18343 let Some(workspace) = self.workspace() else {
18344 return;
18345 };
18346
18347 let title = multibuffer.title(cx).to_string();
18348
18349 let locations = self
18350 .selections
18351 .all_anchors(cx)
18352 .into_iter()
18353 .map(|selection| Location {
18354 buffer: buffer.clone(),
18355 range: selection.start.text_anchor..selection.end.text_anchor,
18356 })
18357 .collect::<Vec<_>>();
18358
18359 cx.spawn_in(window, async move |_, cx| {
18360 workspace.update_in(cx, |workspace, window, cx| {
18361 Self::open_locations_in_multibuffer(
18362 workspace,
18363 locations,
18364 format!("Selections for '{title}'"),
18365 false,
18366 MultibufferSelectionMode::All,
18367 window,
18368 cx,
18369 );
18370 })
18371 })
18372 .detach();
18373 }
18374
18375 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18376 /// last highlight added will be used.
18377 ///
18378 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18379 pub fn highlight_rows<T: 'static>(
18380 &mut self,
18381 range: Range<Anchor>,
18382 color: Hsla,
18383 options: RowHighlightOptions,
18384 cx: &mut Context<Self>,
18385 ) {
18386 let snapshot = self.buffer().read(cx).snapshot(cx);
18387 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18388 let ix = row_highlights.binary_search_by(|highlight| {
18389 Ordering::Equal
18390 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18391 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18392 });
18393
18394 if let Err(mut ix) = ix {
18395 let index = post_inc(&mut self.highlight_order);
18396
18397 // If this range intersects with the preceding highlight, then merge it with
18398 // the preceding highlight. Otherwise insert a new highlight.
18399 let mut merged = false;
18400 if ix > 0 {
18401 let prev_highlight = &mut row_highlights[ix - 1];
18402 if prev_highlight
18403 .range
18404 .end
18405 .cmp(&range.start, &snapshot)
18406 .is_ge()
18407 {
18408 ix -= 1;
18409 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18410 prev_highlight.range.end = range.end;
18411 }
18412 merged = true;
18413 prev_highlight.index = index;
18414 prev_highlight.color = color;
18415 prev_highlight.options = options;
18416 }
18417 }
18418
18419 if !merged {
18420 row_highlights.insert(
18421 ix,
18422 RowHighlight {
18423 range: range.clone(),
18424 index,
18425 color,
18426 options,
18427 type_id: TypeId::of::<T>(),
18428 },
18429 );
18430 }
18431
18432 // If any of the following highlights intersect with this one, merge them.
18433 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18434 let highlight = &row_highlights[ix];
18435 if next_highlight
18436 .range
18437 .start
18438 .cmp(&highlight.range.end, &snapshot)
18439 .is_le()
18440 {
18441 if next_highlight
18442 .range
18443 .end
18444 .cmp(&highlight.range.end, &snapshot)
18445 .is_gt()
18446 {
18447 row_highlights[ix].range.end = next_highlight.range.end;
18448 }
18449 row_highlights.remove(ix + 1);
18450 } else {
18451 break;
18452 }
18453 }
18454 }
18455 }
18456
18457 /// Remove any highlighted row ranges of the given type that intersect the
18458 /// given ranges.
18459 pub fn remove_highlighted_rows<T: 'static>(
18460 &mut self,
18461 ranges_to_remove: Vec<Range<Anchor>>,
18462 cx: &mut Context<Self>,
18463 ) {
18464 let snapshot = self.buffer().read(cx).snapshot(cx);
18465 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18466 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18467 row_highlights.retain(|highlight| {
18468 while let Some(range_to_remove) = ranges_to_remove.peek() {
18469 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18470 Ordering::Less | Ordering::Equal => {
18471 ranges_to_remove.next();
18472 }
18473 Ordering::Greater => {
18474 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18475 Ordering::Less | Ordering::Equal => {
18476 return false;
18477 }
18478 Ordering::Greater => break,
18479 }
18480 }
18481 }
18482 }
18483
18484 true
18485 })
18486 }
18487
18488 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18489 pub fn clear_row_highlights<T: 'static>(&mut self) {
18490 self.highlighted_rows.remove(&TypeId::of::<T>());
18491 }
18492
18493 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18494 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18495 self.highlighted_rows
18496 .get(&TypeId::of::<T>())
18497 .map_or(&[] as &[_], |vec| vec.as_slice())
18498 .iter()
18499 .map(|highlight| (highlight.range.clone(), highlight.color))
18500 }
18501
18502 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18503 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18504 /// Allows to ignore certain kinds of highlights.
18505 pub fn highlighted_display_rows(
18506 &self,
18507 window: &mut Window,
18508 cx: &mut App,
18509 ) -> BTreeMap<DisplayRow, LineHighlight> {
18510 let snapshot = self.snapshot(window, cx);
18511 let mut used_highlight_orders = HashMap::default();
18512 self.highlighted_rows
18513 .iter()
18514 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18515 .fold(
18516 BTreeMap::<DisplayRow, LineHighlight>::new(),
18517 |mut unique_rows, highlight| {
18518 let start = highlight.range.start.to_display_point(&snapshot);
18519 let end = highlight.range.end.to_display_point(&snapshot);
18520 let start_row = start.row().0;
18521 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18522 && end.column() == 0
18523 {
18524 end.row().0.saturating_sub(1)
18525 } else {
18526 end.row().0
18527 };
18528 for row in start_row..=end_row {
18529 let used_index =
18530 used_highlight_orders.entry(row).or_insert(highlight.index);
18531 if highlight.index >= *used_index {
18532 *used_index = highlight.index;
18533 unique_rows.insert(
18534 DisplayRow(row),
18535 LineHighlight {
18536 include_gutter: highlight.options.include_gutter,
18537 border: None,
18538 background: highlight.color.into(),
18539 type_id: Some(highlight.type_id),
18540 },
18541 );
18542 }
18543 }
18544 unique_rows
18545 },
18546 )
18547 }
18548
18549 pub fn highlighted_display_row_for_autoscroll(
18550 &self,
18551 snapshot: &DisplaySnapshot,
18552 ) -> Option<DisplayRow> {
18553 self.highlighted_rows
18554 .values()
18555 .flat_map(|highlighted_rows| highlighted_rows.iter())
18556 .filter_map(|highlight| {
18557 if highlight.options.autoscroll {
18558 Some(highlight.range.start.to_display_point(snapshot).row())
18559 } else {
18560 None
18561 }
18562 })
18563 .min()
18564 }
18565
18566 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18567 self.highlight_background::<SearchWithinRange>(
18568 ranges,
18569 |colors| colors.colors().editor_document_highlight_read_background,
18570 cx,
18571 )
18572 }
18573
18574 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18575 self.breadcrumb_header = Some(new_header);
18576 }
18577
18578 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18579 self.clear_background_highlights::<SearchWithinRange>(cx);
18580 }
18581
18582 pub fn highlight_background<T: 'static>(
18583 &mut self,
18584 ranges: &[Range<Anchor>],
18585 color_fetcher: fn(&Theme) -> Hsla,
18586 cx: &mut Context<Self>,
18587 ) {
18588 self.background_highlights.insert(
18589 HighlightKey::Type(TypeId::of::<T>()),
18590 (color_fetcher, Arc::from(ranges)),
18591 );
18592 self.scrollbar_marker_state.dirty = true;
18593 cx.notify();
18594 }
18595
18596 pub fn highlight_background_key<T: 'static>(
18597 &mut self,
18598 key: usize,
18599 ranges: &[Range<Anchor>],
18600 color_fetcher: fn(&Theme) -> Hsla,
18601 cx: &mut Context<Self>,
18602 ) {
18603 self.background_highlights.insert(
18604 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18605 (color_fetcher, Arc::from(ranges)),
18606 );
18607 self.scrollbar_marker_state.dirty = true;
18608 cx.notify();
18609 }
18610
18611 pub fn clear_background_highlights<T: 'static>(
18612 &mut self,
18613 cx: &mut Context<Self>,
18614 ) -> Option<BackgroundHighlight> {
18615 let text_highlights = self
18616 .background_highlights
18617 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18618 if !text_highlights.1.is_empty() {
18619 self.scrollbar_marker_state.dirty = true;
18620 cx.notify();
18621 }
18622 Some(text_highlights)
18623 }
18624
18625 pub fn highlight_gutter<T: 'static>(
18626 &mut self,
18627 ranges: impl Into<Vec<Range<Anchor>>>,
18628 color_fetcher: fn(&App) -> Hsla,
18629 cx: &mut Context<Self>,
18630 ) {
18631 self.gutter_highlights
18632 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18633 cx.notify();
18634 }
18635
18636 pub fn clear_gutter_highlights<T: 'static>(
18637 &mut self,
18638 cx: &mut Context<Self>,
18639 ) -> Option<GutterHighlight> {
18640 cx.notify();
18641 self.gutter_highlights.remove(&TypeId::of::<T>())
18642 }
18643
18644 pub fn insert_gutter_highlight<T: 'static>(
18645 &mut self,
18646 range: Range<Anchor>,
18647 color_fetcher: fn(&App) -> Hsla,
18648 cx: &mut Context<Self>,
18649 ) {
18650 let snapshot = self.buffer().read(cx).snapshot(cx);
18651 let mut highlights = self
18652 .gutter_highlights
18653 .remove(&TypeId::of::<T>())
18654 .map(|(_, highlights)| highlights)
18655 .unwrap_or_default();
18656 let ix = highlights.binary_search_by(|highlight| {
18657 Ordering::Equal
18658 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18659 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18660 });
18661 if let Err(ix) = ix {
18662 highlights.insert(ix, range);
18663 }
18664 self.gutter_highlights
18665 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18666 }
18667
18668 pub fn remove_gutter_highlights<T: 'static>(
18669 &mut self,
18670 ranges_to_remove: Vec<Range<Anchor>>,
18671 cx: &mut Context<Self>,
18672 ) {
18673 let snapshot = self.buffer().read(cx).snapshot(cx);
18674 let Some((color_fetcher, mut gutter_highlights)) =
18675 self.gutter_highlights.remove(&TypeId::of::<T>())
18676 else {
18677 return;
18678 };
18679 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18680 gutter_highlights.retain(|highlight| {
18681 while let Some(range_to_remove) = ranges_to_remove.peek() {
18682 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18683 Ordering::Less | Ordering::Equal => {
18684 ranges_to_remove.next();
18685 }
18686 Ordering::Greater => {
18687 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18688 Ordering::Less | Ordering::Equal => {
18689 return false;
18690 }
18691 Ordering::Greater => break,
18692 }
18693 }
18694 }
18695 }
18696
18697 true
18698 });
18699 self.gutter_highlights
18700 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18701 }
18702
18703 #[cfg(feature = "test-support")]
18704 pub fn all_text_highlights(
18705 &self,
18706 window: &mut Window,
18707 cx: &mut Context<Self>,
18708 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
18709 let snapshot = self.snapshot(window, cx);
18710 self.display_map.update(cx, |display_map, _| {
18711 display_map
18712 .all_text_highlights()
18713 .map(|highlight| {
18714 let (style, ranges) = highlight.as_ref();
18715 (
18716 *style,
18717 ranges
18718 .iter()
18719 .map(|range| range.clone().to_display_points(&snapshot))
18720 .collect(),
18721 )
18722 })
18723 .collect()
18724 })
18725 }
18726
18727 #[cfg(feature = "test-support")]
18728 pub fn all_text_background_highlights(
18729 &self,
18730 window: &mut Window,
18731 cx: &mut Context<Self>,
18732 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18733 let snapshot = self.snapshot(window, cx);
18734 let buffer = &snapshot.buffer_snapshot;
18735 let start = buffer.anchor_before(0);
18736 let end = buffer.anchor_after(buffer.len());
18737 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
18738 }
18739
18740 #[cfg(feature = "test-support")]
18741 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18742 let snapshot = self.buffer().read(cx).snapshot(cx);
18743
18744 let highlights = self
18745 .background_highlights
18746 .get(&HighlightKey::Type(TypeId::of::<
18747 items::BufferSearchHighlights,
18748 >()));
18749
18750 if let Some((_color, ranges)) = highlights {
18751 ranges
18752 .iter()
18753 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18754 .collect_vec()
18755 } else {
18756 vec![]
18757 }
18758 }
18759
18760 fn document_highlights_for_position<'a>(
18761 &'a self,
18762 position: Anchor,
18763 buffer: &'a MultiBufferSnapshot,
18764 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18765 let read_highlights = self
18766 .background_highlights
18767 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
18768 .map(|h| &h.1);
18769 let write_highlights = self
18770 .background_highlights
18771 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
18772 .map(|h| &h.1);
18773 let left_position = position.bias_left(buffer);
18774 let right_position = position.bias_right(buffer);
18775 read_highlights
18776 .into_iter()
18777 .chain(write_highlights)
18778 .flat_map(move |ranges| {
18779 let start_ix = match ranges.binary_search_by(|probe| {
18780 let cmp = probe.end.cmp(&left_position, buffer);
18781 if cmp.is_ge() {
18782 Ordering::Greater
18783 } else {
18784 Ordering::Less
18785 }
18786 }) {
18787 Ok(i) | Err(i) => i,
18788 };
18789
18790 ranges[start_ix..]
18791 .iter()
18792 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18793 })
18794 }
18795
18796 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18797 self.background_highlights
18798 .get(&HighlightKey::Type(TypeId::of::<T>()))
18799 .map_or(false, |(_, highlights)| !highlights.is_empty())
18800 }
18801
18802 pub fn background_highlights_in_range(
18803 &self,
18804 search_range: Range<Anchor>,
18805 display_snapshot: &DisplaySnapshot,
18806 theme: &Theme,
18807 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18808 let mut results = Vec::new();
18809 for (color_fetcher, ranges) in self.background_highlights.values() {
18810 let color = color_fetcher(theme);
18811 let start_ix = match ranges.binary_search_by(|probe| {
18812 let cmp = probe
18813 .end
18814 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18815 if cmp.is_gt() {
18816 Ordering::Greater
18817 } else {
18818 Ordering::Less
18819 }
18820 }) {
18821 Ok(i) | Err(i) => i,
18822 };
18823 for range in &ranges[start_ix..] {
18824 if range
18825 .start
18826 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18827 .is_ge()
18828 {
18829 break;
18830 }
18831
18832 let start = range.start.to_display_point(display_snapshot);
18833 let end = range.end.to_display_point(display_snapshot);
18834 results.push((start..end, color))
18835 }
18836 }
18837 results
18838 }
18839
18840 pub fn background_highlight_row_ranges<T: 'static>(
18841 &self,
18842 search_range: Range<Anchor>,
18843 display_snapshot: &DisplaySnapshot,
18844 count: usize,
18845 ) -> Vec<RangeInclusive<DisplayPoint>> {
18846 let mut results = Vec::new();
18847 let Some((_, ranges)) = self
18848 .background_highlights
18849 .get(&HighlightKey::Type(TypeId::of::<T>()))
18850 else {
18851 return vec![];
18852 };
18853
18854 let start_ix = match ranges.binary_search_by(|probe| {
18855 let cmp = probe
18856 .end
18857 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18858 if cmp.is_gt() {
18859 Ordering::Greater
18860 } else {
18861 Ordering::Less
18862 }
18863 }) {
18864 Ok(i) | Err(i) => i,
18865 };
18866 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18867 if let (Some(start_display), Some(end_display)) = (start, end) {
18868 results.push(
18869 start_display.to_display_point(display_snapshot)
18870 ..=end_display.to_display_point(display_snapshot),
18871 );
18872 }
18873 };
18874 let mut start_row: Option<Point> = None;
18875 let mut end_row: Option<Point> = None;
18876 if ranges.len() > count {
18877 return Vec::new();
18878 }
18879 for range in &ranges[start_ix..] {
18880 if range
18881 .start
18882 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18883 .is_ge()
18884 {
18885 break;
18886 }
18887 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18888 if let Some(current_row) = &end_row {
18889 if end.row == current_row.row {
18890 continue;
18891 }
18892 }
18893 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18894 if start_row.is_none() {
18895 assert_eq!(end_row, None);
18896 start_row = Some(start);
18897 end_row = Some(end);
18898 continue;
18899 }
18900 if let Some(current_end) = end_row.as_mut() {
18901 if start.row > current_end.row + 1 {
18902 push_region(start_row, end_row);
18903 start_row = Some(start);
18904 end_row = Some(end);
18905 } else {
18906 // Merge two hunks.
18907 *current_end = end;
18908 }
18909 } else {
18910 unreachable!();
18911 }
18912 }
18913 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18914 push_region(start_row, end_row);
18915 results
18916 }
18917
18918 pub fn gutter_highlights_in_range(
18919 &self,
18920 search_range: Range<Anchor>,
18921 display_snapshot: &DisplaySnapshot,
18922 cx: &App,
18923 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18924 let mut results = Vec::new();
18925 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18926 let color = color_fetcher(cx);
18927 let start_ix = match ranges.binary_search_by(|probe| {
18928 let cmp = probe
18929 .end
18930 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18931 if cmp.is_gt() {
18932 Ordering::Greater
18933 } else {
18934 Ordering::Less
18935 }
18936 }) {
18937 Ok(i) | Err(i) => i,
18938 };
18939 for range in &ranges[start_ix..] {
18940 if range
18941 .start
18942 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18943 .is_ge()
18944 {
18945 break;
18946 }
18947
18948 let start = range.start.to_display_point(display_snapshot);
18949 let end = range.end.to_display_point(display_snapshot);
18950 results.push((start..end, color))
18951 }
18952 }
18953 results
18954 }
18955
18956 /// Get the text ranges corresponding to the redaction query
18957 pub fn redacted_ranges(
18958 &self,
18959 search_range: Range<Anchor>,
18960 display_snapshot: &DisplaySnapshot,
18961 cx: &App,
18962 ) -> Vec<Range<DisplayPoint>> {
18963 display_snapshot
18964 .buffer_snapshot
18965 .redacted_ranges(search_range, |file| {
18966 if let Some(file) = file {
18967 file.is_private()
18968 && EditorSettings::get(
18969 Some(SettingsLocation {
18970 worktree_id: file.worktree_id(cx),
18971 path: file.path().as_ref(),
18972 }),
18973 cx,
18974 )
18975 .redact_private_values
18976 } else {
18977 false
18978 }
18979 })
18980 .map(|range| {
18981 range.start.to_display_point(display_snapshot)
18982 ..range.end.to_display_point(display_snapshot)
18983 })
18984 .collect()
18985 }
18986
18987 pub fn highlight_text_key<T: 'static>(
18988 &mut self,
18989 key: usize,
18990 ranges: Vec<Range<Anchor>>,
18991 style: HighlightStyle,
18992 cx: &mut Context<Self>,
18993 ) {
18994 self.display_map.update(cx, |map, _| {
18995 map.highlight_text(
18996 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18997 ranges,
18998 style,
18999 );
19000 });
19001 cx.notify();
19002 }
19003
19004 pub fn highlight_text<T: 'static>(
19005 &mut self,
19006 ranges: Vec<Range<Anchor>>,
19007 style: HighlightStyle,
19008 cx: &mut Context<Self>,
19009 ) {
19010 self.display_map.update(cx, |map, _| {
19011 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19012 });
19013 cx.notify();
19014 }
19015
19016 pub(crate) fn highlight_inlays<T: 'static>(
19017 &mut self,
19018 highlights: Vec<InlayHighlight>,
19019 style: HighlightStyle,
19020 cx: &mut Context<Self>,
19021 ) {
19022 self.display_map.update(cx, |map, _| {
19023 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19024 });
19025 cx.notify();
19026 }
19027
19028 pub fn text_highlights<'a, T: 'static>(
19029 &'a self,
19030 cx: &'a App,
19031 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19032 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19033 }
19034
19035 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19036 let cleared = self
19037 .display_map
19038 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19039 if cleared {
19040 cx.notify();
19041 }
19042 }
19043
19044 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19045 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19046 && self.focus_handle.is_focused(window)
19047 }
19048
19049 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19050 self.show_cursor_when_unfocused = is_enabled;
19051 cx.notify();
19052 }
19053
19054 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19055 cx.notify();
19056 }
19057
19058 fn on_debug_session_event(
19059 &mut self,
19060 _session: Entity<Session>,
19061 event: &SessionEvent,
19062 cx: &mut Context<Self>,
19063 ) {
19064 match event {
19065 SessionEvent::InvalidateInlineValue => {
19066 self.refresh_inline_values(cx);
19067 }
19068 _ => {}
19069 }
19070 }
19071
19072 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19073 let Some(project) = self.project.clone() else {
19074 return;
19075 };
19076
19077 if !self.inline_value_cache.enabled {
19078 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19079 self.splice_inlays(&inlays, Vec::new(), cx);
19080 return;
19081 }
19082
19083 let current_execution_position = self
19084 .highlighted_rows
19085 .get(&TypeId::of::<ActiveDebugLine>())
19086 .and_then(|lines| lines.last().map(|line| line.range.start));
19087
19088 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19089 let inline_values = editor
19090 .update(cx, |editor, cx| {
19091 let Some(current_execution_position) = current_execution_position else {
19092 return Some(Task::ready(Ok(Vec::new())));
19093 };
19094
19095 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19096 let snapshot = buffer.snapshot(cx);
19097
19098 let excerpt = snapshot.excerpt_containing(
19099 current_execution_position..current_execution_position,
19100 )?;
19101
19102 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19103 })?;
19104
19105 let range =
19106 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19107
19108 project.inline_values(buffer, range, cx)
19109 })
19110 .ok()
19111 .flatten()?
19112 .await
19113 .context("refreshing debugger inlays")
19114 .log_err()?;
19115
19116 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19117
19118 for (buffer_id, inline_value) in inline_values
19119 .into_iter()
19120 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19121 {
19122 buffer_inline_values
19123 .entry(buffer_id)
19124 .or_default()
19125 .push(inline_value);
19126 }
19127
19128 editor
19129 .update(cx, |editor, cx| {
19130 let snapshot = editor.buffer.read(cx).snapshot(cx);
19131 let mut new_inlays = Vec::default();
19132
19133 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19134 let buffer_id = buffer_snapshot.remote_id();
19135 buffer_inline_values
19136 .get(&buffer_id)
19137 .into_iter()
19138 .flatten()
19139 .for_each(|hint| {
19140 let inlay = Inlay::debugger(
19141 post_inc(&mut editor.next_inlay_id),
19142 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19143 hint.text(),
19144 );
19145
19146 new_inlays.push(inlay);
19147 });
19148 }
19149
19150 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19151 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19152
19153 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19154 })
19155 .ok()?;
19156 Some(())
19157 });
19158 }
19159
19160 fn on_buffer_event(
19161 &mut self,
19162 multibuffer: &Entity<MultiBuffer>,
19163 event: &multi_buffer::Event,
19164 window: &mut Window,
19165 cx: &mut Context<Self>,
19166 ) {
19167 match event {
19168 multi_buffer::Event::Edited {
19169 singleton_buffer_edited,
19170 edited_buffer,
19171 } => {
19172 self.scrollbar_marker_state.dirty = true;
19173 self.active_indent_guides_state.dirty = true;
19174 self.refresh_active_diagnostics(cx);
19175 self.refresh_code_actions(window, cx);
19176 self.refresh_selected_text_highlights(true, window, cx);
19177 refresh_matching_bracket_highlights(self, window, cx);
19178 if self.has_active_inline_completion() {
19179 self.update_visible_inline_completion(window, cx);
19180 }
19181 if let Some(project) = self.project.as_ref() {
19182 if let Some(edited_buffer) = edited_buffer {
19183 project.update(cx, |project, cx| {
19184 self.registered_buffers
19185 .entry(edited_buffer.read(cx).remote_id())
19186 .or_insert_with(|| {
19187 project
19188 .register_buffer_with_language_servers(&edited_buffer, cx)
19189 });
19190 });
19191 }
19192 }
19193 cx.emit(EditorEvent::BufferEdited);
19194 cx.emit(SearchEvent::MatchesInvalidated);
19195
19196 if let Some(buffer) = edited_buffer {
19197 self.update_lsp_data(true, None, Some(buffer.read(cx).remote_id()), window, cx);
19198 }
19199
19200 if *singleton_buffer_edited {
19201 if let Some(buffer) = edited_buffer {
19202 if buffer.read(cx).file().is_none() {
19203 cx.emit(EditorEvent::TitleChanged);
19204 }
19205 }
19206 if let Some(project) = &self.project {
19207 #[allow(clippy::mutable_key_type)]
19208 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19209 multibuffer
19210 .all_buffers()
19211 .into_iter()
19212 .filter_map(|buffer| {
19213 buffer.update(cx, |buffer, cx| {
19214 let language = buffer.language()?;
19215 let should_discard = project.update(cx, |project, cx| {
19216 project.is_local()
19217 && !project.has_language_servers_for(buffer, cx)
19218 });
19219 should_discard.not().then_some(language.clone())
19220 })
19221 })
19222 .collect::<HashSet<_>>()
19223 });
19224 if !languages_affected.is_empty() {
19225 self.refresh_inlay_hints(
19226 InlayHintRefreshReason::BufferEdited(languages_affected),
19227 cx,
19228 );
19229 }
19230 }
19231 }
19232
19233 let Some(project) = &self.project else { return };
19234 let (telemetry, is_via_ssh) = {
19235 let project = project.read(cx);
19236 let telemetry = project.client().telemetry().clone();
19237 let is_via_ssh = project.is_via_ssh();
19238 (telemetry, is_via_ssh)
19239 };
19240 refresh_linked_ranges(self, window, cx);
19241 telemetry.log_edit_event("editor", is_via_ssh);
19242 }
19243 multi_buffer::Event::ExcerptsAdded {
19244 buffer,
19245 predecessor,
19246 excerpts,
19247 } => {
19248 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19249 let buffer_id = buffer.read(cx).remote_id();
19250 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19251 if let Some(project) = &self.project {
19252 update_uncommitted_diff_for_buffer(
19253 cx.entity(),
19254 project,
19255 [buffer.clone()],
19256 self.buffer.clone(),
19257 cx,
19258 )
19259 .detach();
19260 }
19261 }
19262 self.update_lsp_data(false, None, Some(buffer_id), window, cx);
19263 cx.emit(EditorEvent::ExcerptsAdded {
19264 buffer: buffer.clone(),
19265 predecessor: *predecessor,
19266 excerpts: excerpts.clone(),
19267 });
19268 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19269 }
19270 multi_buffer::Event::ExcerptsRemoved {
19271 ids,
19272 removed_buffer_ids,
19273 } => {
19274 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19275 let buffer = self.buffer.read(cx);
19276 self.registered_buffers
19277 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19278 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19279 cx.emit(EditorEvent::ExcerptsRemoved {
19280 ids: ids.clone(),
19281 removed_buffer_ids: removed_buffer_ids.clone(),
19282 });
19283 }
19284 multi_buffer::Event::ExcerptsEdited {
19285 excerpt_ids,
19286 buffer_ids,
19287 } => {
19288 self.display_map.update(cx, |map, cx| {
19289 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19290 });
19291 cx.emit(EditorEvent::ExcerptsEdited {
19292 ids: excerpt_ids.clone(),
19293 });
19294 }
19295 multi_buffer::Event::ExcerptsExpanded { ids } => {
19296 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19297 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19298 }
19299 multi_buffer::Event::Reparsed(buffer_id) => {
19300 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19301 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19302
19303 cx.emit(EditorEvent::Reparsed(*buffer_id));
19304 }
19305 multi_buffer::Event::DiffHunksToggled => {
19306 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19307 }
19308 multi_buffer::Event::LanguageChanged(buffer_id) => {
19309 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19310 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19311 cx.emit(EditorEvent::Reparsed(*buffer_id));
19312 cx.notify();
19313 }
19314 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19315 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19316 multi_buffer::Event::FileHandleChanged
19317 | multi_buffer::Event::Reloaded
19318 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19319 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19320 multi_buffer::Event::DiagnosticsUpdated => {
19321 self.update_diagnostics_state(window, cx);
19322 }
19323 _ => {}
19324 };
19325 }
19326
19327 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19328 self.refresh_active_diagnostics(cx);
19329 self.refresh_inline_diagnostics(true, window, cx);
19330 self.scrollbar_marker_state.dirty = true;
19331 cx.notify();
19332 }
19333
19334 pub fn start_temporary_diff_override(&mut self) {
19335 self.load_diff_task.take();
19336 self.temporary_diff_override = true;
19337 }
19338
19339 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19340 self.temporary_diff_override = false;
19341 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19342 self.buffer.update(cx, |buffer, cx| {
19343 buffer.set_all_diff_hunks_collapsed(cx);
19344 });
19345
19346 if let Some(project) = self.project.clone() {
19347 self.load_diff_task = Some(
19348 update_uncommitted_diff_for_buffer(
19349 cx.entity(),
19350 &project,
19351 self.buffer.read(cx).all_buffers(),
19352 self.buffer.clone(),
19353 cx,
19354 )
19355 .shared(),
19356 );
19357 }
19358 }
19359
19360 fn on_display_map_changed(
19361 &mut self,
19362 _: Entity<DisplayMap>,
19363 _: &mut Window,
19364 cx: &mut Context<Self>,
19365 ) {
19366 cx.notify();
19367 }
19368
19369 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19370 let new_severity = if self.diagnostics_enabled() {
19371 EditorSettings::get_global(cx)
19372 .diagnostics_max_severity
19373 .unwrap_or(DiagnosticSeverity::Hint)
19374 } else {
19375 DiagnosticSeverity::Off
19376 };
19377 self.set_max_diagnostics_severity(new_severity, cx);
19378 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19379 self.update_edit_prediction_settings(cx);
19380 self.refresh_inline_completion(true, false, window, cx);
19381 self.refresh_inlay_hints(
19382 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19383 self.selections.newest_anchor().head(),
19384 &self.buffer.read(cx).snapshot(cx),
19385 cx,
19386 )),
19387 cx,
19388 );
19389
19390 let old_cursor_shape = self.cursor_shape;
19391
19392 {
19393 let editor_settings = EditorSettings::get_global(cx);
19394 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19395 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19396 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19397 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19398 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19399 }
19400
19401 if old_cursor_shape != self.cursor_shape {
19402 cx.emit(EditorEvent::CursorShapeChanged);
19403 }
19404
19405 let project_settings = ProjectSettings::get_global(cx);
19406 self.serialize_dirty_buffers =
19407 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19408
19409 if self.mode.is_full() {
19410 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19411 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19412 if self.show_inline_diagnostics != show_inline_diagnostics {
19413 self.show_inline_diagnostics = show_inline_diagnostics;
19414 self.refresh_inline_diagnostics(false, window, cx);
19415 }
19416
19417 if self.git_blame_inline_enabled != inline_blame_enabled {
19418 self.toggle_git_blame_inline_internal(false, window, cx);
19419 }
19420
19421 let minimap_settings = EditorSettings::get_global(cx).minimap;
19422 if self.minimap_visibility != MinimapVisibility::Disabled {
19423 if self.minimap_visibility.settings_visibility()
19424 != minimap_settings.minimap_enabled()
19425 {
19426 self.set_minimap_visibility(
19427 MinimapVisibility::for_mode(self.mode(), cx),
19428 window,
19429 cx,
19430 );
19431 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19432 minimap_entity.update(cx, |minimap_editor, cx| {
19433 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19434 })
19435 }
19436 }
19437 }
19438
19439 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19440 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19441 }) {
19442 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19443 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19444 }
19445 self.refresh_colors(true, None, None, window, cx);
19446 }
19447
19448 cx.notify();
19449 }
19450
19451 pub fn set_searchable(&mut self, searchable: bool) {
19452 self.searchable = searchable;
19453 }
19454
19455 pub fn searchable(&self) -> bool {
19456 self.searchable
19457 }
19458
19459 fn open_proposed_changes_editor(
19460 &mut self,
19461 _: &OpenProposedChangesEditor,
19462 window: &mut Window,
19463 cx: &mut Context<Self>,
19464 ) {
19465 let Some(workspace) = self.workspace() else {
19466 cx.propagate();
19467 return;
19468 };
19469
19470 let selections = self.selections.all::<usize>(cx);
19471 let multi_buffer = self.buffer.read(cx);
19472 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19473 let mut new_selections_by_buffer = HashMap::default();
19474 for selection in selections {
19475 for (buffer, range, _) in
19476 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19477 {
19478 let mut range = range.to_point(buffer);
19479 range.start.column = 0;
19480 range.end.column = buffer.line_len(range.end.row);
19481 new_selections_by_buffer
19482 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19483 .or_insert(Vec::new())
19484 .push(range)
19485 }
19486 }
19487
19488 let proposed_changes_buffers = new_selections_by_buffer
19489 .into_iter()
19490 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19491 .collect::<Vec<_>>();
19492 let proposed_changes_editor = cx.new(|cx| {
19493 ProposedChangesEditor::new(
19494 "Proposed changes",
19495 proposed_changes_buffers,
19496 self.project.clone(),
19497 window,
19498 cx,
19499 )
19500 });
19501
19502 window.defer(cx, move |window, cx| {
19503 workspace.update(cx, |workspace, cx| {
19504 workspace.active_pane().update(cx, |pane, cx| {
19505 pane.add_item(
19506 Box::new(proposed_changes_editor),
19507 true,
19508 true,
19509 None,
19510 window,
19511 cx,
19512 );
19513 });
19514 });
19515 });
19516 }
19517
19518 pub fn open_excerpts_in_split(
19519 &mut self,
19520 _: &OpenExcerptsSplit,
19521 window: &mut Window,
19522 cx: &mut Context<Self>,
19523 ) {
19524 self.open_excerpts_common(None, true, window, cx)
19525 }
19526
19527 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19528 self.open_excerpts_common(None, false, window, cx)
19529 }
19530
19531 fn open_excerpts_common(
19532 &mut self,
19533 jump_data: Option<JumpData>,
19534 split: bool,
19535 window: &mut Window,
19536 cx: &mut Context<Self>,
19537 ) {
19538 let Some(workspace) = self.workspace() else {
19539 cx.propagate();
19540 return;
19541 };
19542
19543 if self.buffer.read(cx).is_singleton() {
19544 cx.propagate();
19545 return;
19546 }
19547
19548 let mut new_selections_by_buffer = HashMap::default();
19549 match &jump_data {
19550 Some(JumpData::MultiBufferPoint {
19551 excerpt_id,
19552 position,
19553 anchor,
19554 line_offset_from_top,
19555 }) => {
19556 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19557 if let Some(buffer) = multi_buffer_snapshot
19558 .buffer_id_for_excerpt(*excerpt_id)
19559 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19560 {
19561 let buffer_snapshot = buffer.read(cx).snapshot();
19562 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19563 language::ToPoint::to_point(anchor, &buffer_snapshot)
19564 } else {
19565 buffer_snapshot.clip_point(*position, Bias::Left)
19566 };
19567 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19568 new_selections_by_buffer.insert(
19569 buffer,
19570 (
19571 vec![jump_to_offset..jump_to_offset],
19572 Some(*line_offset_from_top),
19573 ),
19574 );
19575 }
19576 }
19577 Some(JumpData::MultiBufferRow {
19578 row,
19579 line_offset_from_top,
19580 }) => {
19581 let point = MultiBufferPoint::new(row.0, 0);
19582 if let Some((buffer, buffer_point, _)) =
19583 self.buffer.read(cx).point_to_buffer_point(point, cx)
19584 {
19585 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19586 new_selections_by_buffer
19587 .entry(buffer)
19588 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19589 .0
19590 .push(buffer_offset..buffer_offset)
19591 }
19592 }
19593 None => {
19594 let selections = self.selections.all::<usize>(cx);
19595 let multi_buffer = self.buffer.read(cx);
19596 for selection in selections {
19597 for (snapshot, range, _, anchor) in multi_buffer
19598 .snapshot(cx)
19599 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19600 {
19601 if let Some(anchor) = anchor {
19602 // selection is in a deleted hunk
19603 let Some(buffer_id) = anchor.buffer_id else {
19604 continue;
19605 };
19606 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19607 continue;
19608 };
19609 let offset = text::ToOffset::to_offset(
19610 &anchor.text_anchor,
19611 &buffer_handle.read(cx).snapshot(),
19612 );
19613 let range = offset..offset;
19614 new_selections_by_buffer
19615 .entry(buffer_handle)
19616 .or_insert((Vec::new(), None))
19617 .0
19618 .push(range)
19619 } else {
19620 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19621 else {
19622 continue;
19623 };
19624 new_selections_by_buffer
19625 .entry(buffer_handle)
19626 .or_insert((Vec::new(), None))
19627 .0
19628 .push(range)
19629 }
19630 }
19631 }
19632 }
19633 }
19634
19635 new_selections_by_buffer
19636 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19637
19638 if new_selections_by_buffer.is_empty() {
19639 return;
19640 }
19641
19642 // We defer the pane interaction because we ourselves are a workspace item
19643 // and activating a new item causes the pane to call a method on us reentrantly,
19644 // which panics if we're on the stack.
19645 window.defer(cx, move |window, cx| {
19646 workspace.update(cx, |workspace, cx| {
19647 let pane = if split {
19648 workspace.adjacent_pane(window, cx)
19649 } else {
19650 workspace.active_pane().clone()
19651 };
19652
19653 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19654 let editor = buffer
19655 .read(cx)
19656 .file()
19657 .is_none()
19658 .then(|| {
19659 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19660 // so `workspace.open_project_item` will never find them, always opening a new editor.
19661 // Instead, we try to activate the existing editor in the pane first.
19662 let (editor, pane_item_index) =
19663 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19664 let editor = item.downcast::<Editor>()?;
19665 let singleton_buffer =
19666 editor.read(cx).buffer().read(cx).as_singleton()?;
19667 if singleton_buffer == buffer {
19668 Some((editor, i))
19669 } else {
19670 None
19671 }
19672 })?;
19673 pane.update(cx, |pane, cx| {
19674 pane.activate_item(pane_item_index, true, true, window, cx)
19675 });
19676 Some(editor)
19677 })
19678 .flatten()
19679 .unwrap_or_else(|| {
19680 workspace.open_project_item::<Self>(
19681 pane.clone(),
19682 buffer,
19683 true,
19684 true,
19685 window,
19686 cx,
19687 )
19688 });
19689
19690 editor.update(cx, |editor, cx| {
19691 let autoscroll = match scroll_offset {
19692 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19693 None => Autoscroll::newest(),
19694 };
19695 let nav_history = editor.nav_history.take();
19696 editor.change_selections(Some(autoscroll), window, cx, |s| {
19697 s.select_ranges(ranges);
19698 });
19699 editor.nav_history = nav_history;
19700 });
19701 }
19702 })
19703 });
19704 }
19705
19706 // For now, don't allow opening excerpts in buffers that aren't backed by
19707 // regular project files.
19708 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19709 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19710 }
19711
19712 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19713 let snapshot = self.buffer.read(cx).read(cx);
19714 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19715 Some(
19716 ranges
19717 .iter()
19718 .map(move |range| {
19719 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19720 })
19721 .collect(),
19722 )
19723 }
19724
19725 fn selection_replacement_ranges(
19726 &self,
19727 range: Range<OffsetUtf16>,
19728 cx: &mut App,
19729 ) -> Vec<Range<OffsetUtf16>> {
19730 let selections = self.selections.all::<OffsetUtf16>(cx);
19731 let newest_selection = selections
19732 .iter()
19733 .max_by_key(|selection| selection.id)
19734 .unwrap();
19735 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19736 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19737 let snapshot = self.buffer.read(cx).read(cx);
19738 selections
19739 .into_iter()
19740 .map(|mut selection| {
19741 selection.start.0 =
19742 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19743 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19744 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19745 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19746 })
19747 .collect()
19748 }
19749
19750 fn report_editor_event(
19751 &self,
19752 event_type: &'static str,
19753 file_extension: Option<String>,
19754 cx: &App,
19755 ) {
19756 if cfg!(any(test, feature = "test-support")) {
19757 return;
19758 }
19759
19760 let Some(project) = &self.project else { return };
19761
19762 // If None, we are in a file without an extension
19763 let file = self
19764 .buffer
19765 .read(cx)
19766 .as_singleton()
19767 .and_then(|b| b.read(cx).file());
19768 let file_extension = file_extension.or(file
19769 .as_ref()
19770 .and_then(|file| Path::new(file.file_name(cx)).extension())
19771 .and_then(|e| e.to_str())
19772 .map(|a| a.to_string()));
19773
19774 let vim_mode = vim_enabled(cx);
19775
19776 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19777 let copilot_enabled = edit_predictions_provider
19778 == language::language_settings::EditPredictionProvider::Copilot;
19779 let copilot_enabled_for_language = self
19780 .buffer
19781 .read(cx)
19782 .language_settings(cx)
19783 .show_edit_predictions;
19784
19785 let project = project.read(cx);
19786 telemetry::event!(
19787 event_type,
19788 file_extension,
19789 vim_mode,
19790 copilot_enabled,
19791 copilot_enabled_for_language,
19792 edit_predictions_provider,
19793 is_via_ssh = project.is_via_ssh(),
19794 );
19795 }
19796
19797 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19798 /// with each line being an array of {text, highlight} objects.
19799 fn copy_highlight_json(
19800 &mut self,
19801 _: &CopyHighlightJson,
19802 window: &mut Window,
19803 cx: &mut Context<Self>,
19804 ) {
19805 #[derive(Serialize)]
19806 struct Chunk<'a> {
19807 text: String,
19808 highlight: Option<&'a str>,
19809 }
19810
19811 let snapshot = self.buffer.read(cx).snapshot(cx);
19812 let range = self
19813 .selected_text_range(false, window, cx)
19814 .and_then(|selection| {
19815 if selection.range.is_empty() {
19816 None
19817 } else {
19818 Some(selection.range)
19819 }
19820 })
19821 .unwrap_or_else(|| 0..snapshot.len());
19822
19823 let chunks = snapshot.chunks(range, true);
19824 let mut lines = Vec::new();
19825 let mut line: VecDeque<Chunk> = VecDeque::new();
19826
19827 let Some(style) = self.style.as_ref() else {
19828 return;
19829 };
19830
19831 for chunk in chunks {
19832 let highlight = chunk
19833 .syntax_highlight_id
19834 .and_then(|id| id.name(&style.syntax));
19835 let mut chunk_lines = chunk.text.split('\n').peekable();
19836 while let Some(text) = chunk_lines.next() {
19837 let mut merged_with_last_token = false;
19838 if let Some(last_token) = line.back_mut() {
19839 if last_token.highlight == highlight {
19840 last_token.text.push_str(text);
19841 merged_with_last_token = true;
19842 }
19843 }
19844
19845 if !merged_with_last_token {
19846 line.push_back(Chunk {
19847 text: text.into(),
19848 highlight,
19849 });
19850 }
19851
19852 if chunk_lines.peek().is_some() {
19853 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19854 line.pop_front();
19855 }
19856 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19857 line.pop_back();
19858 }
19859
19860 lines.push(mem::take(&mut line));
19861 }
19862 }
19863 }
19864
19865 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19866 return;
19867 };
19868 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19869 }
19870
19871 pub fn open_context_menu(
19872 &mut self,
19873 _: &OpenContextMenu,
19874 window: &mut Window,
19875 cx: &mut Context<Self>,
19876 ) {
19877 self.request_autoscroll(Autoscroll::newest(), cx);
19878 let position = self.selections.newest_display(cx).start;
19879 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19880 }
19881
19882 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19883 &self.inlay_hint_cache
19884 }
19885
19886 pub fn replay_insert_event(
19887 &mut self,
19888 text: &str,
19889 relative_utf16_range: Option<Range<isize>>,
19890 window: &mut Window,
19891 cx: &mut Context<Self>,
19892 ) {
19893 if !self.input_enabled {
19894 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19895 return;
19896 }
19897 if let Some(relative_utf16_range) = relative_utf16_range {
19898 let selections = self.selections.all::<OffsetUtf16>(cx);
19899 self.change_selections(None, window, cx, |s| {
19900 let new_ranges = selections.into_iter().map(|range| {
19901 let start = OffsetUtf16(
19902 range
19903 .head()
19904 .0
19905 .saturating_add_signed(relative_utf16_range.start),
19906 );
19907 let end = OffsetUtf16(
19908 range
19909 .head()
19910 .0
19911 .saturating_add_signed(relative_utf16_range.end),
19912 );
19913 start..end
19914 });
19915 s.select_ranges(new_ranges);
19916 });
19917 }
19918
19919 self.handle_input(text, window, cx);
19920 }
19921
19922 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19923 let Some(provider) = self.semantics_provider.as_ref() else {
19924 return false;
19925 };
19926
19927 let mut supports = false;
19928 self.buffer().update(cx, |this, cx| {
19929 this.for_each_buffer(|buffer| {
19930 supports |= provider.supports_inlay_hints(buffer, cx);
19931 });
19932 });
19933
19934 supports
19935 }
19936
19937 pub fn is_focused(&self, window: &Window) -> bool {
19938 self.focus_handle.is_focused(window)
19939 }
19940
19941 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19942 cx.emit(EditorEvent::Focused);
19943
19944 if let Some(descendant) = self
19945 .last_focused_descendant
19946 .take()
19947 .and_then(|descendant| descendant.upgrade())
19948 {
19949 window.focus(&descendant);
19950 } else {
19951 if let Some(blame) = self.blame.as_ref() {
19952 blame.update(cx, GitBlame::focus)
19953 }
19954
19955 self.blink_manager.update(cx, BlinkManager::enable);
19956 self.show_cursor_names(window, cx);
19957 self.buffer.update(cx, |buffer, cx| {
19958 buffer.finalize_last_transaction(cx);
19959 if self.leader_id.is_none() {
19960 buffer.set_active_selections(
19961 &self.selections.disjoint_anchors(),
19962 self.selections.line_mode,
19963 self.cursor_shape,
19964 cx,
19965 );
19966 }
19967 });
19968 }
19969 }
19970
19971 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19972 cx.emit(EditorEvent::FocusedIn)
19973 }
19974
19975 fn handle_focus_out(
19976 &mut self,
19977 event: FocusOutEvent,
19978 _window: &mut Window,
19979 cx: &mut Context<Self>,
19980 ) {
19981 if event.blurred != self.focus_handle {
19982 self.last_focused_descendant = Some(event.blurred);
19983 }
19984 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19985 }
19986
19987 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19988 self.blink_manager.update(cx, BlinkManager::disable);
19989 self.buffer
19990 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19991
19992 if let Some(blame) = self.blame.as_ref() {
19993 blame.update(cx, GitBlame::blur)
19994 }
19995 if !self.hover_state.focused(window, cx) {
19996 hide_hover(self, cx);
19997 }
19998 if !self
19999 .context_menu
20000 .borrow()
20001 .as_ref()
20002 .is_some_and(|context_menu| context_menu.focused(window, cx))
20003 {
20004 self.hide_context_menu(window, cx);
20005 }
20006 self.discard_inline_completion(false, cx);
20007 cx.emit(EditorEvent::Blurred);
20008 cx.notify();
20009 }
20010
20011 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20012 let mut pending: String = window
20013 .pending_input_keystrokes()
20014 .into_iter()
20015 .flatten()
20016 .filter_map(|keystroke| {
20017 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20018 keystroke.key_char.clone()
20019 } else {
20020 None
20021 }
20022 })
20023 .collect();
20024
20025 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20026 pending = "".to_string();
20027 }
20028
20029 let existing_pending = self
20030 .text_highlights::<PendingInput>(cx)
20031 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20032 if existing_pending.is_none() && pending.is_empty() {
20033 return;
20034 }
20035 let transaction =
20036 self.transact(window, cx, |this, window, cx| {
20037 let selections = this.selections.all::<usize>(cx);
20038 let edits = selections
20039 .iter()
20040 .map(|selection| (selection.end..selection.end, pending.clone()));
20041 this.edit(edits, cx);
20042 this.change_selections(None, window, cx, |s| {
20043 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20044 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20045 }));
20046 });
20047 if let Some(existing_ranges) = existing_pending {
20048 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20049 this.edit(edits, cx);
20050 }
20051 });
20052
20053 let snapshot = self.snapshot(window, cx);
20054 let ranges = self
20055 .selections
20056 .all::<usize>(cx)
20057 .into_iter()
20058 .map(|selection| {
20059 snapshot.buffer_snapshot.anchor_after(selection.end)
20060 ..snapshot
20061 .buffer_snapshot
20062 .anchor_before(selection.end + pending.len())
20063 })
20064 .collect();
20065
20066 if pending.is_empty() {
20067 self.clear_highlights::<PendingInput>(cx);
20068 } else {
20069 self.highlight_text::<PendingInput>(
20070 ranges,
20071 HighlightStyle {
20072 underline: Some(UnderlineStyle {
20073 thickness: px(1.),
20074 color: None,
20075 wavy: false,
20076 }),
20077 ..Default::default()
20078 },
20079 cx,
20080 );
20081 }
20082
20083 self.ime_transaction = self.ime_transaction.or(transaction);
20084 if let Some(transaction) = self.ime_transaction {
20085 self.buffer.update(cx, |buffer, cx| {
20086 buffer.group_until_transaction(transaction, cx);
20087 });
20088 }
20089
20090 if self.text_highlights::<PendingInput>(cx).is_none() {
20091 self.ime_transaction.take();
20092 }
20093 }
20094
20095 pub fn register_action_renderer(
20096 &mut self,
20097 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20098 ) -> Subscription {
20099 let id = self.next_editor_action_id.post_inc();
20100 self.editor_actions
20101 .borrow_mut()
20102 .insert(id, Box::new(listener));
20103
20104 let editor_actions = self.editor_actions.clone();
20105 Subscription::new(move || {
20106 editor_actions.borrow_mut().remove(&id);
20107 })
20108 }
20109
20110 pub fn register_action<A: Action>(
20111 &mut self,
20112 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20113 ) -> Subscription {
20114 let id = self.next_editor_action_id.post_inc();
20115 let listener = Arc::new(listener);
20116 self.editor_actions.borrow_mut().insert(
20117 id,
20118 Box::new(move |_, window, _| {
20119 let listener = listener.clone();
20120 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20121 let action = action.downcast_ref().unwrap();
20122 if phase == DispatchPhase::Bubble {
20123 listener(action, window, cx)
20124 }
20125 })
20126 }),
20127 );
20128
20129 let editor_actions = self.editor_actions.clone();
20130 Subscription::new(move || {
20131 editor_actions.borrow_mut().remove(&id);
20132 })
20133 }
20134
20135 pub fn file_header_size(&self) -> u32 {
20136 FILE_HEADER_HEIGHT
20137 }
20138
20139 pub fn restore(
20140 &mut self,
20141 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20142 window: &mut Window,
20143 cx: &mut Context<Self>,
20144 ) {
20145 let workspace = self.workspace();
20146 let project = self.project.as_ref();
20147 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20148 let mut tasks = Vec::new();
20149 for (buffer_id, changes) in revert_changes {
20150 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20151 buffer.update(cx, |buffer, cx| {
20152 buffer.edit(
20153 changes
20154 .into_iter()
20155 .map(|(range, text)| (range, text.to_string())),
20156 None,
20157 cx,
20158 );
20159 });
20160
20161 if let Some(project) =
20162 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20163 {
20164 project.update(cx, |project, cx| {
20165 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20166 })
20167 }
20168 }
20169 }
20170 tasks
20171 });
20172 cx.spawn_in(window, async move |_, cx| {
20173 for (buffer, task) in save_tasks {
20174 let result = task.await;
20175 if result.is_err() {
20176 let Some(path) = buffer
20177 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20178 .ok()
20179 else {
20180 continue;
20181 };
20182 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20183 let Some(task) = cx
20184 .update_window_entity(&workspace, |workspace, window, cx| {
20185 workspace
20186 .open_path_preview(path, None, false, false, false, window, cx)
20187 })
20188 .ok()
20189 else {
20190 continue;
20191 };
20192 task.await.log_err();
20193 }
20194 }
20195 }
20196 })
20197 .detach();
20198 self.change_selections(None, window, cx, |selections| selections.refresh());
20199 }
20200
20201 pub fn to_pixel_point(
20202 &self,
20203 source: multi_buffer::Anchor,
20204 editor_snapshot: &EditorSnapshot,
20205 window: &mut Window,
20206 ) -> Option<gpui::Point<Pixels>> {
20207 let source_point = source.to_display_point(editor_snapshot);
20208 self.display_to_pixel_point(source_point, editor_snapshot, window)
20209 }
20210
20211 pub fn display_to_pixel_point(
20212 &self,
20213 source: DisplayPoint,
20214 editor_snapshot: &EditorSnapshot,
20215 window: &mut Window,
20216 ) -> Option<gpui::Point<Pixels>> {
20217 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20218 let text_layout_details = self.text_layout_details(window);
20219 let scroll_top = text_layout_details
20220 .scroll_anchor
20221 .scroll_position(editor_snapshot)
20222 .y;
20223
20224 if source.row().as_f32() < scroll_top.floor() {
20225 return None;
20226 }
20227 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20228 let source_y = line_height * (source.row().as_f32() - scroll_top);
20229 Some(gpui::Point::new(source_x, source_y))
20230 }
20231
20232 pub fn has_visible_completions_menu(&self) -> bool {
20233 !self.edit_prediction_preview_is_active()
20234 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20235 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20236 })
20237 }
20238
20239 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20240 if self.mode.is_minimap() {
20241 return;
20242 }
20243 self.addons
20244 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20245 }
20246
20247 pub fn unregister_addon<T: Addon>(&mut self) {
20248 self.addons.remove(&std::any::TypeId::of::<T>());
20249 }
20250
20251 pub fn addon<T: Addon>(&self) -> Option<&T> {
20252 let type_id = std::any::TypeId::of::<T>();
20253 self.addons
20254 .get(&type_id)
20255 .and_then(|item| item.to_any().downcast_ref::<T>())
20256 }
20257
20258 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20259 let type_id = std::any::TypeId::of::<T>();
20260 self.addons
20261 .get_mut(&type_id)
20262 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20263 }
20264
20265 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20266 let text_layout_details = self.text_layout_details(window);
20267 let style = &text_layout_details.editor_style;
20268 let font_id = window.text_system().resolve_font(&style.text.font());
20269 let font_size = style.text.font_size.to_pixels(window.rem_size());
20270 let line_height = style.text.line_height_in_pixels(window.rem_size());
20271 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20272
20273 gpui::Size::new(em_width, line_height)
20274 }
20275
20276 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20277 self.load_diff_task.clone()
20278 }
20279
20280 fn read_metadata_from_db(
20281 &mut self,
20282 item_id: u64,
20283 workspace_id: WorkspaceId,
20284 window: &mut Window,
20285 cx: &mut Context<Editor>,
20286 ) {
20287 if self.is_singleton(cx)
20288 && !self.mode.is_minimap()
20289 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20290 {
20291 let buffer_snapshot = OnceCell::new();
20292
20293 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20294 if !folds.is_empty() {
20295 let snapshot =
20296 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20297 self.fold_ranges(
20298 folds
20299 .into_iter()
20300 .map(|(start, end)| {
20301 snapshot.clip_offset(start, Bias::Left)
20302 ..snapshot.clip_offset(end, Bias::Right)
20303 })
20304 .collect(),
20305 false,
20306 window,
20307 cx,
20308 );
20309 }
20310 }
20311
20312 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20313 if !selections.is_empty() {
20314 let snapshot =
20315 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20316 // skip adding the initial selection to selection history
20317 self.selection_history.mode = SelectionHistoryMode::Skipping;
20318 self.change_selections(None, window, cx, |s| {
20319 s.select_ranges(selections.into_iter().map(|(start, end)| {
20320 snapshot.clip_offset(start, Bias::Left)
20321 ..snapshot.clip_offset(end, Bias::Right)
20322 }));
20323 });
20324 self.selection_history.mode = SelectionHistoryMode::Normal;
20325 }
20326 };
20327 }
20328
20329 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20330 }
20331
20332 fn update_lsp_data(
20333 &mut self,
20334 update_on_edit: bool,
20335 for_server_id: Option<LanguageServerId>,
20336 for_buffer: Option<BufferId>,
20337 window: &mut Window,
20338 cx: &mut Context<'_, Self>,
20339 ) {
20340 self.pull_diagnostics(for_buffer, window, cx);
20341 self.refresh_colors(update_on_edit, for_server_id, for_buffer, window, cx);
20342 }
20343}
20344
20345fn vim_enabled(cx: &App) -> bool {
20346 cx.global::<SettingsStore>()
20347 .raw_user_settings()
20348 .get("vim_mode")
20349 == Some(&serde_json::Value::Bool(true))
20350}
20351
20352fn process_completion_for_edit(
20353 completion: &Completion,
20354 intent: CompletionIntent,
20355 buffer: &Entity<Buffer>,
20356 cursor_position: &text::Anchor,
20357 cx: &mut Context<Editor>,
20358) -> CompletionEdit {
20359 let buffer = buffer.read(cx);
20360 let buffer_snapshot = buffer.snapshot();
20361 let (snippet, new_text) = if completion.is_snippet() {
20362 // Workaround for typescript language server issues so that methods don't expand within
20363 // strings and functions with type expressions. The previous point is used because the query
20364 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20365 let mut snippet_source = completion.new_text.clone();
20366 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20367 previous_point.column = previous_point.column.saturating_sub(1);
20368 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20369 if scope.prefers_label_for_snippet_in_completion() {
20370 if let Some(label) = completion.label() {
20371 if matches!(
20372 completion.kind(),
20373 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20374 ) {
20375 snippet_source = label;
20376 }
20377 }
20378 }
20379 }
20380 match Snippet::parse(&snippet_source).log_err() {
20381 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20382 None => (None, completion.new_text.clone()),
20383 }
20384 } else {
20385 (None, completion.new_text.clone())
20386 };
20387
20388 let mut range_to_replace = {
20389 let replace_range = &completion.replace_range;
20390 if let CompletionSource::Lsp {
20391 insert_range: Some(insert_range),
20392 ..
20393 } = &completion.source
20394 {
20395 debug_assert_eq!(
20396 insert_range.start, replace_range.start,
20397 "insert_range and replace_range should start at the same position"
20398 );
20399 debug_assert!(
20400 insert_range
20401 .start
20402 .cmp(&cursor_position, &buffer_snapshot)
20403 .is_le(),
20404 "insert_range should start before or at cursor position"
20405 );
20406 debug_assert!(
20407 replace_range
20408 .start
20409 .cmp(&cursor_position, &buffer_snapshot)
20410 .is_le(),
20411 "replace_range should start before or at cursor position"
20412 );
20413 debug_assert!(
20414 insert_range
20415 .end
20416 .cmp(&cursor_position, &buffer_snapshot)
20417 .is_le(),
20418 "insert_range should end before or at cursor position"
20419 );
20420
20421 let should_replace = match intent {
20422 CompletionIntent::CompleteWithInsert => false,
20423 CompletionIntent::CompleteWithReplace => true,
20424 CompletionIntent::Complete | CompletionIntent::Compose => {
20425 let insert_mode =
20426 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20427 .completions
20428 .lsp_insert_mode;
20429 match insert_mode {
20430 LspInsertMode::Insert => false,
20431 LspInsertMode::Replace => true,
20432 LspInsertMode::ReplaceSubsequence => {
20433 let mut text_to_replace = buffer.chars_for_range(
20434 buffer.anchor_before(replace_range.start)
20435 ..buffer.anchor_after(replace_range.end),
20436 );
20437 let mut current_needle = text_to_replace.next();
20438 for haystack_ch in completion.label.text.chars() {
20439 if let Some(needle_ch) = current_needle {
20440 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20441 current_needle = text_to_replace.next();
20442 }
20443 }
20444 }
20445 current_needle.is_none()
20446 }
20447 LspInsertMode::ReplaceSuffix => {
20448 if replace_range
20449 .end
20450 .cmp(&cursor_position, &buffer_snapshot)
20451 .is_gt()
20452 {
20453 let range_after_cursor = *cursor_position..replace_range.end;
20454 let text_after_cursor = buffer
20455 .text_for_range(
20456 buffer.anchor_before(range_after_cursor.start)
20457 ..buffer.anchor_after(range_after_cursor.end),
20458 )
20459 .collect::<String>()
20460 .to_ascii_lowercase();
20461 completion
20462 .label
20463 .text
20464 .to_ascii_lowercase()
20465 .ends_with(&text_after_cursor)
20466 } else {
20467 true
20468 }
20469 }
20470 }
20471 }
20472 };
20473
20474 if should_replace {
20475 replace_range.clone()
20476 } else {
20477 insert_range.clone()
20478 }
20479 } else {
20480 replace_range.clone()
20481 }
20482 };
20483
20484 if range_to_replace
20485 .end
20486 .cmp(&cursor_position, &buffer_snapshot)
20487 .is_lt()
20488 {
20489 range_to_replace.end = *cursor_position;
20490 }
20491
20492 CompletionEdit {
20493 new_text,
20494 replace_range: range_to_replace.to_offset(&buffer),
20495 snippet,
20496 }
20497}
20498
20499struct CompletionEdit {
20500 new_text: String,
20501 replace_range: Range<usize>,
20502 snippet: Option<Snippet>,
20503}
20504
20505fn insert_extra_newline_brackets(
20506 buffer: &MultiBufferSnapshot,
20507 range: Range<usize>,
20508 language: &language::LanguageScope,
20509) -> bool {
20510 let leading_whitespace_len = buffer
20511 .reversed_chars_at(range.start)
20512 .take_while(|c| c.is_whitespace() && *c != '\n')
20513 .map(|c| c.len_utf8())
20514 .sum::<usize>();
20515 let trailing_whitespace_len = buffer
20516 .chars_at(range.end)
20517 .take_while(|c| c.is_whitespace() && *c != '\n')
20518 .map(|c| c.len_utf8())
20519 .sum::<usize>();
20520 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20521
20522 language.brackets().any(|(pair, enabled)| {
20523 let pair_start = pair.start.trim_end();
20524 let pair_end = pair.end.trim_start();
20525
20526 enabled
20527 && pair.newline
20528 && buffer.contains_str_at(range.end, pair_end)
20529 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20530 })
20531}
20532
20533fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20534 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20535 [(buffer, range, _)] => (*buffer, range.clone()),
20536 _ => return false,
20537 };
20538 let pair = {
20539 let mut result: Option<BracketMatch> = None;
20540
20541 for pair in buffer
20542 .all_bracket_ranges(range.clone())
20543 .filter(move |pair| {
20544 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20545 })
20546 {
20547 let len = pair.close_range.end - pair.open_range.start;
20548
20549 if let Some(existing) = &result {
20550 let existing_len = existing.close_range.end - existing.open_range.start;
20551 if len > existing_len {
20552 continue;
20553 }
20554 }
20555
20556 result = Some(pair);
20557 }
20558
20559 result
20560 };
20561 let Some(pair) = pair else {
20562 return false;
20563 };
20564 pair.newline_only
20565 && buffer
20566 .chars_for_range(pair.open_range.end..range.start)
20567 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20568 .all(|c| c.is_whitespace() && c != '\n')
20569}
20570
20571fn update_uncommitted_diff_for_buffer(
20572 editor: Entity<Editor>,
20573 project: &Entity<Project>,
20574 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20575 buffer: Entity<MultiBuffer>,
20576 cx: &mut App,
20577) -> Task<()> {
20578 let mut tasks = Vec::new();
20579 project.update(cx, |project, cx| {
20580 for buffer in buffers {
20581 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20582 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20583 }
20584 }
20585 });
20586 cx.spawn(async move |cx| {
20587 let diffs = future::join_all(tasks).await;
20588 if editor
20589 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20590 .unwrap_or(false)
20591 {
20592 return;
20593 }
20594
20595 buffer
20596 .update(cx, |buffer, cx| {
20597 for diff in diffs.into_iter().flatten() {
20598 buffer.add_diff(diff, cx);
20599 }
20600 })
20601 .ok();
20602 })
20603}
20604
20605fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20606 let tab_size = tab_size.get() as usize;
20607 let mut width = offset;
20608
20609 for ch in text.chars() {
20610 width += if ch == '\t' {
20611 tab_size - (width % tab_size)
20612 } else {
20613 1
20614 };
20615 }
20616
20617 width - offset
20618}
20619
20620#[cfg(test)]
20621mod tests {
20622 use super::*;
20623
20624 #[test]
20625 fn test_string_size_with_expanded_tabs() {
20626 let nz = |val| NonZeroU32::new(val).unwrap();
20627 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20628 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20629 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20630 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20631 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20632 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20633 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20634 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20635 }
20636}
20637
20638/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20639struct WordBreakingTokenizer<'a> {
20640 input: &'a str,
20641}
20642
20643impl<'a> WordBreakingTokenizer<'a> {
20644 fn new(input: &'a str) -> Self {
20645 Self { input }
20646 }
20647}
20648
20649fn is_char_ideographic(ch: char) -> bool {
20650 use unicode_script::Script::*;
20651 use unicode_script::UnicodeScript;
20652 matches!(ch.script(), Han | Tangut | Yi)
20653}
20654
20655fn is_grapheme_ideographic(text: &str) -> bool {
20656 text.chars().any(is_char_ideographic)
20657}
20658
20659fn is_grapheme_whitespace(text: &str) -> bool {
20660 text.chars().any(|x| x.is_whitespace())
20661}
20662
20663fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20664 text.chars().next().map_or(false, |ch| {
20665 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20666 })
20667}
20668
20669#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20670enum WordBreakToken<'a> {
20671 Word { token: &'a str, grapheme_len: usize },
20672 InlineWhitespace { token: &'a str, grapheme_len: usize },
20673 Newline,
20674}
20675
20676impl<'a> Iterator for WordBreakingTokenizer<'a> {
20677 /// Yields a span, the count of graphemes in the token, and whether it was
20678 /// whitespace. Note that it also breaks at word boundaries.
20679 type Item = WordBreakToken<'a>;
20680
20681 fn next(&mut self) -> Option<Self::Item> {
20682 use unicode_segmentation::UnicodeSegmentation;
20683 if self.input.is_empty() {
20684 return None;
20685 }
20686
20687 let mut iter = self.input.graphemes(true).peekable();
20688 let mut offset = 0;
20689 let mut grapheme_len = 0;
20690 if let Some(first_grapheme) = iter.next() {
20691 let is_newline = first_grapheme == "\n";
20692 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20693 offset += first_grapheme.len();
20694 grapheme_len += 1;
20695 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20696 if let Some(grapheme) = iter.peek().copied() {
20697 if should_stay_with_preceding_ideograph(grapheme) {
20698 offset += grapheme.len();
20699 grapheme_len += 1;
20700 }
20701 }
20702 } else {
20703 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20704 let mut next_word_bound = words.peek().copied();
20705 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20706 next_word_bound = words.next();
20707 }
20708 while let Some(grapheme) = iter.peek().copied() {
20709 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20710 break;
20711 };
20712 if is_grapheme_whitespace(grapheme) != is_whitespace
20713 || (grapheme == "\n") != is_newline
20714 {
20715 break;
20716 };
20717 offset += grapheme.len();
20718 grapheme_len += 1;
20719 iter.next();
20720 }
20721 }
20722 let token = &self.input[..offset];
20723 self.input = &self.input[offset..];
20724 if token == "\n" {
20725 Some(WordBreakToken::Newline)
20726 } else if is_whitespace {
20727 Some(WordBreakToken::InlineWhitespace {
20728 token,
20729 grapheme_len,
20730 })
20731 } else {
20732 Some(WordBreakToken::Word {
20733 token,
20734 grapheme_len,
20735 })
20736 }
20737 } else {
20738 None
20739 }
20740 }
20741}
20742
20743#[test]
20744fn test_word_breaking_tokenizer() {
20745 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20746 ("", &[]),
20747 (" ", &[whitespace(" ", 2)]),
20748 ("Ʒ", &[word("Ʒ", 1)]),
20749 ("Ǽ", &[word("Ǽ", 1)]),
20750 ("⋑", &[word("⋑", 1)]),
20751 ("⋑⋑", &[word("⋑⋑", 2)]),
20752 (
20753 "原理,进而",
20754 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20755 ),
20756 (
20757 "hello world",
20758 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20759 ),
20760 (
20761 "hello, world",
20762 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20763 ),
20764 (
20765 " hello world",
20766 &[
20767 whitespace(" ", 2),
20768 word("hello", 5),
20769 whitespace(" ", 1),
20770 word("world", 5),
20771 ],
20772 ),
20773 (
20774 "这是什么 \n 钢笔",
20775 &[
20776 word("这", 1),
20777 word("是", 1),
20778 word("什", 1),
20779 word("么", 1),
20780 whitespace(" ", 1),
20781 newline(),
20782 whitespace(" ", 1),
20783 word("钢", 1),
20784 word("笔", 1),
20785 ],
20786 ),
20787 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20788 ];
20789
20790 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20791 WordBreakToken::Word {
20792 token,
20793 grapheme_len,
20794 }
20795 }
20796
20797 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20798 WordBreakToken::InlineWhitespace {
20799 token,
20800 grapheme_len,
20801 }
20802 }
20803
20804 fn newline() -> WordBreakToken<'static> {
20805 WordBreakToken::Newline
20806 }
20807
20808 for (input, result) in tests {
20809 assert_eq!(
20810 WordBreakingTokenizer::new(input)
20811 .collect::<Vec<_>>()
20812 .as_slice(),
20813 *result,
20814 );
20815 }
20816}
20817
20818fn wrap_with_prefix(
20819 line_prefix: String,
20820 unwrapped_text: String,
20821 wrap_column: usize,
20822 tab_size: NonZeroU32,
20823 preserve_existing_whitespace: bool,
20824) -> String {
20825 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20826 let mut wrapped_text = String::new();
20827 let mut current_line = line_prefix.clone();
20828
20829 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20830 let mut current_line_len = line_prefix_len;
20831 let mut in_whitespace = false;
20832 for token in tokenizer {
20833 let have_preceding_whitespace = in_whitespace;
20834 match token {
20835 WordBreakToken::Word {
20836 token,
20837 grapheme_len,
20838 } => {
20839 in_whitespace = false;
20840 if current_line_len + grapheme_len > wrap_column
20841 && current_line_len != line_prefix_len
20842 {
20843 wrapped_text.push_str(current_line.trim_end());
20844 wrapped_text.push('\n');
20845 current_line.truncate(line_prefix.len());
20846 current_line_len = line_prefix_len;
20847 }
20848 current_line.push_str(token);
20849 current_line_len += grapheme_len;
20850 }
20851 WordBreakToken::InlineWhitespace {
20852 mut token,
20853 mut grapheme_len,
20854 } => {
20855 in_whitespace = true;
20856 if have_preceding_whitespace && !preserve_existing_whitespace {
20857 continue;
20858 }
20859 if !preserve_existing_whitespace {
20860 token = " ";
20861 grapheme_len = 1;
20862 }
20863 if current_line_len + grapheme_len > wrap_column {
20864 wrapped_text.push_str(current_line.trim_end());
20865 wrapped_text.push('\n');
20866 current_line.truncate(line_prefix.len());
20867 current_line_len = line_prefix_len;
20868 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20869 current_line.push_str(token);
20870 current_line_len += grapheme_len;
20871 }
20872 }
20873 WordBreakToken::Newline => {
20874 in_whitespace = true;
20875 if preserve_existing_whitespace {
20876 wrapped_text.push_str(current_line.trim_end());
20877 wrapped_text.push('\n');
20878 current_line.truncate(line_prefix.len());
20879 current_line_len = line_prefix_len;
20880 } else if have_preceding_whitespace {
20881 continue;
20882 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20883 {
20884 wrapped_text.push_str(current_line.trim_end());
20885 wrapped_text.push('\n');
20886 current_line.truncate(line_prefix.len());
20887 current_line_len = line_prefix_len;
20888 } else if current_line_len != line_prefix_len {
20889 current_line.push(' ');
20890 current_line_len += 1;
20891 }
20892 }
20893 }
20894 }
20895
20896 if !current_line.is_empty() {
20897 wrapped_text.push_str(¤t_line);
20898 }
20899 wrapped_text
20900}
20901
20902#[test]
20903fn test_wrap_with_prefix() {
20904 assert_eq!(
20905 wrap_with_prefix(
20906 "# ".to_string(),
20907 "abcdefg".to_string(),
20908 4,
20909 NonZeroU32::new(4).unwrap(),
20910 false,
20911 ),
20912 "# abcdefg"
20913 );
20914 assert_eq!(
20915 wrap_with_prefix(
20916 "".to_string(),
20917 "\thello world".to_string(),
20918 8,
20919 NonZeroU32::new(4).unwrap(),
20920 false,
20921 ),
20922 "hello\nworld"
20923 );
20924 assert_eq!(
20925 wrap_with_prefix(
20926 "// ".to_string(),
20927 "xx \nyy zz aa bb cc".to_string(),
20928 12,
20929 NonZeroU32::new(4).unwrap(),
20930 false,
20931 ),
20932 "// xx yy zz\n// aa bb cc"
20933 );
20934 assert_eq!(
20935 wrap_with_prefix(
20936 String::new(),
20937 "这是什么 \n 钢笔".to_string(),
20938 3,
20939 NonZeroU32::new(4).unwrap(),
20940 false,
20941 ),
20942 "这是什\n么 钢\n笔"
20943 );
20944}
20945
20946pub trait CollaborationHub {
20947 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20948 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20949 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20950}
20951
20952impl CollaborationHub for Entity<Project> {
20953 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20954 self.read(cx).collaborators()
20955 }
20956
20957 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20958 self.read(cx).user_store().read(cx).participant_indices()
20959 }
20960
20961 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20962 let this = self.read(cx);
20963 let user_ids = this.collaborators().values().map(|c| c.user_id);
20964 this.user_store().read(cx).participant_names(user_ids, cx)
20965 }
20966}
20967
20968pub trait SemanticsProvider {
20969 fn hover(
20970 &self,
20971 buffer: &Entity<Buffer>,
20972 position: text::Anchor,
20973 cx: &mut App,
20974 ) -> Option<Task<Vec<project::Hover>>>;
20975
20976 fn inline_values(
20977 &self,
20978 buffer_handle: Entity<Buffer>,
20979 range: Range<text::Anchor>,
20980 cx: &mut App,
20981 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20982
20983 fn inlay_hints(
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 resolve_inlay_hint(
20991 &self,
20992 hint: InlayHint,
20993 buffer_handle: Entity<Buffer>,
20994 server_id: LanguageServerId,
20995 cx: &mut App,
20996 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20997
20998 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20999
21000 fn document_highlights(
21001 &self,
21002 buffer: &Entity<Buffer>,
21003 position: text::Anchor,
21004 cx: &mut App,
21005 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21006
21007 fn definitions(
21008 &self,
21009 buffer: &Entity<Buffer>,
21010 position: text::Anchor,
21011 kind: GotoDefinitionKind,
21012 cx: &mut App,
21013 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21014
21015 fn range_for_rename(
21016 &self,
21017 buffer: &Entity<Buffer>,
21018 position: text::Anchor,
21019 cx: &mut App,
21020 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21021
21022 fn perform_rename(
21023 &self,
21024 buffer: &Entity<Buffer>,
21025 position: text::Anchor,
21026 new_name: String,
21027 cx: &mut App,
21028 ) -> Option<Task<Result<ProjectTransaction>>>;
21029}
21030
21031pub trait CompletionProvider {
21032 fn completions(
21033 &self,
21034 excerpt_id: ExcerptId,
21035 buffer: &Entity<Buffer>,
21036 buffer_position: text::Anchor,
21037 trigger: CompletionContext,
21038 window: &mut Window,
21039 cx: &mut Context<Editor>,
21040 ) -> Task<Result<Vec<CompletionResponse>>>;
21041
21042 fn resolve_completions(
21043 &self,
21044 _buffer: Entity<Buffer>,
21045 _completion_indices: Vec<usize>,
21046 _completions: Rc<RefCell<Box<[Completion]>>>,
21047 _cx: &mut Context<Editor>,
21048 ) -> Task<Result<bool>> {
21049 Task::ready(Ok(false))
21050 }
21051
21052 fn apply_additional_edits_for_completion(
21053 &self,
21054 _buffer: Entity<Buffer>,
21055 _completions: Rc<RefCell<Box<[Completion]>>>,
21056 _completion_index: usize,
21057 _push_to_history: bool,
21058 _cx: &mut Context<Editor>,
21059 ) -> Task<Result<Option<language::Transaction>>> {
21060 Task::ready(Ok(None))
21061 }
21062
21063 fn is_completion_trigger(
21064 &self,
21065 buffer: &Entity<Buffer>,
21066 position: language::Anchor,
21067 text: &str,
21068 trigger_in_words: bool,
21069 menu_is_open: bool,
21070 cx: &mut Context<Editor>,
21071 ) -> bool;
21072
21073 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21074
21075 fn sort_completions(&self) -> bool {
21076 true
21077 }
21078
21079 fn filter_completions(&self) -> bool {
21080 true
21081 }
21082}
21083
21084pub trait CodeActionProvider {
21085 fn id(&self) -> Arc<str>;
21086
21087 fn code_actions(
21088 &self,
21089 buffer: &Entity<Buffer>,
21090 range: Range<text::Anchor>,
21091 window: &mut Window,
21092 cx: &mut App,
21093 ) -> Task<Result<Vec<CodeAction>>>;
21094
21095 fn apply_code_action(
21096 &self,
21097 buffer_handle: Entity<Buffer>,
21098 action: CodeAction,
21099 excerpt_id: ExcerptId,
21100 push_to_history: bool,
21101 window: &mut Window,
21102 cx: &mut App,
21103 ) -> Task<Result<ProjectTransaction>>;
21104}
21105
21106impl CodeActionProvider for Entity<Project> {
21107 fn id(&self) -> Arc<str> {
21108 "project".into()
21109 }
21110
21111 fn code_actions(
21112 &self,
21113 buffer: &Entity<Buffer>,
21114 range: Range<text::Anchor>,
21115 _window: &mut Window,
21116 cx: &mut App,
21117 ) -> Task<Result<Vec<CodeAction>>> {
21118 self.update(cx, |project, cx| {
21119 let code_lens = project.code_lens(buffer, range.clone(), cx);
21120 let code_actions = project.code_actions(buffer, range, None, cx);
21121 cx.background_spawn(async move {
21122 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21123 Ok(code_lens
21124 .context("code lens fetch")?
21125 .into_iter()
21126 .chain(code_actions.context("code action fetch")?)
21127 .collect())
21128 })
21129 })
21130 }
21131
21132 fn apply_code_action(
21133 &self,
21134 buffer_handle: Entity<Buffer>,
21135 action: CodeAction,
21136 _excerpt_id: ExcerptId,
21137 push_to_history: bool,
21138 _window: &mut Window,
21139 cx: &mut App,
21140 ) -> Task<Result<ProjectTransaction>> {
21141 self.update(cx, |project, cx| {
21142 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21143 })
21144 }
21145}
21146
21147fn snippet_completions(
21148 project: &Project,
21149 buffer: &Entity<Buffer>,
21150 buffer_position: text::Anchor,
21151 cx: &mut App,
21152) -> Task<Result<CompletionResponse>> {
21153 let languages = buffer.read(cx).languages_at(buffer_position);
21154 let snippet_store = project.snippets().read(cx);
21155
21156 let scopes: Vec<_> = languages
21157 .iter()
21158 .filter_map(|language| {
21159 let language_name = language.lsp_id();
21160 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21161
21162 if snippets.is_empty() {
21163 None
21164 } else {
21165 Some((language.default_scope(), snippets))
21166 }
21167 })
21168 .collect();
21169
21170 if scopes.is_empty() {
21171 return Task::ready(Ok(CompletionResponse {
21172 completions: vec![],
21173 is_incomplete: false,
21174 }));
21175 }
21176
21177 let snapshot = buffer.read(cx).text_snapshot();
21178 let chars: String = snapshot
21179 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21180 .collect();
21181 let executor = cx.background_executor().clone();
21182
21183 cx.background_spawn(async move {
21184 let mut is_incomplete = false;
21185 let mut completions: Vec<Completion> = Vec::new();
21186 for (scope, snippets) in scopes.into_iter() {
21187 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21188 let mut last_word = chars
21189 .chars()
21190 .take_while(|c| classifier.is_word(*c))
21191 .collect::<String>();
21192 last_word = last_word.chars().rev().collect();
21193
21194 if last_word.is_empty() {
21195 return Ok(CompletionResponse {
21196 completions: vec![],
21197 is_incomplete: true,
21198 });
21199 }
21200
21201 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21202 let to_lsp = |point: &text::Anchor| {
21203 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21204 point_to_lsp(end)
21205 };
21206 let lsp_end = to_lsp(&buffer_position);
21207
21208 let candidates = snippets
21209 .iter()
21210 .enumerate()
21211 .flat_map(|(ix, snippet)| {
21212 snippet
21213 .prefix
21214 .iter()
21215 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21216 })
21217 .collect::<Vec<StringMatchCandidate>>();
21218
21219 const MAX_RESULTS: usize = 100;
21220 let mut matches = fuzzy::match_strings(
21221 &candidates,
21222 &last_word,
21223 last_word.chars().any(|c| c.is_uppercase()),
21224 true,
21225 MAX_RESULTS,
21226 &Default::default(),
21227 executor.clone(),
21228 )
21229 .await;
21230
21231 if matches.len() >= MAX_RESULTS {
21232 is_incomplete = true;
21233 }
21234
21235 // Remove all candidates where the query's start does not match the start of any word in the candidate
21236 if let Some(query_start) = last_word.chars().next() {
21237 matches.retain(|string_match| {
21238 split_words(&string_match.string).any(|word| {
21239 // Check that the first codepoint of the word as lowercase matches the first
21240 // codepoint of the query as lowercase
21241 word.chars()
21242 .flat_map(|codepoint| codepoint.to_lowercase())
21243 .zip(query_start.to_lowercase())
21244 .all(|(word_cp, query_cp)| word_cp == query_cp)
21245 })
21246 });
21247 }
21248
21249 let matched_strings = matches
21250 .into_iter()
21251 .map(|m| m.string)
21252 .collect::<HashSet<_>>();
21253
21254 completions.extend(snippets.iter().filter_map(|snippet| {
21255 let matching_prefix = snippet
21256 .prefix
21257 .iter()
21258 .find(|prefix| matched_strings.contains(*prefix))?;
21259 let start = as_offset - last_word.len();
21260 let start = snapshot.anchor_before(start);
21261 let range = start..buffer_position;
21262 let lsp_start = to_lsp(&start);
21263 let lsp_range = lsp::Range {
21264 start: lsp_start,
21265 end: lsp_end,
21266 };
21267 Some(Completion {
21268 replace_range: range,
21269 new_text: snippet.body.clone(),
21270 source: CompletionSource::Lsp {
21271 insert_range: None,
21272 server_id: LanguageServerId(usize::MAX),
21273 resolved: true,
21274 lsp_completion: Box::new(lsp::CompletionItem {
21275 label: snippet.prefix.first().unwrap().clone(),
21276 kind: Some(CompletionItemKind::SNIPPET),
21277 label_details: snippet.description.as_ref().map(|description| {
21278 lsp::CompletionItemLabelDetails {
21279 detail: Some(description.clone()),
21280 description: None,
21281 }
21282 }),
21283 insert_text_format: Some(InsertTextFormat::SNIPPET),
21284 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21285 lsp::InsertReplaceEdit {
21286 new_text: snippet.body.clone(),
21287 insert: lsp_range,
21288 replace: lsp_range,
21289 },
21290 )),
21291 filter_text: Some(snippet.body.clone()),
21292 sort_text: Some(char::MAX.to_string()),
21293 ..lsp::CompletionItem::default()
21294 }),
21295 lsp_defaults: None,
21296 },
21297 label: CodeLabel {
21298 text: matching_prefix.clone(),
21299 runs: Vec::new(),
21300 filter_range: 0..matching_prefix.len(),
21301 },
21302 icon_path: None,
21303 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21304 single_line: snippet.name.clone().into(),
21305 plain_text: snippet
21306 .description
21307 .clone()
21308 .map(|description| description.into()),
21309 }),
21310 insert_text_mode: None,
21311 confirm: None,
21312 })
21313 }))
21314 }
21315
21316 Ok(CompletionResponse {
21317 completions,
21318 is_incomplete,
21319 })
21320 })
21321}
21322
21323impl CompletionProvider for Entity<Project> {
21324 fn completions(
21325 &self,
21326 _excerpt_id: ExcerptId,
21327 buffer: &Entity<Buffer>,
21328 buffer_position: text::Anchor,
21329 options: CompletionContext,
21330 _window: &mut Window,
21331 cx: &mut Context<Editor>,
21332 ) -> Task<Result<Vec<CompletionResponse>>> {
21333 self.update(cx, |project, cx| {
21334 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21335 let project_completions = project.completions(buffer, buffer_position, options, cx);
21336 cx.background_spawn(async move {
21337 let mut responses = project_completions.await?;
21338 let snippets = snippets.await?;
21339 if !snippets.completions.is_empty() {
21340 responses.push(snippets);
21341 }
21342 Ok(responses)
21343 })
21344 })
21345 }
21346
21347 fn resolve_completions(
21348 &self,
21349 buffer: Entity<Buffer>,
21350 completion_indices: Vec<usize>,
21351 completions: Rc<RefCell<Box<[Completion]>>>,
21352 cx: &mut Context<Editor>,
21353 ) -> Task<Result<bool>> {
21354 self.update(cx, |project, cx| {
21355 project.lsp_store().update(cx, |lsp_store, cx| {
21356 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21357 })
21358 })
21359 }
21360
21361 fn apply_additional_edits_for_completion(
21362 &self,
21363 buffer: Entity<Buffer>,
21364 completions: Rc<RefCell<Box<[Completion]>>>,
21365 completion_index: usize,
21366 push_to_history: bool,
21367 cx: &mut Context<Editor>,
21368 ) -> Task<Result<Option<language::Transaction>>> {
21369 self.update(cx, |project, cx| {
21370 project.lsp_store().update(cx, |lsp_store, cx| {
21371 lsp_store.apply_additional_edits_for_completion(
21372 buffer,
21373 completions,
21374 completion_index,
21375 push_to_history,
21376 cx,
21377 )
21378 })
21379 })
21380 }
21381
21382 fn is_completion_trigger(
21383 &self,
21384 buffer: &Entity<Buffer>,
21385 position: language::Anchor,
21386 text: &str,
21387 trigger_in_words: bool,
21388 menu_is_open: bool,
21389 cx: &mut Context<Editor>,
21390 ) -> bool {
21391 let mut chars = text.chars();
21392 let char = if let Some(char) = chars.next() {
21393 char
21394 } else {
21395 return false;
21396 };
21397 if chars.next().is_some() {
21398 return false;
21399 }
21400
21401 let buffer = buffer.read(cx);
21402 let snapshot = buffer.snapshot();
21403 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21404 return false;
21405 }
21406 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21407 if trigger_in_words && classifier.is_word(char) {
21408 return true;
21409 }
21410
21411 buffer.completion_triggers().contains(text)
21412 }
21413}
21414
21415impl SemanticsProvider for Entity<Project> {
21416 fn hover(
21417 &self,
21418 buffer: &Entity<Buffer>,
21419 position: text::Anchor,
21420 cx: &mut App,
21421 ) -> Option<Task<Vec<project::Hover>>> {
21422 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21423 }
21424
21425 fn document_highlights(
21426 &self,
21427 buffer: &Entity<Buffer>,
21428 position: text::Anchor,
21429 cx: &mut App,
21430 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21431 Some(self.update(cx, |project, cx| {
21432 project.document_highlights(buffer, position, cx)
21433 }))
21434 }
21435
21436 fn definitions(
21437 &self,
21438 buffer: &Entity<Buffer>,
21439 position: text::Anchor,
21440 kind: GotoDefinitionKind,
21441 cx: &mut App,
21442 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21443 Some(self.update(cx, |project, cx| match kind {
21444 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21445 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21446 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21447 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21448 }))
21449 }
21450
21451 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21452 // TODO: make this work for remote projects
21453 self.update(cx, |project, cx| {
21454 if project
21455 .active_debug_session(cx)
21456 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21457 {
21458 return true;
21459 }
21460
21461 buffer.update(cx, |buffer, cx| {
21462 project.any_language_server_supports_inlay_hints(buffer, cx)
21463 })
21464 })
21465 }
21466
21467 fn inline_values(
21468 &self,
21469 buffer_handle: Entity<Buffer>,
21470
21471 range: Range<text::Anchor>,
21472 cx: &mut App,
21473 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21474 self.update(cx, |project, cx| {
21475 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21476
21477 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21478 })
21479 }
21480
21481 fn inlay_hints(
21482 &self,
21483 buffer_handle: Entity<Buffer>,
21484 range: Range<text::Anchor>,
21485 cx: &mut App,
21486 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21487 Some(self.update(cx, |project, cx| {
21488 project.inlay_hints(buffer_handle, range, cx)
21489 }))
21490 }
21491
21492 fn resolve_inlay_hint(
21493 &self,
21494 hint: InlayHint,
21495 buffer_handle: Entity<Buffer>,
21496 server_id: LanguageServerId,
21497 cx: &mut App,
21498 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21499 Some(self.update(cx, |project, cx| {
21500 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21501 }))
21502 }
21503
21504 fn range_for_rename(
21505 &self,
21506 buffer: &Entity<Buffer>,
21507 position: text::Anchor,
21508 cx: &mut App,
21509 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21510 Some(self.update(cx, |project, cx| {
21511 let buffer = buffer.clone();
21512 let task = project.prepare_rename(buffer.clone(), position, cx);
21513 cx.spawn(async move |_, cx| {
21514 Ok(match task.await? {
21515 PrepareRenameResponse::Success(range) => Some(range),
21516 PrepareRenameResponse::InvalidPosition => None,
21517 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21518 // Fallback on using TreeSitter info to determine identifier range
21519 buffer.read_with(cx, |buffer, _| {
21520 let snapshot = buffer.snapshot();
21521 let (range, kind) = snapshot.surrounding_word(position);
21522 if kind != Some(CharKind::Word) {
21523 return None;
21524 }
21525 Some(
21526 snapshot.anchor_before(range.start)
21527 ..snapshot.anchor_after(range.end),
21528 )
21529 })?
21530 }
21531 })
21532 })
21533 }))
21534 }
21535
21536 fn perform_rename(
21537 &self,
21538 buffer: &Entity<Buffer>,
21539 position: text::Anchor,
21540 new_name: String,
21541 cx: &mut App,
21542 ) -> Option<Task<Result<ProjectTransaction>>> {
21543 Some(self.update(cx, |project, cx| {
21544 project.perform_rename(buffer.clone(), position, new_name, cx)
21545 }))
21546 }
21547}
21548
21549fn inlay_hint_settings(
21550 location: Anchor,
21551 snapshot: &MultiBufferSnapshot,
21552 cx: &mut Context<Editor>,
21553) -> InlayHintSettings {
21554 let file = snapshot.file_at(location);
21555 let language = snapshot.language_at(location).map(|l| l.name());
21556 language_settings(language, file, cx).inlay_hints
21557}
21558
21559fn consume_contiguous_rows(
21560 contiguous_row_selections: &mut Vec<Selection<Point>>,
21561 selection: &Selection<Point>,
21562 display_map: &DisplaySnapshot,
21563 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21564) -> (MultiBufferRow, MultiBufferRow) {
21565 contiguous_row_selections.push(selection.clone());
21566 let start_row = MultiBufferRow(selection.start.row);
21567 let mut end_row = ending_row(selection, display_map);
21568
21569 while let Some(next_selection) = selections.peek() {
21570 if next_selection.start.row <= end_row.0 {
21571 end_row = ending_row(next_selection, display_map);
21572 contiguous_row_selections.push(selections.next().unwrap().clone());
21573 } else {
21574 break;
21575 }
21576 }
21577 (start_row, end_row)
21578}
21579
21580fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21581 if next_selection.end.column > 0 || next_selection.is_empty() {
21582 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21583 } else {
21584 MultiBufferRow(next_selection.end.row)
21585 }
21586}
21587
21588impl EditorSnapshot {
21589 pub fn remote_selections_in_range<'a>(
21590 &'a self,
21591 range: &'a Range<Anchor>,
21592 collaboration_hub: &dyn CollaborationHub,
21593 cx: &'a App,
21594 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21595 let participant_names = collaboration_hub.user_names(cx);
21596 let participant_indices = collaboration_hub.user_participant_indices(cx);
21597 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21598 let collaborators_by_replica_id = collaborators_by_peer_id
21599 .values()
21600 .map(|collaborator| (collaborator.replica_id, collaborator))
21601 .collect::<HashMap<_, _>>();
21602 self.buffer_snapshot
21603 .selections_in_range(range, false)
21604 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21605 if replica_id == AGENT_REPLICA_ID {
21606 Some(RemoteSelection {
21607 replica_id,
21608 selection,
21609 cursor_shape,
21610 line_mode,
21611 collaborator_id: CollaboratorId::Agent,
21612 user_name: Some("Agent".into()),
21613 color: cx.theme().players().agent(),
21614 })
21615 } else {
21616 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21617 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21618 let user_name = participant_names.get(&collaborator.user_id).cloned();
21619 Some(RemoteSelection {
21620 replica_id,
21621 selection,
21622 cursor_shape,
21623 line_mode,
21624 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21625 user_name,
21626 color: if let Some(index) = participant_index {
21627 cx.theme().players().color_for_participant(index.0)
21628 } else {
21629 cx.theme().players().absent()
21630 },
21631 })
21632 }
21633 })
21634 }
21635
21636 pub fn hunks_for_ranges(
21637 &self,
21638 ranges: impl IntoIterator<Item = Range<Point>>,
21639 ) -> Vec<MultiBufferDiffHunk> {
21640 let mut hunks = Vec::new();
21641 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21642 HashMap::default();
21643 for query_range in ranges {
21644 let query_rows =
21645 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21646 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21647 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21648 ) {
21649 // Include deleted hunks that are adjacent to the query range, because
21650 // otherwise they would be missed.
21651 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21652 if hunk.status().is_deleted() {
21653 intersects_range |= hunk.row_range.start == query_rows.end;
21654 intersects_range |= hunk.row_range.end == query_rows.start;
21655 }
21656 if intersects_range {
21657 if !processed_buffer_rows
21658 .entry(hunk.buffer_id)
21659 .or_default()
21660 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21661 {
21662 continue;
21663 }
21664 hunks.push(hunk);
21665 }
21666 }
21667 }
21668
21669 hunks
21670 }
21671
21672 fn display_diff_hunks_for_rows<'a>(
21673 &'a self,
21674 display_rows: Range<DisplayRow>,
21675 folded_buffers: &'a HashSet<BufferId>,
21676 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21677 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21678 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21679
21680 self.buffer_snapshot
21681 .diff_hunks_in_range(buffer_start..buffer_end)
21682 .filter_map(|hunk| {
21683 if folded_buffers.contains(&hunk.buffer_id) {
21684 return None;
21685 }
21686
21687 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21688 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21689
21690 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21691 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21692
21693 let display_hunk = if hunk_display_start.column() != 0 {
21694 DisplayDiffHunk::Folded {
21695 display_row: hunk_display_start.row(),
21696 }
21697 } else {
21698 let mut end_row = hunk_display_end.row();
21699 if hunk_display_end.column() > 0 {
21700 end_row.0 += 1;
21701 }
21702 let is_created_file = hunk.is_created_file();
21703 DisplayDiffHunk::Unfolded {
21704 status: hunk.status(),
21705 diff_base_byte_range: hunk.diff_base_byte_range,
21706 display_row_range: hunk_display_start.row()..end_row,
21707 multi_buffer_range: Anchor::range_in_buffer(
21708 hunk.excerpt_id,
21709 hunk.buffer_id,
21710 hunk.buffer_range,
21711 ),
21712 is_created_file,
21713 }
21714 };
21715
21716 Some(display_hunk)
21717 })
21718 }
21719
21720 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21721 self.display_snapshot.buffer_snapshot.language_at(position)
21722 }
21723
21724 pub fn is_focused(&self) -> bool {
21725 self.is_focused
21726 }
21727
21728 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21729 self.placeholder_text.as_ref()
21730 }
21731
21732 pub fn scroll_position(&self) -> gpui::Point<f32> {
21733 self.scroll_anchor.scroll_position(&self.display_snapshot)
21734 }
21735
21736 fn gutter_dimensions(
21737 &self,
21738 font_id: FontId,
21739 font_size: Pixels,
21740 max_line_number_width: Pixels,
21741 cx: &App,
21742 ) -> Option<GutterDimensions> {
21743 if !self.show_gutter {
21744 return None;
21745 }
21746
21747 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21748 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21749
21750 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21751 matches!(
21752 ProjectSettings::get_global(cx).git.git_gutter,
21753 Some(GitGutterSetting::TrackedFiles)
21754 )
21755 });
21756 let gutter_settings = EditorSettings::get_global(cx).gutter;
21757 let show_line_numbers = self
21758 .show_line_numbers
21759 .unwrap_or(gutter_settings.line_numbers);
21760 let line_gutter_width = if show_line_numbers {
21761 // Avoid flicker-like gutter resizes when the line number gains another digit by
21762 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21763 let min_width_for_number_on_gutter =
21764 ch_advance * gutter_settings.min_line_number_digits as f32;
21765 max_line_number_width.max(min_width_for_number_on_gutter)
21766 } else {
21767 0.0.into()
21768 };
21769
21770 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21771 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21772
21773 let git_blame_entries_width =
21774 self.git_blame_gutter_max_author_length
21775 .map(|max_author_length| {
21776 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21777 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21778
21779 /// The number of characters to dedicate to gaps and margins.
21780 const SPACING_WIDTH: usize = 4;
21781
21782 let max_char_count = max_author_length.min(renderer.max_author_length())
21783 + ::git::SHORT_SHA_LENGTH
21784 + MAX_RELATIVE_TIMESTAMP.len()
21785 + SPACING_WIDTH;
21786
21787 ch_advance * max_char_count
21788 });
21789
21790 let is_singleton = self.buffer_snapshot.is_singleton();
21791
21792 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21793 left_padding += if !is_singleton {
21794 ch_width * 4.0
21795 } else if show_runnables || show_breakpoints {
21796 ch_width * 3.0
21797 } else if show_git_gutter && show_line_numbers {
21798 ch_width * 2.0
21799 } else if show_git_gutter || show_line_numbers {
21800 ch_width
21801 } else {
21802 px(0.)
21803 };
21804
21805 let shows_folds = is_singleton && gutter_settings.folds;
21806
21807 let right_padding = if shows_folds && show_line_numbers {
21808 ch_width * 4.0
21809 } else if shows_folds || (!is_singleton && show_line_numbers) {
21810 ch_width * 3.0
21811 } else if show_line_numbers {
21812 ch_width
21813 } else {
21814 px(0.)
21815 };
21816
21817 Some(GutterDimensions {
21818 left_padding,
21819 right_padding,
21820 width: line_gutter_width + left_padding + right_padding,
21821 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21822 git_blame_entries_width,
21823 })
21824 }
21825
21826 pub fn render_crease_toggle(
21827 &self,
21828 buffer_row: MultiBufferRow,
21829 row_contains_cursor: bool,
21830 editor: Entity<Editor>,
21831 window: &mut Window,
21832 cx: &mut App,
21833 ) -> Option<AnyElement> {
21834 let folded = self.is_line_folded(buffer_row);
21835 let mut is_foldable = false;
21836
21837 if let Some(crease) = self
21838 .crease_snapshot
21839 .query_row(buffer_row, &self.buffer_snapshot)
21840 {
21841 is_foldable = true;
21842 match crease {
21843 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21844 if let Some(render_toggle) = render_toggle {
21845 let toggle_callback =
21846 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21847 if folded {
21848 editor.update(cx, |editor, cx| {
21849 editor.fold_at(buffer_row, window, cx)
21850 });
21851 } else {
21852 editor.update(cx, |editor, cx| {
21853 editor.unfold_at(buffer_row, window, cx)
21854 });
21855 }
21856 });
21857 return Some((render_toggle)(
21858 buffer_row,
21859 folded,
21860 toggle_callback,
21861 window,
21862 cx,
21863 ));
21864 }
21865 }
21866 }
21867 }
21868
21869 is_foldable |= self.starts_indent(buffer_row);
21870
21871 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21872 Some(
21873 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21874 .toggle_state(folded)
21875 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21876 if folded {
21877 this.unfold_at(buffer_row, window, cx);
21878 } else {
21879 this.fold_at(buffer_row, window, cx);
21880 }
21881 }))
21882 .into_any_element(),
21883 )
21884 } else {
21885 None
21886 }
21887 }
21888
21889 pub fn render_crease_trailer(
21890 &self,
21891 buffer_row: MultiBufferRow,
21892 window: &mut Window,
21893 cx: &mut App,
21894 ) -> Option<AnyElement> {
21895 let folded = self.is_line_folded(buffer_row);
21896 if let Crease::Inline { render_trailer, .. } = self
21897 .crease_snapshot
21898 .query_row(buffer_row, &self.buffer_snapshot)?
21899 {
21900 let render_trailer = render_trailer.as_ref()?;
21901 Some(render_trailer(buffer_row, folded, window, cx))
21902 } else {
21903 None
21904 }
21905 }
21906}
21907
21908impl Deref for EditorSnapshot {
21909 type Target = DisplaySnapshot;
21910
21911 fn deref(&self) -> &Self::Target {
21912 &self.display_snapshot
21913 }
21914}
21915
21916#[derive(Clone, Debug, PartialEq, Eq)]
21917pub enum EditorEvent {
21918 InputIgnored {
21919 text: Arc<str>,
21920 },
21921 InputHandled {
21922 utf16_range_to_replace: Option<Range<isize>>,
21923 text: Arc<str>,
21924 },
21925 ExcerptsAdded {
21926 buffer: Entity<Buffer>,
21927 predecessor: ExcerptId,
21928 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21929 },
21930 ExcerptsRemoved {
21931 ids: Vec<ExcerptId>,
21932 removed_buffer_ids: Vec<BufferId>,
21933 },
21934 BufferFoldToggled {
21935 ids: Vec<ExcerptId>,
21936 folded: bool,
21937 },
21938 ExcerptsEdited {
21939 ids: Vec<ExcerptId>,
21940 },
21941 ExcerptsExpanded {
21942 ids: Vec<ExcerptId>,
21943 },
21944 BufferEdited,
21945 Edited {
21946 transaction_id: clock::Lamport,
21947 },
21948 Reparsed(BufferId),
21949 Focused,
21950 FocusedIn,
21951 Blurred,
21952 DirtyChanged,
21953 Saved,
21954 TitleChanged,
21955 DiffBaseChanged,
21956 SelectionsChanged {
21957 local: bool,
21958 },
21959 ScrollPositionChanged {
21960 local: bool,
21961 autoscroll: bool,
21962 },
21963 Closed,
21964 TransactionUndone {
21965 transaction_id: clock::Lamport,
21966 },
21967 TransactionBegun {
21968 transaction_id: clock::Lamport,
21969 },
21970 Reloaded,
21971 CursorShapeChanged,
21972 PushedToNavHistory {
21973 anchor: Anchor,
21974 is_deactivate: bool,
21975 },
21976}
21977
21978impl EventEmitter<EditorEvent> for Editor {}
21979
21980impl Focusable for Editor {
21981 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21982 self.focus_handle.clone()
21983 }
21984}
21985
21986impl Render for Editor {
21987 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21988 let settings = ThemeSettings::get_global(cx);
21989
21990 let mut text_style = match self.mode {
21991 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21992 color: cx.theme().colors().editor_foreground,
21993 font_family: settings.ui_font.family.clone(),
21994 font_features: settings.ui_font.features.clone(),
21995 font_fallbacks: settings.ui_font.fallbacks.clone(),
21996 font_size: rems(0.875).into(),
21997 font_weight: settings.ui_font.weight,
21998 line_height: relative(settings.buffer_line_height.value()),
21999 ..Default::default()
22000 },
22001 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22002 color: cx.theme().colors().editor_foreground,
22003 font_family: settings.buffer_font.family.clone(),
22004 font_features: settings.buffer_font.features.clone(),
22005 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22006 font_size: settings.buffer_font_size(cx).into(),
22007 font_weight: settings.buffer_font.weight,
22008 line_height: relative(settings.buffer_line_height.value()),
22009 ..Default::default()
22010 },
22011 };
22012 if let Some(text_style_refinement) = &self.text_style_refinement {
22013 text_style.refine(text_style_refinement)
22014 }
22015
22016 let background = match self.mode {
22017 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22018 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22019 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22020 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22021 };
22022
22023 EditorElement::new(
22024 &cx.entity(),
22025 EditorStyle {
22026 background,
22027 local_player: cx.theme().players().local(),
22028 text: text_style,
22029 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22030 syntax: cx.theme().syntax().clone(),
22031 status: cx.theme().status().clone(),
22032 inlay_hints_style: make_inlay_hints_style(cx),
22033 inline_completion_styles: make_suggestion_styles(cx),
22034 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22035 show_underlines: !self.mode.is_minimap(),
22036 },
22037 )
22038 }
22039}
22040
22041impl EntityInputHandler for Editor {
22042 fn text_for_range(
22043 &mut self,
22044 range_utf16: Range<usize>,
22045 adjusted_range: &mut Option<Range<usize>>,
22046 _: &mut Window,
22047 cx: &mut Context<Self>,
22048 ) -> Option<String> {
22049 let snapshot = self.buffer.read(cx).read(cx);
22050 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22051 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22052 if (start.0..end.0) != range_utf16 {
22053 adjusted_range.replace(start.0..end.0);
22054 }
22055 Some(snapshot.text_for_range(start..end).collect())
22056 }
22057
22058 fn selected_text_range(
22059 &mut self,
22060 ignore_disabled_input: bool,
22061 _: &mut Window,
22062 cx: &mut Context<Self>,
22063 ) -> Option<UTF16Selection> {
22064 // Prevent the IME menu from appearing when holding down an alphabetic key
22065 // while input is disabled.
22066 if !ignore_disabled_input && !self.input_enabled {
22067 return None;
22068 }
22069
22070 let selection = self.selections.newest::<OffsetUtf16>(cx);
22071 let range = selection.range();
22072
22073 Some(UTF16Selection {
22074 range: range.start.0..range.end.0,
22075 reversed: selection.reversed,
22076 })
22077 }
22078
22079 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22080 let snapshot = self.buffer.read(cx).read(cx);
22081 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22082 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22083 }
22084
22085 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22086 self.clear_highlights::<InputComposition>(cx);
22087 self.ime_transaction.take();
22088 }
22089
22090 fn replace_text_in_range(
22091 &mut self,
22092 range_utf16: Option<Range<usize>>,
22093 text: &str,
22094 window: &mut Window,
22095 cx: &mut Context<Self>,
22096 ) {
22097 if !self.input_enabled {
22098 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22099 return;
22100 }
22101
22102 self.transact(window, cx, |this, window, cx| {
22103 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22104 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22105 Some(this.selection_replacement_ranges(range_utf16, cx))
22106 } else {
22107 this.marked_text_ranges(cx)
22108 };
22109
22110 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22111 let newest_selection_id = this.selections.newest_anchor().id;
22112 this.selections
22113 .all::<OffsetUtf16>(cx)
22114 .iter()
22115 .zip(ranges_to_replace.iter())
22116 .find_map(|(selection, range)| {
22117 if selection.id == newest_selection_id {
22118 Some(
22119 (range.start.0 as isize - selection.head().0 as isize)
22120 ..(range.end.0 as isize - selection.head().0 as isize),
22121 )
22122 } else {
22123 None
22124 }
22125 })
22126 });
22127
22128 cx.emit(EditorEvent::InputHandled {
22129 utf16_range_to_replace: range_to_replace,
22130 text: text.into(),
22131 });
22132
22133 if let Some(new_selected_ranges) = new_selected_ranges {
22134 this.change_selections(None, window, cx, |selections| {
22135 selections.select_ranges(new_selected_ranges)
22136 });
22137 this.backspace(&Default::default(), window, cx);
22138 }
22139
22140 this.handle_input(text, window, cx);
22141 });
22142
22143 if let Some(transaction) = self.ime_transaction {
22144 self.buffer.update(cx, |buffer, cx| {
22145 buffer.group_until_transaction(transaction, cx);
22146 });
22147 }
22148
22149 self.unmark_text(window, cx);
22150 }
22151
22152 fn replace_and_mark_text_in_range(
22153 &mut self,
22154 range_utf16: Option<Range<usize>>,
22155 text: &str,
22156 new_selected_range_utf16: Option<Range<usize>>,
22157 window: &mut Window,
22158 cx: &mut Context<Self>,
22159 ) {
22160 if !self.input_enabled {
22161 return;
22162 }
22163
22164 let transaction = self.transact(window, cx, |this, window, cx| {
22165 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22166 let snapshot = this.buffer.read(cx).read(cx);
22167 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22168 for marked_range in &mut marked_ranges {
22169 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22170 marked_range.start.0 += relative_range_utf16.start;
22171 marked_range.start =
22172 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22173 marked_range.end =
22174 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22175 }
22176 }
22177 Some(marked_ranges)
22178 } else if let Some(range_utf16) = range_utf16 {
22179 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22180 Some(this.selection_replacement_ranges(range_utf16, cx))
22181 } else {
22182 None
22183 };
22184
22185 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22186 let newest_selection_id = this.selections.newest_anchor().id;
22187 this.selections
22188 .all::<OffsetUtf16>(cx)
22189 .iter()
22190 .zip(ranges_to_replace.iter())
22191 .find_map(|(selection, range)| {
22192 if selection.id == newest_selection_id {
22193 Some(
22194 (range.start.0 as isize - selection.head().0 as isize)
22195 ..(range.end.0 as isize - selection.head().0 as isize),
22196 )
22197 } else {
22198 None
22199 }
22200 })
22201 });
22202
22203 cx.emit(EditorEvent::InputHandled {
22204 utf16_range_to_replace: range_to_replace,
22205 text: text.into(),
22206 });
22207
22208 if let Some(ranges) = ranges_to_replace {
22209 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22210 }
22211
22212 let marked_ranges = {
22213 let snapshot = this.buffer.read(cx).read(cx);
22214 this.selections
22215 .disjoint_anchors()
22216 .iter()
22217 .map(|selection| {
22218 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22219 })
22220 .collect::<Vec<_>>()
22221 };
22222
22223 if text.is_empty() {
22224 this.unmark_text(window, cx);
22225 } else {
22226 this.highlight_text::<InputComposition>(
22227 marked_ranges.clone(),
22228 HighlightStyle {
22229 underline: Some(UnderlineStyle {
22230 thickness: px(1.),
22231 color: None,
22232 wavy: false,
22233 }),
22234 ..Default::default()
22235 },
22236 cx,
22237 );
22238 }
22239
22240 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22241 let use_autoclose = this.use_autoclose;
22242 let use_auto_surround = this.use_auto_surround;
22243 this.set_use_autoclose(false);
22244 this.set_use_auto_surround(false);
22245 this.handle_input(text, window, cx);
22246 this.set_use_autoclose(use_autoclose);
22247 this.set_use_auto_surround(use_auto_surround);
22248
22249 if let Some(new_selected_range) = new_selected_range_utf16 {
22250 let snapshot = this.buffer.read(cx).read(cx);
22251 let new_selected_ranges = marked_ranges
22252 .into_iter()
22253 .map(|marked_range| {
22254 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22255 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22256 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22257 snapshot.clip_offset_utf16(new_start, Bias::Left)
22258 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22259 })
22260 .collect::<Vec<_>>();
22261
22262 drop(snapshot);
22263 this.change_selections(None, window, cx, |selections| {
22264 selections.select_ranges(new_selected_ranges)
22265 });
22266 }
22267 });
22268
22269 self.ime_transaction = self.ime_transaction.or(transaction);
22270 if let Some(transaction) = self.ime_transaction {
22271 self.buffer.update(cx, |buffer, cx| {
22272 buffer.group_until_transaction(transaction, cx);
22273 });
22274 }
22275
22276 if self.text_highlights::<InputComposition>(cx).is_none() {
22277 self.ime_transaction.take();
22278 }
22279 }
22280
22281 fn bounds_for_range(
22282 &mut self,
22283 range_utf16: Range<usize>,
22284 element_bounds: gpui::Bounds<Pixels>,
22285 window: &mut Window,
22286 cx: &mut Context<Self>,
22287 ) -> Option<gpui::Bounds<Pixels>> {
22288 let text_layout_details = self.text_layout_details(window);
22289 let gpui::Size {
22290 width: em_width,
22291 height: line_height,
22292 } = self.character_size(window);
22293
22294 let snapshot = self.snapshot(window, cx);
22295 let scroll_position = snapshot.scroll_position();
22296 let scroll_left = scroll_position.x * em_width;
22297
22298 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22299 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22300 + self.gutter_dimensions.width
22301 + self.gutter_dimensions.margin;
22302 let y = line_height * (start.row().as_f32() - scroll_position.y);
22303
22304 Some(Bounds {
22305 origin: element_bounds.origin + point(x, y),
22306 size: size(em_width, line_height),
22307 })
22308 }
22309
22310 fn character_index_for_point(
22311 &mut self,
22312 point: gpui::Point<Pixels>,
22313 _window: &mut Window,
22314 _cx: &mut Context<Self>,
22315 ) -> Option<usize> {
22316 let position_map = self.last_position_map.as_ref()?;
22317 if !position_map.text_hitbox.contains(&point) {
22318 return None;
22319 }
22320 let display_point = position_map.point_for_position(point).previous_valid;
22321 let anchor = position_map
22322 .snapshot
22323 .display_point_to_anchor(display_point, Bias::Left);
22324 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22325 Some(utf16_offset.0)
22326 }
22327}
22328
22329trait SelectionExt {
22330 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22331 fn spanned_rows(
22332 &self,
22333 include_end_if_at_line_start: bool,
22334 map: &DisplaySnapshot,
22335 ) -> Range<MultiBufferRow>;
22336}
22337
22338impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22339 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22340 let start = self
22341 .start
22342 .to_point(&map.buffer_snapshot)
22343 .to_display_point(map);
22344 let end = self
22345 .end
22346 .to_point(&map.buffer_snapshot)
22347 .to_display_point(map);
22348 if self.reversed {
22349 end..start
22350 } else {
22351 start..end
22352 }
22353 }
22354
22355 fn spanned_rows(
22356 &self,
22357 include_end_if_at_line_start: bool,
22358 map: &DisplaySnapshot,
22359 ) -> Range<MultiBufferRow> {
22360 let start = self.start.to_point(&map.buffer_snapshot);
22361 let mut end = self.end.to_point(&map.buffer_snapshot);
22362 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22363 end.row -= 1;
22364 }
22365
22366 let buffer_start = map.prev_line_boundary(start).0;
22367 let buffer_end = map.next_line_boundary(end).0;
22368 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22369 }
22370}
22371
22372impl<T: InvalidationRegion> InvalidationStack<T> {
22373 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22374 where
22375 S: Clone + ToOffset,
22376 {
22377 while let Some(region) = self.last() {
22378 let all_selections_inside_invalidation_ranges =
22379 if selections.len() == region.ranges().len() {
22380 selections
22381 .iter()
22382 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22383 .all(|(selection, invalidation_range)| {
22384 let head = selection.head().to_offset(buffer);
22385 invalidation_range.start <= head && invalidation_range.end >= head
22386 })
22387 } else {
22388 false
22389 };
22390
22391 if all_selections_inside_invalidation_ranges {
22392 break;
22393 } else {
22394 self.pop();
22395 }
22396 }
22397 }
22398}
22399
22400impl<T> Default for InvalidationStack<T> {
22401 fn default() -> Self {
22402 Self(Default::default())
22403 }
22404}
22405
22406impl<T> Deref for InvalidationStack<T> {
22407 type Target = Vec<T>;
22408
22409 fn deref(&self) -> &Self::Target {
22410 &self.0
22411 }
22412}
22413
22414impl<T> DerefMut for InvalidationStack<T> {
22415 fn deref_mut(&mut self) -> &mut Self::Target {
22416 &mut self.0
22417 }
22418}
22419
22420impl InvalidationRegion for SnippetState {
22421 fn ranges(&self) -> &[Range<Anchor>] {
22422 &self.ranges[self.active_index]
22423 }
22424}
22425
22426fn inline_completion_edit_text(
22427 current_snapshot: &BufferSnapshot,
22428 edits: &[(Range<Anchor>, String)],
22429 edit_preview: &EditPreview,
22430 include_deletions: bool,
22431 cx: &App,
22432) -> HighlightedText {
22433 let edits = edits
22434 .iter()
22435 .map(|(anchor, text)| {
22436 (
22437 anchor.start.text_anchor..anchor.end.text_anchor,
22438 text.clone(),
22439 )
22440 })
22441 .collect::<Vec<_>>();
22442
22443 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22444}
22445
22446pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22447 match severity {
22448 lsp::DiagnosticSeverity::ERROR => colors.error,
22449 lsp::DiagnosticSeverity::WARNING => colors.warning,
22450 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22451 lsp::DiagnosticSeverity::HINT => colors.info,
22452 _ => colors.ignored,
22453 }
22454}
22455
22456pub fn styled_runs_for_code_label<'a>(
22457 label: &'a CodeLabel,
22458 syntax_theme: &'a theme::SyntaxTheme,
22459) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22460 let fade_out = HighlightStyle {
22461 fade_out: Some(0.35),
22462 ..Default::default()
22463 };
22464
22465 let mut prev_end = label.filter_range.end;
22466 label
22467 .runs
22468 .iter()
22469 .enumerate()
22470 .flat_map(move |(ix, (range, highlight_id))| {
22471 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22472 style
22473 } else {
22474 return Default::default();
22475 };
22476 let mut muted_style = style;
22477 muted_style.highlight(fade_out);
22478
22479 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22480 if range.start >= label.filter_range.end {
22481 if range.start > prev_end {
22482 runs.push((prev_end..range.start, fade_out));
22483 }
22484 runs.push((range.clone(), muted_style));
22485 } else if range.end <= label.filter_range.end {
22486 runs.push((range.clone(), style));
22487 } else {
22488 runs.push((range.start..label.filter_range.end, style));
22489 runs.push((label.filter_range.end..range.end, muted_style));
22490 }
22491 prev_end = cmp::max(prev_end, range.end);
22492
22493 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22494 runs.push((prev_end..label.text.len(), fade_out));
22495 }
22496
22497 runs
22498 })
22499}
22500
22501pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22502 let mut prev_index = 0;
22503 let mut prev_codepoint: Option<char> = None;
22504 text.char_indices()
22505 .chain([(text.len(), '\0')])
22506 .filter_map(move |(index, codepoint)| {
22507 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22508 let is_boundary = index == text.len()
22509 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22510 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22511 if is_boundary {
22512 let chunk = &text[prev_index..index];
22513 prev_index = index;
22514 Some(chunk)
22515 } else {
22516 None
22517 }
22518 })
22519}
22520
22521pub trait RangeToAnchorExt: Sized {
22522 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22523
22524 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22525 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22526 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22527 }
22528}
22529
22530impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22531 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22532 let start_offset = self.start.to_offset(snapshot);
22533 let end_offset = self.end.to_offset(snapshot);
22534 if start_offset == end_offset {
22535 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22536 } else {
22537 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22538 }
22539 }
22540}
22541
22542pub trait RowExt {
22543 fn as_f32(&self) -> f32;
22544
22545 fn next_row(&self) -> Self;
22546
22547 fn previous_row(&self) -> Self;
22548
22549 fn minus(&self, other: Self) -> u32;
22550}
22551
22552impl RowExt for DisplayRow {
22553 fn as_f32(&self) -> f32 {
22554 self.0 as f32
22555 }
22556
22557 fn next_row(&self) -> Self {
22558 Self(self.0 + 1)
22559 }
22560
22561 fn previous_row(&self) -> Self {
22562 Self(self.0.saturating_sub(1))
22563 }
22564
22565 fn minus(&self, other: Self) -> u32 {
22566 self.0 - other.0
22567 }
22568}
22569
22570impl RowExt for MultiBufferRow {
22571 fn as_f32(&self) -> f32 {
22572 self.0 as f32
22573 }
22574
22575 fn next_row(&self) -> Self {
22576 Self(self.0 + 1)
22577 }
22578
22579 fn previous_row(&self) -> Self {
22580 Self(self.0.saturating_sub(1))
22581 }
22582
22583 fn minus(&self, other: Self) -> u32 {
22584 self.0 - other.0
22585 }
22586}
22587
22588trait RowRangeExt {
22589 type Row;
22590
22591 fn len(&self) -> usize;
22592
22593 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22594}
22595
22596impl RowRangeExt for Range<MultiBufferRow> {
22597 type Row = MultiBufferRow;
22598
22599 fn len(&self) -> usize {
22600 (self.end.0 - self.start.0) as usize
22601 }
22602
22603 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22604 (self.start.0..self.end.0).map(MultiBufferRow)
22605 }
22606}
22607
22608impl RowRangeExt for Range<DisplayRow> {
22609 type Row = DisplayRow;
22610
22611 fn len(&self) -> usize {
22612 (self.end.0 - self.start.0) as usize
22613 }
22614
22615 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22616 (self.start.0..self.end.0).map(DisplayRow)
22617 }
22618}
22619
22620/// If select range has more than one line, we
22621/// just point the cursor to range.start.
22622fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22623 if range.start.row == range.end.row {
22624 range
22625 } else {
22626 range.start..range.start
22627 }
22628}
22629pub struct KillRing(ClipboardItem);
22630impl Global for KillRing {}
22631
22632const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22633
22634enum BreakpointPromptEditAction {
22635 Log,
22636 Condition,
22637 HitCondition,
22638}
22639
22640struct BreakpointPromptEditor {
22641 pub(crate) prompt: Entity<Editor>,
22642 editor: WeakEntity<Editor>,
22643 breakpoint_anchor: Anchor,
22644 breakpoint: Breakpoint,
22645 edit_action: BreakpointPromptEditAction,
22646 block_ids: HashSet<CustomBlockId>,
22647 editor_margins: Arc<Mutex<EditorMargins>>,
22648 _subscriptions: Vec<Subscription>,
22649}
22650
22651impl BreakpointPromptEditor {
22652 const MAX_LINES: u8 = 4;
22653
22654 fn new(
22655 editor: WeakEntity<Editor>,
22656 breakpoint_anchor: Anchor,
22657 breakpoint: Breakpoint,
22658 edit_action: BreakpointPromptEditAction,
22659 window: &mut Window,
22660 cx: &mut Context<Self>,
22661 ) -> Self {
22662 let base_text = match edit_action {
22663 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22664 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22665 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22666 }
22667 .map(|msg| msg.to_string())
22668 .unwrap_or_default();
22669
22670 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22672
22673 let prompt = cx.new(|cx| {
22674 let mut prompt = Editor::new(
22675 EditorMode::AutoHeight {
22676 min_lines: 1,
22677 max_lines: Self::MAX_LINES as usize,
22678 },
22679 buffer,
22680 None,
22681 window,
22682 cx,
22683 );
22684 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22685 prompt.set_show_cursor_when_unfocused(false, cx);
22686 prompt.set_placeholder_text(
22687 match edit_action {
22688 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22689 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22690 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22691 },
22692 cx,
22693 );
22694
22695 prompt
22696 });
22697
22698 Self {
22699 prompt,
22700 editor,
22701 breakpoint_anchor,
22702 breakpoint,
22703 edit_action,
22704 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22705 block_ids: Default::default(),
22706 _subscriptions: vec![],
22707 }
22708 }
22709
22710 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22711 self.block_ids.extend(block_ids)
22712 }
22713
22714 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22715 if let Some(editor) = self.editor.upgrade() {
22716 let message = self
22717 .prompt
22718 .read(cx)
22719 .buffer
22720 .read(cx)
22721 .as_singleton()
22722 .expect("A multi buffer in breakpoint prompt isn't possible")
22723 .read(cx)
22724 .as_rope()
22725 .to_string();
22726
22727 editor.update(cx, |editor, cx| {
22728 editor.edit_breakpoint_at_anchor(
22729 self.breakpoint_anchor,
22730 self.breakpoint.clone(),
22731 match self.edit_action {
22732 BreakpointPromptEditAction::Log => {
22733 BreakpointEditAction::EditLogMessage(message.into())
22734 }
22735 BreakpointPromptEditAction::Condition => {
22736 BreakpointEditAction::EditCondition(message.into())
22737 }
22738 BreakpointPromptEditAction::HitCondition => {
22739 BreakpointEditAction::EditHitCondition(message.into())
22740 }
22741 },
22742 cx,
22743 );
22744
22745 editor.remove_blocks(self.block_ids.clone(), None, cx);
22746 cx.focus_self(window);
22747 });
22748 }
22749 }
22750
22751 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22752 self.editor
22753 .update(cx, |editor, cx| {
22754 editor.remove_blocks(self.block_ids.clone(), None, cx);
22755 window.focus(&editor.focus_handle);
22756 })
22757 .log_err();
22758 }
22759
22760 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22761 let settings = ThemeSettings::get_global(cx);
22762 let text_style = TextStyle {
22763 color: if self.prompt.read(cx).read_only(cx) {
22764 cx.theme().colors().text_disabled
22765 } else {
22766 cx.theme().colors().text
22767 },
22768 font_family: settings.buffer_font.family.clone(),
22769 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22770 font_size: settings.buffer_font_size(cx).into(),
22771 font_weight: settings.buffer_font.weight,
22772 line_height: relative(settings.buffer_line_height.value()),
22773 ..Default::default()
22774 };
22775 EditorElement::new(
22776 &self.prompt,
22777 EditorStyle {
22778 background: cx.theme().colors().editor_background,
22779 local_player: cx.theme().players().local(),
22780 text: text_style,
22781 ..Default::default()
22782 },
22783 )
22784 }
22785}
22786
22787impl Render for BreakpointPromptEditor {
22788 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22789 let editor_margins = *self.editor_margins.lock();
22790 let gutter_dimensions = editor_margins.gutter;
22791 h_flex()
22792 .key_context("Editor")
22793 .bg(cx.theme().colors().editor_background)
22794 .border_y_1()
22795 .border_color(cx.theme().status().info_border)
22796 .size_full()
22797 .py(window.line_height() / 2.5)
22798 .on_action(cx.listener(Self::confirm))
22799 .on_action(cx.listener(Self::cancel))
22800 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22801 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22802 }
22803}
22804
22805impl Focusable for BreakpointPromptEditor {
22806 fn focus_handle(&self, cx: &App) -> FocusHandle {
22807 self.prompt.focus_handle(cx)
22808 }
22809}
22810
22811fn all_edits_insertions_or_deletions(
22812 edits: &Vec<(Range<Anchor>, String)>,
22813 snapshot: &MultiBufferSnapshot,
22814) -> bool {
22815 let mut all_insertions = true;
22816 let mut all_deletions = true;
22817
22818 for (range, new_text) in edits.iter() {
22819 let range_is_empty = range.to_offset(&snapshot).is_empty();
22820 let text_is_empty = new_text.is_empty();
22821
22822 if range_is_empty != text_is_empty {
22823 if range_is_empty {
22824 all_deletions = false;
22825 } else {
22826 all_insertions = false;
22827 }
22828 } else {
22829 return false;
22830 }
22831
22832 if !all_insertions && !all_deletions {
22833 return false;
22834 }
22835 }
22836 all_insertions || all_deletions
22837}
22838
22839struct MissingEditPredictionKeybindingTooltip;
22840
22841impl Render for MissingEditPredictionKeybindingTooltip {
22842 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22843 ui::tooltip_container(window, cx, |container, _, cx| {
22844 container
22845 .flex_shrink_0()
22846 .max_w_80()
22847 .min_h(rems_from_px(124.))
22848 .justify_between()
22849 .child(
22850 v_flex()
22851 .flex_1()
22852 .text_ui_sm(cx)
22853 .child(Label::new("Conflict with Accept Keybinding"))
22854 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22855 )
22856 .child(
22857 h_flex()
22858 .pb_1()
22859 .gap_1()
22860 .items_end()
22861 .w_full()
22862 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22863 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22864 }))
22865 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22866 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22867 })),
22868 )
22869 })
22870 }
22871}
22872
22873#[derive(Debug, Clone, Copy, PartialEq)]
22874pub struct LineHighlight {
22875 pub background: Background,
22876 pub border: Option<gpui::Hsla>,
22877 pub include_gutter: bool,
22878 pub type_id: Option<TypeId>,
22879}
22880
22881fn render_diff_hunk_controls(
22882 row: u32,
22883 status: &DiffHunkStatus,
22884 hunk_range: Range<Anchor>,
22885 is_created_file: bool,
22886 line_height: Pixels,
22887 editor: &Entity<Editor>,
22888 _window: &mut Window,
22889 cx: &mut App,
22890) -> AnyElement {
22891 h_flex()
22892 .h(line_height)
22893 .mr_1()
22894 .gap_1()
22895 .px_0p5()
22896 .pb_1()
22897 .border_x_1()
22898 .border_b_1()
22899 .border_color(cx.theme().colors().border_variant)
22900 .rounded_b_lg()
22901 .bg(cx.theme().colors().editor_background)
22902 .gap_1()
22903 .block_mouse_except_scroll()
22904 .shadow_md()
22905 .child(if status.has_secondary_hunk() {
22906 Button::new(("stage", row as u64), "Stage")
22907 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22908 .tooltip({
22909 let focus_handle = editor.focus_handle(cx);
22910 move |window, cx| {
22911 Tooltip::for_action_in(
22912 "Stage Hunk",
22913 &::git::ToggleStaged,
22914 &focus_handle,
22915 window,
22916 cx,
22917 )
22918 }
22919 })
22920 .on_click({
22921 let editor = editor.clone();
22922 move |_event, _window, cx| {
22923 editor.update(cx, |editor, cx| {
22924 editor.stage_or_unstage_diff_hunks(
22925 true,
22926 vec![hunk_range.start..hunk_range.start],
22927 cx,
22928 );
22929 });
22930 }
22931 })
22932 } else {
22933 Button::new(("unstage", row as u64), "Unstage")
22934 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22935 .tooltip({
22936 let focus_handle = editor.focus_handle(cx);
22937 move |window, cx| {
22938 Tooltip::for_action_in(
22939 "Unstage Hunk",
22940 &::git::ToggleStaged,
22941 &focus_handle,
22942 window,
22943 cx,
22944 )
22945 }
22946 })
22947 .on_click({
22948 let editor = editor.clone();
22949 move |_event, _window, cx| {
22950 editor.update(cx, |editor, cx| {
22951 editor.stage_or_unstage_diff_hunks(
22952 false,
22953 vec![hunk_range.start..hunk_range.start],
22954 cx,
22955 );
22956 });
22957 }
22958 })
22959 })
22960 .child(
22961 Button::new(("restore", row as u64), "Restore")
22962 .tooltip({
22963 let focus_handle = editor.focus_handle(cx);
22964 move |window, cx| {
22965 Tooltip::for_action_in(
22966 "Restore Hunk",
22967 &::git::Restore,
22968 &focus_handle,
22969 window,
22970 cx,
22971 )
22972 }
22973 })
22974 .on_click({
22975 let editor = editor.clone();
22976 move |_event, window, cx| {
22977 editor.update(cx, |editor, cx| {
22978 let snapshot = editor.snapshot(window, cx);
22979 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22980 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22981 });
22982 }
22983 })
22984 .disabled(is_created_file),
22985 )
22986 .when(
22987 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22988 |el| {
22989 el.child(
22990 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22991 .shape(IconButtonShape::Square)
22992 .icon_size(IconSize::Small)
22993 // .disabled(!has_multiple_hunks)
22994 .tooltip({
22995 let focus_handle = editor.focus_handle(cx);
22996 move |window, cx| {
22997 Tooltip::for_action_in(
22998 "Next Hunk",
22999 &GoToHunk,
23000 &focus_handle,
23001 window,
23002 cx,
23003 )
23004 }
23005 })
23006 .on_click({
23007 let editor = editor.clone();
23008 move |_event, window, cx| {
23009 editor.update(cx, |editor, cx| {
23010 let snapshot = editor.snapshot(window, cx);
23011 let position =
23012 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23013 editor.go_to_hunk_before_or_after_position(
23014 &snapshot,
23015 position,
23016 Direction::Next,
23017 window,
23018 cx,
23019 );
23020 editor.expand_selected_diff_hunks(cx);
23021 });
23022 }
23023 }),
23024 )
23025 .child(
23026 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23027 .shape(IconButtonShape::Square)
23028 .icon_size(IconSize::Small)
23029 // .disabled(!has_multiple_hunks)
23030 .tooltip({
23031 let focus_handle = editor.focus_handle(cx);
23032 move |window, cx| {
23033 Tooltip::for_action_in(
23034 "Previous Hunk",
23035 &GoToPreviousHunk,
23036 &focus_handle,
23037 window,
23038 cx,
23039 )
23040 }
23041 })
23042 .on_click({
23043 let editor = editor.clone();
23044 move |_event, window, cx| {
23045 editor.update(cx, |editor, cx| {
23046 let snapshot = editor.snapshot(window, cx);
23047 let point =
23048 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23049 editor.go_to_hunk_before_or_after_position(
23050 &snapshot,
23051 point,
23052 Direction::Prev,
23053 window,
23054 cx,
23055 );
23056 editor.expand_selected_diff_hunks(cx);
23057 });
23058 }
23059 }),
23060 )
23061 },
23062 )
23063 .into_any_element()
23064}